여러 기능을 동시에 개발하려고 할 때 가장 먼저 떠올리는 방법은 branch를 여러 개 만들고 하나의 repository 디렉터리에서 계속 checkout하는 방식입니다. 짧게는 쓸 만하지만 새 기능을 개발하면서 서비스를 띄우고, 동시에 운영 bug까지 고치기 시작하면 금방 상태가 섞입니다.

저는 결국 다른 방식으로 거의 고정했습니다. main 작업 디렉터리는 안정 branch만 유지하고, 실제 개발은 git worktree로 기능마다 별도 디렉터리를 만든다는 방식입니다. 같은 머신에서 여러 feature branch를 동시에 다룰 수 있고 디렉터리, 실행 프로세스, build 산출물이 서로 덜 오염됩니다.

Git 병렬 branch 개발, Git worktree 사용법, Worktree 표준 workflow를 찾고 있다면 이 글이 바로 그 흐름을 정리한 것입니다.

branch를 계속 바꾸는 대신 Worktree를 선택한 이유

진짜 어려운 것은 새 branch를 만드는 방법이 아닙니다. 지금 테스트하는 코드가 정말 그 branch인지, 실행 중인 서비스가 그 디렉터리를 사용하는지, commit할 때 다른 기능의 잔여물이 같이 들어가지 않는지를 보장하는 것이 더 어렵습니다.

하나의 repository 디렉터리에서 branch를 계속 바꾸면 다음 문제가 자주 생깁니다.

  • 새 기능을 테스트한다고 생각했는데 서비스는 이전 디렉터리에서 실행 중
  • 한 기능의 build 결과가 다른 branch를 오염
  • git add .로 불필요한 파일까지 한꺼번에 stage
  • 운영 bug 수정과 새 기능 개발을 동시에 하다가 작업 디렉터리가 계속 더러워짐

git worktree의 장점은 branch를 더 빨리 만드는 데 있지 않습니다. 기능마다 독립적인 작업 디렉터리를 갖게 한다는 것이 핵심입니다. 저는 이 격리감이 가장 중요했습니다.

제가 고정해서 쓰는 디렉터리 규칙

나중에 더 복잡해지지 않도록 몇 가지 규칙을 먼저 정합니다.

  • main repository: /Users/kuangyongjie/charting_library
  • worktree root: /Users/kuangyongjie/worktrees
  • branch 이름: codex/<feature-name>
  • main branch: master

main 디렉터리는 안정 상태 유지와 merge에만 사용합니다. 실제 기능 개발은 별도 worktree에 둡니다.

/Users/kuangyongjie/worktrees/hotspot-ff

작은 규칙처럼 보이지만 경로만 봐도 지금 main repository에 있는지 feature 디렉터리에 있는지 바로 알 수 있어서 꽤 유용합니다.

표준 흐름은 몇 단계뿐이지만 생략하지 않는 편이 좋다

먼저 worktree root를 만듭니다.

mkdir -p /Users/kuangyongjie/worktrees

main repository에서 별도 worktree와 새 branch를 만듭니다.

REPO=/Users/kuangyongjie/charting_library
WT=/Users/kuangyongjie/worktrees/<feature-name>
BRANCH=codex/<feature-name>
BASE=master

# 如果远端可用,先更新 master
# git -C "$REPO" pull --rebase central "$BASE"

git -C "$REPO" worktree add "$WT" -b "$BRANCH" "$BASE"
cd "$WT"
git branch --show-current

정상이라면 마지막에 다음 branch가 보여야 합니다.

codex/<feature-name>

이 순간부터 해당 기능의 코드 수정, build, test는 모두 "$WT" 안에서만 한다고 정하면 됩니다.

개발과 테스트는 feature worktree 안에서만 한다

프런트엔드를 수정하든 backend를 실행하든 로컬 검증을 하든 해당 worktree 디렉터리 안에서만 진행합니다.

예를 들어 frontend가 하위 디렉터리에 있다면 그 branch의 경로에서 build합니다.

cd "$WT/trendradar-service/trendradar-web"
npm run build
# 生产混淆构建
# npm run build:multi-layer

저도 자주 겪었던 문제 중 하나가 “코드는 바꿨는데 화면은 계속 예전 상태”였습니다. 나중에 확인해 보면 코드 문제가 아니라 서비스 자체가 다른 디렉터리에서 실행 중인 경우가 많았습니다.

그래서 “분명히 수정했는데 왜 안 바뀌지?”라는 상황이면 framework부터 의심하기보다 **현재 process의 cwd**를 먼저 봅니다.

