Git Fork 의 Custom Commands
Git 클라이언트 Fork의 Custom Commands 기능을 활용하여 Git Cherry 명령어 설정 및 환경 변수 관련 이슈 해결 방법을 소개합니다.
Contents
- 개요
 - Custom Commands
 - 무시할 커밋 파일 존재 여부 확인
 - 무시할 커밋 해시 목록을 배열에 저장
 - cherry-pick 결과에서 필요한 정보 추출
 - 각 라인 처리
 - 기타
 - 설정 파일 디렉토리
 - Custom Commands 파일
 - 무시할 커밋 파일 존재 여부 확인
 - 무시할 커밋 해시 목록을 배열에 저장
 - cherry-pick 결과에서 필요한 정보 추출
 - 각 라인 처리
 - External Diff, Merge Tool
 
개요
평소에 Git Client 로 Fork를 사용한다. IDE 에서 제공해 주는 Git Client 도 충분히 빠르고 좋지만, Code Editor 와 형상관리 Client 를 분리하여 사용하는 것이 더 편하다고 느끼기 때문에 독립적인 Git Client 소프트웨어를 설치하여 사용하는 것을 선호한다. 4년정도 Fork 를 사용하였고, 개인적으로 유료 라이센스를 구매하여 사용하고 있다.

Fork 는 2명의 개발자에 의해 개발되고 있는데, 이 둘을 응원하고 싶은 마음이 커져 유료 라이센스를 구매하여 사용하고 있다.
이 글에서는 Fork 의 기본적인 사용법을 다루지 않는다. 개인적으로 유용하게 사용하고 있는 "Custom Command" 에 대해 정리하는 글이다.
Custom Commands
개인적으로 개발을 하며 필요한 Git 의 기능들을 Fork 에서는 대부분 제공한다. Commit, Branch 관련 기본적인 기능들은 물론 Cherry Pick, Interactive Rebase, History, Blame 등 일반적인 상황에서 필요한 기능들을 편리한 UI 로 제공한다. 다만 속해있는 팀 / 작업해야 할 저장소의 특성에 따라 특수하게 필요한 기능들이 존재한다. 나의 경우에는 git cherry 명령어가 그러한 기능이었다. Fork 는 사용자가 정의한 Custom 기능들을 UI 를 통해 실행할 수 있는 "Custom Commands" 라는 기능을 제공한다. 

Fork 에서 제공하는 Custom Command 설정 화면
Git Cherry
현재 업무에서는 브랜치 전략으로 Git Flow 를 사용하고 있다. 

출처 : 우아한형제들 기술 블로그
main 브랜치에 feature 의 기능들을 추가할 때, 커밋들을 Cherry Pick 기능을 사용할 때가 많다. 하지만 만약 배포 시 Cherry Pick 해야 할 커밋이 많아질 경우, 누락된 커밋이 발생할까 불안해지기도 한다. git cherry 는 이러한 경우에 사용하기 유용한 명령어이다. 아직 upstream 에 Cherry Pick 되지 않은 커밋들을 출력해 준다. 만약 origin/develop 브랜치에서 origin/main 으로 Cherry Pick 되지 않은 커밋들을 살펴보고 싶을 때에는 아래와 같은 명령어를 사용하면 된다.
git cherry remotes/origin/main remotes/origin/develop | grep "^+ "
매번 각자 다른 브랜치에 대해 위의 명령어를 수행하는 것 보다, Fork 에서 Custom Command 로 등록하여 사용하면 편하겠다는 생각이 들어 아래와 같은 스크립트를 만들어서 사용중이다.
#!/bin/bash
ROOT_BRANCH=$1{ref}
COMPARE_BRANCH=$2{ref}
IGNORE_FILE_NAME=".gitcherryignore"
IGNORE_FILE="${repo:path}/$IGNORE_FILE_NAME"
# 무시할 커밋 파일 존재 여부 확인
if [ ! -f "$IGNORE_FILE" ]; then
  echo "Git Cherry에 무시할 커밋을 추가하시려면 $IGNORE_FILE를 생성해 주세요
