← Notes

git은 부모 브랜치를 기억하지 않는다 — dev 하드코딩을 벗기며 배운 것

브랜치는 커밋 포인터일 뿐이다

브랜치 이름이 가리키는 실체는 커밋 하나뿐이다.

브랜치는 특정 커밋을 가리키는 이동 가능한 포인터다. 새 커밋을 쌓으면 포인터가 그 커밋으로 옮겨갈 뿐, 브랜치 자체가 계보나 분기 이력을 담는 그릇은 아니다.

문패를 옮겨 다는 것과 같다. dev라는 문패를 커밋 A에서 커밋 B로 옮기면 그 순간부터 dev는 B를 가리킨다. 그 문패가 예전에 어느 문에 달려 있었는지, 애초에 어떤 문에서 떼어와 새로 만든 문패인지는 문패 자체가 기억하지 않는다.

이 사실은 AI 코딩 에이전트용 태스크 가드레일 도구 taskery의 브랜치 분기 로직에서 dev 하드코딩을 걷어내다가 정면으로 부딪힌 것이다.

dev 하드코딩이 만든 문제

taskery는 태스크마다 브랜치를 새로 분기하고 완료 시 되병합하는데, 그 병합 대상이 dev로 고정돼 있었다.

  • 개인 개발: 모든 작업이 dev로 모이는 통합 흐름이라 문제가 없다.
  • 회사 흐름: master → 팀/프로젝트 중간 브랜치 → 개인 브랜치 구조이고, 공유 브랜치로의 병합은 PR을 거친다. dev 고정과 정면충돌한다.

처음엔 “회사 환경이면 기준 브랜치를 옵션으로 받자”는 설정값 접근을 검토했다. 그런데 “프로젝트 브랜치가 곧 개인의 dev”라는 통찰로 PR 문제 자체가 사라졌고, 한 걸음 더 나가 “설정값조차 필요 없다. 태스크를 시작한 시점의 현재 브랜치가 곧 부모”로 재축소됐다.

git은 부모를 저장하지 않는다

그런데 태스크를 마칠(close) 시점에 “부모가 누구였는지”는 무엇으로 아는가.

merge-base나 reflog로 되짚어 추정하는 방법을 검토했지만, 조상 커밋을 여러 브랜치가 공유하는 상황에서는 추정이 모호해져 신뢰할 수 없었다. 분기 시점의 부모-자식 관계는 git 저장소 어디에도 남지 않기 때문에, 나중에 계산으로 복원하려는 접근 자체가 근본적으로 불안정하다.

해법은 방향을 뒤집는 것이었다. 브랜치를 새로 딸(fork) 때, 부모 브랜치 이름을 태스크 문서 헤더에 도구가 스스로 적어 둔다. 이후 close 시점엔 계산이 아니라 그냥 읽으면 된다.

걷어낸 하드코딩의 진짜 부피

dev 하드코딩을 걷어내는 작업의 크기는 예상과 다른 곳에 있었다.

  • 실제 dev 매치는 253건/27개 파일이었는데, 그중 코드는 소수였고 대부분은 문서였다.
  • dev는 다의어였다: 통합 브랜치 이름·개발자 식별자 인자·스킬 이름·developing 상태값·npm run dev·/dev/null. 전량 치환이 아니라 성격별로 바꿀 것·문맥만 고칠 것·그대로 둘 것을 가르는 판단(triage)이 작업의 본체였다.
  • 구현 도중, 상태값을 항상 “마지막 칸”으로 다루던 기존 로직이 새 칸(부모 브랜치)을 상태 앞에 끼워 넣으면서, 칸 수로 신구 데이터를 구분하던 보호 로직이 조용히 뚫릴 뻔한 문제도 함께 드러났다.

설계 통찰은 하나 더 있다. 도구의 병합 범위를 “자기가 연 브랜치 안”으로 한정하니, 공유 브랜치로 올라가는 승격 경로(PR 지원 같은 옵션)가 설계에서 통째로 필요 없어졌다. 옵션을 늘리는 대신 도구가 책임지는 경계를 좁힌 결과다.

즉, 분기 도구는 fork 순간에 부모를 스스로 기록해야 한다. git이 알아서 기억해주지 않기 때문이고, 하드코딩을 걷어내는 진짜 비용은 코드보다 다의어로 오염된 문서 쪽에 있다.

참고 자료

  • [1] Git 공식 문서 — Branches in a Nutshell
목차