lsof -nP -iTCP:<port> | sed -n '1,5p'
ps -p <pid> -o pid,ppid,command
lsof -p <pid> | rg "cwd"

최종적으로 cwd가 다음 경로를 가리키는지 확인합니다.

/Users/kuangyongjie/worktrees/<feature-name>/...

아니라면 지금 테스트하는 것은 애초에 그 branch가 아닐 가능성이 큽니다.

commit할 때는 거의 git add .를 쓰지 않는다

병렬 worktree 개발에서는 일부러 보수적으로 stage합니다.

로컬 실행 산출물, cache, snapshot 등이 있을 수 있기 때문에 git add . 한 번으로 불필요한 파일이 같이 들어오기 쉽습니다.

예를 들어 다음 파일은 흔합니다.

  • __pycache__
  • *.db
  • snapshot_*
  • .DS_Store

저는 목표 파일만 명시적으로 추가합니다.

cd "$WT"
git add <file1> <file2> <file3>
git status --short
git commit -m "feat/fix: <summary>"

commit 전에 git status --short를 한 번 보는 작은 습관만으로도 저수준 실수를 많이 막을 수 있습니다.

main branch로 merge하는 작업은 main 디렉터리에서만 한다

기능이 끝나면 merge는 main repository 디렉터리에서만 실행합니다.

cd /Users/kuangyongjie/charting_library
git branch --show-current   # 必须是 master

# 如远端可用,先更新
# git pull --rebase central master

git merge --no-ff codex/<feature-name>

중요한 것은 merge --no-ff 자체보다 정말 main 디렉터리와 master branch에 서 있는지 확인하는 것입니다.

다음 오류가 나온다면,

MERGE_HEAD exists

이전 merge가 끝나지 않았다는 뜻일 가능성이 큽니다.

git merge --abort

그 뒤 pull과 merge를 다시 진행합니다.

remote에 push해야 한다면 다음 단계를 추가합니다.

cd /Users/kuangyongjie/charting_library
git push <remote> master

환경에 따라 remote 이름은 origin이 아니라 central일 수 있습니다. 이건 Worktree 문제가 아니라 각 repository 설정 차이입니다.

merge 기록을 남기지 않아도 된다면 Fast-forward를 쓸 수 있다

“feature branch를 별도로 실행해서 테스트하고, 통과하면 merge commit 없이 main에 반영”하는 흐름이라면 fast-forward 방식도 가능합니다.

cd /Users/kuangyongjie/charting_library
git checkout master
git pull --rebase central master

git worktree add /Users/kuangyongjie/worktrees/hotspot-ff -b codex/hotspot-ff master

서비스도 반드시 feature worktree에서 실행합니다.

cd /Users/kuangyongjie/worktrees/hotspot-ff/trendradar-service
./venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 9544

다른 terminal에서 process가 정말 그 branch 디렉터리를 사용하고 있는지 확인합니다.

lsof -nP -iTCP:9544 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,command
lsof -a -p <PID> -d cwd -Fn

이 확인은 꽤 중요합니다. “branch 테스트가 통과했다”는 결론이 사실은 다른 작업 디렉터리를 테스트한 결과인 경우가 생각보다 자주 있습니다.

장기간 쓰면서 고정한 규칙

Worktree를 계속 사용할 생각이라면 최소한 다음 규칙은 강하게 유지하는 편이 좋습니다.

  • 기능 하나당 worktree 디렉터리 하나
  • 하나의 대화나 task는 하나의 worktree에만 대응
  • 작업 시작 전에 git branch --show-current
  • commit 전에 git status --short
  • merge는 main 디렉터리의 master에서만 수행

평범한 규칙처럼 보이지만 병렬 개발이 무너지지 않게 하는 것은 결국 이런 기본 동작입니다.

자주 발생하는 문제

로컬 화면이 계속 이전 버전으로 보일 때

코드부터 의심하지 말고 서비스 process의 cwd가 목표 worktree인지 확인합니다. 그다음 해당 branch에서 frontend를 다시 build했는지도 봅니다.

fatal: 'origin' does not appear to be a git repository

Git 자체가 망가진 경우보다 remote 이름이 다른 경우가 많습니다.

git remote -v

실제 remote 이름으로 push하면 됩니다. 이 환경에서는 central을 사용하는 경우가 있습니다.

MERGE_HEAD exists

이전 merge가 끝나지 않았다는 뜻입니다.

git merge --abort

그 뒤 다시 merge 흐름을 진행합니다.

frontend만 바꿨는데 backend를 재시작해야 할까

보통은 아닙니다. 정적 자원을 다시 build하고 배포했다면 cache를 새로고침하는 것만으로 충분한 경우가 많습니다.