Alex Georgiev 시험서 Git 2.56 중간 커밋 삭제 명령, bare 저장소 rebase 실행보다 최대 77.5배 빨라
DEV Community 필자 Alex Georgiev는 Git 2.56.0의 실험 명령 `git history drop`으로 중간 커밋을 제거하고 뒤의 커밋을 다시 연결하는 작업을 시험했습니다. 파일을 체크아웃하지 않는 bare 저장소에서 후속 커밋 20·100·400개를 각각 세 차례 처리한 결과, 기존 방식의 작업 트리 생성·제거를 제외한 `rebase` 실행 시간보다 40.7~77.5배 짧았다고 보고했습니다. 루트·병합 커밋은 삭제할 수 없고, 재적용 중 충돌이 생기면 명령이 중단됩니다.

Git v2.56.0에 들어간 실험 명령 git history drop은 이력 중간의 커밋 하나를 제거하고, 그 뒤의 커밋들을 삭제 대상의 부모에 다시 적용합니다. DEV Community 필자 Alex Georgiev는 Git v2.56.0 태그의 소스 코드를 빌드해 bare 저장소에서 이 명령을 시험했습니다. 후속 커밋이 20개, 100개, 400개인 조건에서 각각 세 차례 측정한 결과, 새 명령의 실행 시간은 기존 rebase 실행 구간보다 40.7~77.5배 짧았다고 보고했습니다. Git 공식 문서는 이 명령을 실험 기능으로 표시하며 동작이 바뀔 수 있다고 안내합니다.
작업 트리는 현재 버전의 파일을 실제 디렉터리에 펼쳐 놓은 공간이고, 인덱스는 다음 커밋에 담을 파일 상태를 준비하는 공간입니다. bare 저장소에는 체크아웃한 파일을 두는 작업 트리가 없습니다. Georgiev가 비교한 기존 절차는 bare 저장소에 임시 작업 트리를 만든 뒤 그 안에서 git rebase --onto <대상>^ <대상> main을 실행하고, 작업 트리를 제거하는 방식이었습니다. 새 명령은 drop 작업에서 작업 트리를 만들지 않고 bare 저장소에 직접 실행했습니다.
후속 커밋이 20개일 때 기존 rebase 실행 시간은 평균 1.418초, git history drop은 0.035초였습니다. 100개 조건에서는 각각 7.121초와 0.092초, 400개 조건에서는 26.525초와 0.362초였습니다. 이 수치는 각 조건에서 세 차례 실행한 평균입니다. 기존 방식은 작업 트리를 만든 뒤 rebase가 시작될 때부터 끝날 때까지 쟀으며, 작업 트리 생성과 제거 시간은 포함하지 않았습니다. 새 명령은 bare 저장소에서 실행한 시간을 쟀습니다.
Georgiev는 차이가 작업 트리를 준비하는 데서가 아니라 후속 커밋을 하나씩 다시 적용하는 과정에서 생긴다고 분석했습니다. rebase는 각 커밋을 차례로 적용하는 동안 인덱스와 작업 트리를 갱신합니다. 반면 그는 git history drop은 이 작업을 하지 않고 Git의 커밋 객체를 처리한다고 설명했습니다. 400개 조건에서 작업 트리를 추가하는 데 걸린 별도 측정 시간은 17밀리초, 제거에는 9밀리초였고, rebase만 따로 실행했을 때는 25.199초가 걸렸습니다.
작은 저장소에서 두 방식으로 재작성한 뒤 main이 가리키는 파일 트리 해시를 비교했을 때 결과는 같았습니다. 새로 만들어진 개별 커밋의 해시는 타임스탬프 등이 달라 기존 커밋과 달랐습니다.
브랜치 참조를 옮기는 범위도 다릅니다. Git 공식 문서는 기본 설정에서 원래 커밋의 후손인 모든 로컬 브랜치 참조를 새 이력으로 갱신한다고 설명합니다. Georgiev는 --update-refs=branches를 지정한 시험에서 같은 대상 커밋의 후손인 main과 feature가 함께 이동하는 것을 확인했습니다. 그가 비교한 일반적인 rebase는 지정한 브랜치의 이력을 다시 씁니다. 또 공식 문서는 현재 git history가 훅을 실행하지 않는다고 안내합니다. Georgiev의 시험에서도 post-rewrite 훅은 history drop에서는 실행되지 않았고 rebase에서는 실행됐습니다. 공식 문서는 이 동작이 향후 바뀔 수 있다고 덧붙였습니다.
이 명령은 루트 커밋이나 병합 커밋을 삭제할 수 없습니다. 후속 커밋을 다시 적용할 때 충돌이 생기는 경우에도 오류를 내고 중단합니다. Georgiev가 만든 충돌 사례에서는 실행 전 브랜치 이력이 그대로 남았고 rebase 상태 디렉터리도 생기지 않았습니다. --dry-run을 지정하면 브랜치 참조를 바꾸는 대신 git update-ref에 전달할 변경 내용을 출력합니다. 다만 참조를 변경하지 않아도 필요한 새 커밋 객체는 저장소에 기록될 수 있습니다.
Georgiev는 처음 측정할 때 새 명령에 지원되지 않는 --quiet 옵션을 넣었습니다. 오류 출력을 로그로 돌린 탓에 명령이 실패했는데도 실행 시간이 짧은 성공 결과처럼 기록됐습니다. 실행 전후 커밋 해시가 같다는 점을 확인해 오류를 찾아낸 뒤, 해당 측정값을 제외하고 다시 측정했습니다. 본문에 제시한 수치는 수정한 측정 결과입니다.