가장 눈에 띄는 사용성 변화는 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는 별도의 명령줄 기능이며, 그림 속 버튼이 새로 생겼다는 뜻은 아니다.

GitHub 웹 UI의 Mark as resolved 버튼 (GitHub 공식 문서 화면)
GitHub 웹 편집기에서 충돌을 정리한 뒤 누르는 Mark as resolved 버튼. 기존 GUI 흐름을 보여 주는 공식 문서 화면이며, Git 2.56 CLI의 git add --resolved 실행 화면이 아니다.GitHub Docs · CC BY 4.0 · 원문이미지 이용 조건
충돌을 모두 해결한 뒤 GitHub 웹 UI에서 merge commit을 만드는 화면 (GitHub 공식 문서)
모든 충돌을 해결한 뒤 GitHub 웹 UI에서 merge commit을 만드는 단계. 웹 UI의 자동 커밋과 달리 명령줄에서는 git add --resolved로 stage한 뒤 직접 commit한다.GitHub Docs · CC BY 4.0 · 원문이미지 이용 조건

근거 [1] [2] [4]

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에서는 작은 탐색 개선이 반복 비용을 크게 줄일 수 있다.

근거 [1] [2]

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가 줄어드는지 확인할 가치가 있다.

근거 [1] [2]

브랜치 정리와 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에 머문다.

근거 [1] [2]

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을 정하는 편이 안전하다.

근거 [1] [2]

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 오류가 나지 않는다.

근거 [1] [2] [3]