가장 눈에 띄는 사용성 변화는 git add --resolved다
브랜치를 합치다가 충돌이 나면 파일을 고치는 것만으로는 끝나지 않는다. 어떤 파일을 해결했는지 Git에도 알려야 한다. 이때 git add -u나 git add -A를 쓰면 충돌과 관계없는 수정까지 다음 커밋에 포함할 수 있다. Git 2.56의 git add --resolved는 충돌을 해결하는 작업에 맞춰 대상을 좁힌 명령이다.
명령은 Git이 아직 충돌 상태로 기록한 파일만 확인한다. 선택한 파일 중 하나라도 텍스트 충돌 표시가 남아 있으면, 그 파일을 알려 주고 선택 범위 전체를 다음 커밋의 준비 영역에 올리지 않는다. 일부만 처리한 뒤 넘어가는 방식이 아니다. 충돌과 무관한 파일은 처음부터 대상에서 제외되므로 합치기 전부터 작업하던 수정이 섞일 위험을 줄인다.
바이너리 충돌이나 삭제 해결처럼 텍스트 marker가 없는 경우는 정상적으로 stage할 수 있고 pathspec으로 검사 범위를 좁힐 수도 있다. 이 기능은 conflict resolution이라는 좁은 단계에 맞춰져 있어서 git add -u 또는 -A와 함께 사용할 수 없다. 팀 문서에서 '충돌 해결 뒤 add -u'를 관행처럼 쓰고 있다면 2.56 이후에는 이 명령으로 바꿀 이유가 생겼다.
웹에서 비슷한 작업을 해 봤다면 아래 화면이 익숙할 수 있다. GitHub에서는 Resolve conflicts로 편집기를 열고, 수정한 파일을 Mark as resolved로 표시한다. 아래 그림은 이 기존 웹 작업의 비교 자료다. 이번에 추가된 git add --resolved는 별도의 명령줄 기능이며, 그림 속 버튼이 새로 생겼다는 뜻은 아니다.
merge-base 탐색은 더 일찍 멈추고, 큰 히스토리에서 효과가 커질 수 있다
merge, three-dot diff, PR 비교 같은 작업은 두 commit의 가장 좋은 공통 조상을 찾는 merge-base 계산에 의존한다. 복잡하게 갈라졌다 다시 합쳐진 히스토리에서는 여러 merge base 후보가 있을 수 있어 Git이 오래된 공통 히스토리를 상당히 멀리까지 걷는 경우가 있었다. 2.56은 양쪽에서만 도달 가능한 commit이 queue에 얼마나 남았는지 추적해 더 이상 새 meeting point가 나올 수 없는 시점에 탐색을 끝내도록 바꿨다.
GitHub가 공개한 사례에서는 한 실제 monorepo 쿼리가 0.68초에서 0.01초로 줄었고, 두 대형 monorepo의 production evaluation에서 한쪽은 많은 사례가 약 70배, 다른 쪽은 평균 약 20배 개선됐다고 설명한다. Linux kernel 사례의 git merge-base --all v4.8 v4.9도 167,441 traversal step과 0.29초에서 3,887 step과 0.01초로 줄었다고 보고됐다.
이 숫자는 특정 히스토리와 commit-graph 조건에서 측정한 결과다. 작은 저장소에서 모든 명령이 갑자기 수십 배 빨라진다는 뜻은 아니다. 하지만 PR 계산이나 merge 가능성 확인을 매우 자주 수행하는 hosting 환경과 대형 monorepo에서는 작은 탐색 개선이 반복 비용을 크게 줄일 수 있다.
path-walk repack과 내부 병목 제거가 서버 운영에도 닿는다
Git 2.56은 path-walk repacking이 reachability bitmap과 delta islands를 함께 사용할 수 있도록 확장했다. GitHub가 Fluent UI 저장소로 소개한 벤치마크에서는 일반 bitmapped repack이 558.5MB pack을 만든 반면 --path-walk를 사용한 repack은 164.4MB로 약 71% 작았다. 이 역시 특정 입력에서 delta를 다시 계산한 결과라 모든 repository의 절감률을 예측하는 수치로 쓰면 안 된다.
더 낮은 층에서도 scaling cliff를 없애는 수정이 들어갔다. reftable 쓰기에서 lock을 얻은 뒤 중복 reload를 줄여 filesystem stat 호출을 선형에서 상수 수준으로 낮추고, 이미 새 packfile이라는 사실을 아는 경우 기존 목록을 매번 훑지 않도록 해 많은 pack을 가진 저장소에서 O(N²) 회귀를 피했다.
GitHub가 정리한 테스트에서는 tombstone-heavy reftable 성능 테스트가 약 13초에서 0.2초로 줄었고, 약 50만 index entry가 있는 Chromium checkout의 한 path-limited git diff 사례는 약 8분에서 0.07초로 줄었다. 이런 수치는 드물지만 극단적으로 느린 경로가 실제로 존재했다는 점을 보여 준다. 대형 저장소 운영자는 평균 명령 시간만이 아니라 tail latency가 줄어드는지 확인할 가치가 있다.
브랜치 정리와 bisect 종료도 한 명령에 가까워졌다
로컬 topic branch를 여러 개 쓰는 개발자는 upstream에 이미 병합된 브랜치를 정리할 때 하나씩 비교하거나 스크립트를 만들곤 한다. 2.56은 git branch --delete-merged를 추가해 지정한 upstream 패턴과 후보 branch 패턴을 기준으로 병합이 끝난 로컬 브랜치를 한 번에 찾고 삭제할 수 있게 했다. --dry-run으로 먼저 대상만 확인할 수 있다.
Git은 active worktree에서 checkout된 branch, upstream이 없는 branch, 일부 모호한 push 설정을 건너뛴다. branch.<name>.deleteMerged = false로 특정 branch를 보호하는 방법도 제공된다. 무조건 패턴에 맞는 branch를 지우는 단순 glob 삭제와 달리 reachability를 확인한다는 점이 핵심이다.
git bisect run에는 --reset-when-found[=<where>] 옵션이 생겼다. 자동화된 이분 탐색이 culprit를 찾은 뒤 기존 상태로 돌아가기 위해 별도의 git bisect reset을 호출하던 흐름을 한 단계 줄일 수 있다. 기본값 original은 bisect 시작 전 commit으로 돌아가고, found를 선택하면 bisect 상태는 정리하되 찾은 commit에 머문다.
partial clone은 큰 blob을 다시 원격에 맡길 수 있게 됐다
partial clone은 필요한 object만 처음 내려받고 나머지는 필요할 때 promisor remote에서 가져오는 방식이다. 하지만 한 번 가져온 큰 blob은 이후 계속 로컬에 남아 저장 공간을 차지할 수 있었다. Git 2.56은 git repack에 --drop-filtered를 추가해 특정 조건의 recoverable blob을 로컬에서 제거하고 다음 접근 때 원격에서 다시 받게 할 수 있다.
현재 공개된 범위는 blob:limit 필터다. promisor remote가 필요하며 index가 참조하는 object를 지우는 등 안전하지 않은 경우는 Git이 거부한다. GitHub 기사도 이 기능이 자동으로 local cache 크기를 계속 제한하는 기능이 아니라 사람이 실행하는 cleanup mechanism이라고 명확히 설명한다.
대용량 asset이 섞인 repository를 partial clone으로 쓰는 팀이라면 로컬 디스크를 회수할 수 있는 수단이 하나 늘어난 셈이다. 반대로 네트워크가 느리거나 다시 내려받을 가능성이 큰 blob을 무작정 떨어뜨리면 이후 작업 지연이 늘 수 있다. dry-run으로 후보를 확인하고 개발 패턴에 맞는 limit을 정하는 편이 안전하다.
refs와 log --follow까지 바뀌었지만 업그레이드는 단계적으로 확인한다
low-level ref 작업은 git update-ref, git symbolic-ref 등 여러 plumbing 명령에 나뉘어 있었다. 2.56은 git refs create, update, delete, rename 하위 명령을 추가해 reference 관리 인터페이스를 한 toolbox 쪽으로 모으고 있다. update와 delete에는 old value를 선택적으로 줘 compare-and-swap 성격의 보호를 걸 수 있다. 다만 git refs rename은 ref와 reflog를 옮길 뿐 git branch -m처럼 branch 설정까지 조정하지 않는 low-level 명령이다.
git log --follow는 비선형 히스토리에서 여러 부모가 같은 파일을 서로 다르게 rename한 경우 각 parent별 경로를 따로 추적하도록 수정됐다. 이전처럼 하나의 global current path에 의존해 traversal 순서에 따라 결과가 흔들리는 문제를 줄인 변화다. 오래된 monorepo나 subtree merge가 많은 저장소에서 history 조사 품질에 직접 닿을 수 있다.
Git의 BreakingChanges 문서에는 2.56에서 새로 추가된 일반 사용자용 breaking change가 따로 적혀 있지 않다. 그래도 운영 환경을 한 번에 전부 올리기보다 CI 이미지, 개발자 PC, server-side tooling 순으로 실제 사용하는 명령을 확인하는 편이 좋다. 특히 새 옵션을 팀 스크립트에 넣을 때는 최소 지원 Git 버전을 함께 올려야 구버전 환경에서 unknown option 오류가 나지 않는다.
