고급 Git 히스토리 편집: 커밋 수정 및 대화형 리베이스

git commit --amend와 대화형 리베이스를 사용하여 공유 브랜치를 방해하지 않고 로컬 히스토리를 정리하세요.

고급 Git 히스토리 편집: 커밋 수정 및 대화형 리베이스

고급 Git 히스토리 편집은 커밋이 공유 히스토리의 일부가 되기 전에 정리하는 데 도움을 줍니다. 커밋 수정과 대화형 리베이스를 이해하면 작은 실수를 수정하고, 지저분한 커밋을 결합하며, 코드 리뷰를 위해 더 명확한 스토리를 제시할 수 있습니다.

핵심 규칙은 간단합니다. 로컬 히스토리 편집은 정상이지만, 공유 히스토리 재작성은 주의가 필요합니다. 명령어는 커밋 메시지뿐만 아니라 커밋 ID도 변경하기 때문에 강력합니다.

Git 히스토리 편집이 실제로 변경하는 것

모든 Git 커밋은 내용, 메타데이터, 부모 커밋, 작성자, 날짜 및 메시지를 기반으로 ID를 가집니다. 커밋을 수정하거나 리베이스 중에 다시 작성하면 Git은 새 ID를 가진 새 커밋을 생성합니다.

커밋이 내 컴퓨터에만 있을 때는 괜찮습니다. 다른 사람들이 이미 이전 커밋을 가져온 경우 위험합니다. 그들의 브랜치는 여전히 이전 히스토리를 가리키는 반면, 내 브랜치는 다시 작성된 히스토리를 가리킬 수 있습니다.

히스토리 편집은 다음에 사용하세요:

  • 최신 커밋 메시지의 오타 수정.
  • 마지막 로컬 커밋에 잊어버린 파일 추가.
  • 풀 리퀘스트를 열기 전에 여러 작업 중 커밋을 스쿼시.
  • 관련 변경 사항을 더 쉽게 리뷰할 수 있도록 로컬 커밋 재정렬.
  • 하나의 큰 로컬 커밋을 더 작은 논리적 커밋으로 분할.

다음에서는 히스토리 편집을 피하세요:

  • 메인 브랜치.
  • 릴리스 브랜치.
  • 여러 사람이 사용하는 공유 기능 브랜치.
  • 배포 태그 또는 감사에 민감한 커밋이 있는 모든 브랜치.

시작하기 전에 현재 브랜치와 이미 푸시된 내용을 확인하세요:

git status
git branch --show-current
git log --oneline --decorate -5

재작성이 안전한지 확실하지 않으면 백업 브랜치를 만드세요:

git branch backup-before-rebase

이 브랜치는 리베이스가 잘못될 경우 알려진 복구 지점을 제공합니다.

최신 커밋 수정

git commit --amend는 가장 최근 커밋을 새 커밋으로 대체합니다. 다른 사람이 의존하기 전에 마지막 커밋을 수정하는 가장 빠른 방법입니다.

커밋 메시지만 편집하려면:

git commit --amend

Git이 현재 메시지로 편집기를 엽니다. 수정된 메시지를 저장하면 Git이 대체 커밋을 생성합니다.

마지막 커밋에 잊어버린 파일을 추가하려면:

git add path/to/file
git commit --amend --no-edit

--no-edit 플래그는 기존 메시지를 유지합니다. 커밋 메시지가 이미 올바르고 하나의 변경 사항만 스테이징하는 것을 잊었을 때 유용합니다.

일반적인 시나리오입니다. Dockerfile 업데이트를 커밋한 후 관련 .dockerignore 변경을 잊어버린 것을 발견했습니다:

git add Dockerfile
git commit -m "Improve Docker build caching"
git add .dockerignore
git commit --amend --no-edit

이제 브랜치에는 "파일을 잊어버렸습니다"라는 두 번째 커밋 대신 하나의 깔끔한 커밋이 있습니다. 이렇게 하면 리뷰가 더 쉬워집니다.

원래 커밋이 이미 푸시된 경우 원격 브랜치에는 여전히 이전 커밋 ID가 있습니다. 수정된 커밋을 푸시하려면 강제 업데이트가 필요합니다:

git push --force-with-lease

--force보다 --force-with-lease를 선호하세요. 마지막 가져오기 이후 다른 사람이 새 작업을 푸시한 경우 원격 브랜치를 덮어쓰지 않습니다.

대화형 리베이스로 여러 커밋 정리