"
fi
# 무시할 커밋 해시 목록을 배열에 저장
ignore_hashes=()
if [ -f "$IGNORE_FILE" ]; then
  while IFS= read -r ignore_line; do
    # 주석 라인 무시
    [[ "$ignore_line" =~ ^[#//--] ]] && continue
    ignore_hashes+=($(echo "$ignore_line" | awk '{print $1}'))
  done < "$IGNORE_FILE"
fi
# cherry-pick 결과에서 필요한 정보 추출
cherry_result=$(git cherry $ROOT_BRANCH $COMPARE_BRANCH | grep "^+ " | cut -d " " -f 2 | xargs -I {} git log --pretty=format:"%H %an %s %n" -n 1 {})
message_result=""
commit_result=""
# 각 라인 처리
while IFS= read -r line; do
  commit_hash=$(echo "$line" | awk '{print $1}')
  # 무시할 커밋인지 확인
  print_commit=true
  for ignore_hash in "${ignore_hashes[@]}"; do
    if [[ "$commit_hash" == "$ignore_hash"* ]]; then
      print_commit=false
      break
    fi
  done
  # 조건에 따라 커밋을 출력
  if $print_commit; then
    message_result+="$line
"
    commit_result+="$commit_hash "
  fi
done <<< "$cherry_result"
echo "$message_result"
echo "$commit_result" | pbcopy
위의 스크립트는 git cherry 명령어를 사용하여 커밋이 되지 않은 커밋들을 추출한 후, 이를 출력해 준 후 커밋 해시를 클립보드에 복사해 준다. 이 때, 각 레포지토리에 .gitcherryignore 파일이 존재하면, 출력 목록에서 제외시켜주는 기능을 추가하였다. 이와 같은 기능을 추가한 이유는, Cherry Pick 시 운이 안좋아 Conflict 가 난 경우 Cherry Pick 된 커밋들도 git cherry 의 결과로 출력될 수 있기 때문이다.
.gitcherryignore 은 git 에서 공식적으로 지원하는 설정 파일이 아니고, 개인적으로 선언해서 사용하고 있는 파일 형식이다. global git ignore 파일에 .gitcherryignore 파일을 등록하여 개인 로컬 환경에서만 관리하고 있다.
기타
설정 파일
개인적으로 Fork 에 아쉽다고 느끼는 점은, Fork 소프트웨어의 설정을 파일 형태로 Import/Export 할 수 없다는 사실이다. 다른 PC 로 개발 환경을 옮겨야 하는 경우, Fork 의 설정을 하나씩 옮겨야 할 수 있다. 하지만 기존 PC 에 있던 설정 파일을 직접 복사하여 설정을 옮기는 것은 가능하다. (물론 Fork 클라이언트의 버전이 동일해야 안전하게 옮길 수 있다.) 맥에서 다음의 경로를 찾아 들어가면, Fork 의 설정 파일들을 조회해 볼 수 있다.
# 설정 파일 디렉토리
~/Library/Application Support/com.DanPristupov.Fork
# Custom Commands 파일
~/Library/Application Support/com.DanPristupov.Fork/custom-commands.json
위의 Git Cherry Custom Command 를 기준으로, 아래와 같은 설정 파일을 확인할 수 있다.
[
  {
    "version" : 1
  },
  {
    "name" : "Git Cherry",
    "target" : "repository",
    "ui" : {
      "buttons" : [
        {
          "action" : {
            "script" : "#!\/bin\/bash
ROOT_BRANCH=$1{ref}
COMPARE_BRANCH=$2{ref}
IGNORE_FILE_NAME=".gitcherryignore"
IGNORE_FILE="${repo:path}\/$IGNORE_FILE_NAME"
# 무시할 커밋 파일 존재 여부 확인
if [ ! -f "$IGNORE_FILE" ]; then
  echo "Git Cherry에 무시할 커밋을 추가하시려면 $IGNORE_FILE를 생성해 주세요\
\
"
fi
# 무시할 커밋 해시 목록을 배열에 저장
ignore_hashes=()
if [ -f "$IGNORE_FILE" ]; then
  while IFS= read -r ignore_line; do
    # 주석 라인 무시
    [[ "$ignore_line" =~ ^[#\/\/--] ]] && continue
    ignore_hashes+=($(echo "$ignore_line" | awk '{print $1}'))
  done < "$IGNORE_FILE"
fi
# cherry-pick 결과에서 필요한 정보 추출
cherry_result=$(git cherry $ROOT_BRANCH $COMPARE_BRANCH | grep "^+ " | cut -d " " -f 2 | xargs -I {} git log --pretty=format:"%H %an %s %n" -n 1 {})
message_result=""
commit_result=""
# 각 라인 처리
while IFS= read -r line; do
  commit_hash=$(echo "$line" | awk '{print $1}')
  # 무시할 커밋인지 확인
  print_commit=true
  for ignore_hash in "${ignore_hashes[@]}"; do
    if [[ "$commit_hash" == "$ignore_hash"* ]]; then
      print_commit=false
      break
    fi
  done
  # 조건에 따라 커밋을 출력
  if $print_commit; then
    message_result+="$line\
"
    commit_result+="$commit_hash "
  fi
done <<< "$cherry_result"
echo "$message_result"
echo "$commit_result" | pbcopy
",
            "showOutput" : true,
            "type" : "sh",
            "waitForExit" : true
          },
          "title" : "OK"
        },
        {
          "action" : {
            "type" : "cancel"
          },
          "title" : "Cancel"
        }
      ],
      "controls" : [
        {
          "dropdownType" : "references",
          "filter" : "refs\/remotes\/origin\/main refs\/heads\/main ref\/remotes\/origin\/release\/ refs\/heads\/release\/
refs\/remotes\/origin\/main refs\/heads\/main ref\/remotes\/origin\/release\/ refs\/heads\/release\/
refs\/remotes\/origin\/main refs\/heads\/main ref\/remotes\/origin\/release\/ refs\/heads\/release\/
refs\/remotes\/origin\/main refs\/heads\/main ref\/remotes\/origin\/release\/ refs\/heads\/release\/
refs\/remotes\/origin\/main refs\/heads\/main ref\/remotes\/origin\/release\/ refs\/heads\/release\/",
          "title" : "Root Branch",
          "type" : "dropdown"
        },
        {
          "dropdownType" : "references",
          "filter" : "refs\/remotes\/origin\/develop refs\/heads\/develop ref\/remotes\/origin\/release\/ refs\/heads\/release\/",
          "title" : "Compare Branch",
          "type" : "dropdown"
        }
      ],
      "description" : "",
      "title" : "Show UnCherryPicked Commits"
    }
  }
]
Custom Commands 찾아보기
[Discussion] Share tips, tricks and custom commands with Fork community #961 이슈를 살펴보면, Fork 유저들이 공유한 자신들의 Custom Commands 를 읽어볼 수 있다. 이 쓰레드를 읽어보면 새로운 아이디어를 얻을 수도 있다.
환경 변수 및 PATH
  2024-10-22 기준으로, Git Fork 에서 환경변수와 PATH 가 인식되지 않는 이슈가 있다. 이로 인해 husky 등을 활용한 git hook 이 제대로 동작하지 않는 이슈를 겪었다. 구체적으로는, 커밋 시 Kotlin spotless 라이브러리를 통해 코드 형식을 체크하는데, java 명령어를 찾을 수 없다는 에러가 Fork 에서 발생했다. 
  맥 터미널에서 open -a Fork 로 Fork 클라이언트를 실행하면 환경변수와 PATH 가 잘 인식된다. 아래와 같이 alias 로 등록하여 사용하면 편하다.
alias fork="open -a Fork"
External Diff, Merge Tool
  MacOS에서 기본적으로 Fork를 설치하면, 디폴트 Merge, Diff Tool로 FileMerge 어플리케이션이 선택되어 있다. 이를 Intellij IDEA로 변경하는 방법은 다음과 같다.

- File Path : 
/Applications/IntelliJ IDEA.app/Contents/MacOS/idea - Arguments : 
merge $BASE $LOCAL $REMOTE $MERGED 

- File Path : 
/Applications/IntelliJ IDEA.app/Contents/MacOS/idea - Arguments : 
diff $REMOTE $LOCAL 
이것도 읽어보세요