대화형 리베이스를 사용하면 한 번에 여러 커밋을 편집할 수 있습니다. 범위를 선택하면 Git이 할 일 목록을 열어 커밋을 선택, 수정, 스쿼시, 픽스업, 재정렬 또는 중지할 수 있습니다.

마지막 다섯 개의 커밋을 편집하려면:

git rebase -i HEAD~5

다음과 같은 목록이 표시됩니다:

pick a1b2c3d Add deployment script
pick b2c3d4e Fix typo
pick c3d4e5f Add health check
pick d4e5f6a Update docs
pick e5f6a7b Adjust timeout

각 줄의 시작 부분에 있는 명령을 변경하여 수행할 작업을 제어합니다. 일반적인 선택은 다음과 같습니다:

  • pick: 커밋을 그대로 유지.
  • reword: 변경 사항은 유지하지만 메시지 편집.
  • squash: 이 커밋을 이전 커밋에 결합하고 결합된 메시지 편집.
  • fixup: 이 커밋을 이전 커밋에 결합하고 이 커밋의 메시지 폐기.
  • edit: 이 커밋에서 중지하여 내용을 변경할 수 있음.
  • drop: 커밋 제거.

예를 들어, "Fix typo"가 "Add deployment script"에 속하는 경우 두 번째 줄을 변경하세요:

pick a1b2c3d Add deployment script
fixup b2c3d4e Fix typo
pick c3d4e5f Add health check
pick d4e5f6a Update docs
pick e5f6a7b Adjust timeout

편집기를 저장하고 닫습니다. Git이 커밋을 다시 재생하고 오타 수정을 이전 커밋에 결합합니다.

대화형 리베이스는 풀 리퀘스트 전에 커밋 메시지를 개선하는 데도 유용합니다. 세 개의 커밋이 모두 "update"라고 말하는 경우 reword를 사용하여 실제 변경 사항을 설명하는 메시지로 바꾸세요.

충돌이 발생하면 일반 병합 충돌처럼 해결하세요:

git status
git add resolved-file
git rebase --continue

리베이스가 가치가 없다고 판단되면:

git rebase --abort

이렇게 하면 브랜치가 리베이스 시작 전 상태로 돌아갑니다.

리베이스 중 커밋 분할

때로는 하나의 커밋에 두 개의 관련 없는 변경 사항이 포함됩니다. 예를 들어, Kubernetes 매니페스트를 업데이트하고 README 섹션을 수정하는 커밋이 하나 있을 수 있습니다. 이러한 변경 사항은 별도로 분리되어야 합니다.

대화형 리베이스를 시작하세요:

git rebase -i HEAD~3

분할하려는 커밋에 대해 pickedit으로 변경하세요. Git이 해당 커밋에서 중지되면 변경 사항을 작업 트리에 유지하면서 재설정하세요:

git reset HEAD^

이제 첫 번째 논리적 부분을 스테이징하고 커밋하세요:

git add k8s/deployment.yaml
git commit -m "Update deployment health check"

그런 다음 두 번째 부분을 스테이징하고 커밋하세요:

git add README.md
git commit -m "Document health check behavior"

리베이스를 계속하세요:

git rebase --continue

이렇게 하면 하나의 광범위한 커밋이 두 개의 집중된 커밋으로 바뀝니다. 리뷰어는 관련 없는 작업을 정신적으로 분리하지 않고 각 변경 사항을 이해할 수 있습니다.

재작성 전에 도움을 요청해야 할 때

다른 사람이 사용하는 브랜치를 재작성하기 전에 도움을 요청하세요. 팀 리드, 릴리스 엔지니어 또는 저장소 관리자가 브랜치를 재작성해도 안전한지, 팀이 강제 푸시를 어떻게 처리하는지 알려줄 수 있습니다.

리베이스가 이미 배포되었거나, 태그가 지정되었거나, 인시던트 보고서에서 참조된 커밋에 영향을 미치는 경우 즉시 도움을 요청하세요. 이러한 경우 히스토리를 보존하는 것이 깔끔하게 보이게 하는 것보다 더 중요한 경우가 많습니다.

일반적인 기능 브랜치의 경우 히스토리 편집은 일반적인 정리 단계입니다. 최신 커밋에는 git commit --amend를, 작은 로컬 시리즈에는 대화형 리베이스를, 소유한 원격 브랜치를 업데이트해야 하는 경우 --force-with-lease를 사용하세요.