git commit | message 수정, 삭제, 취소, push 취소, 기타 옵션
이번 포스팅은 git commit
명령어와 관련한 거의 모든 것을 다룹니다. 커밋 메시지 수정, 삭제, 취소 방법, push 후 취소 그리고 기타 유용한 옵션들까지 정리하겠습니다.
1. git commit 명령어 자세히 살펴보기
git init 명령어로 새 repo를 만들고, git add 명령어로 원하는 변경사항을 staging 하고 나면 이제 커밋할 차례입니다.
git commit
명령어는 현재 staging 되어 있는 모든 변경사항을 현 리포지토리(repo)의 새로운 히스토리로 등록하는 역할을 합니다.
Git 시스템의 핵심이 되는 부분이니만큼 좀더 구체적으로 알아두는 것이 좋은데요.
단계별로 좀 더 자세히 살펴보도록 하겠습니다.
- 사용자가
git commit
명령어를 입력하면, Git 은 repo에 staged 상태의 변경사항이 존재하는지 확인합니다. 만약 staged 상태의 변경사항이 하나도 존재하지 않는다면 다음의 메시지를 출력합니다.
$ git commit
On branch main
nothing to commit, working tree clean
-
변경사항 존재 여부를 확인한 Git 은 새로운 commit object 를 생성하고, 이 커밋에 해당하는 SHA-1 해시값을 부여합니다. git add 포스팅에서도 보았지만 이 object 는 하나의 커밋과 관련한 모든 데이터 저장을 담당합니다.
-
commit object 에 author, commiter, commit message 등의 메타 데이터 값들이 추가합니다.
-
또한, commit object 는 커밋 당시의 파일 시스템을 나타내는 Tree object 에 대한 참조도 저장합니다. Tree object 는 실제 파일 컨텐츠를 가지고 있는 Blob object 를 가리킵니다. 이를 통해, commit object는 현재 상태의 스냅샷을 알고 있는 상태가 됩니다.
-
현재 작업 브랜치의 가장 최근 커밋이 새로운 commit object 의 부모 커밋 값으로 저장됩니다. 이를 통해, 새로 만든 커밋이 현재 브랜치에 등록됩니다.
-
마지막으로, 기존 최신 커밋을 가리키던
HEAD
가 새로 추가한 커밋을 가리키면서 이 커밋이 현재 브랜치의 가장 마지막 커밋이 됩니다.
이러한 과정을 통해, staged 상태의 모든 변경사항들이 이 repo의 히스토리 중 일부로 등록되는 것이죠. 단순한 명령어이지만 내부에서는 이토록 많은 작업이 일어납니다.
다음은 이미 커밋한 commit message 수정 방법을 알아봅니다.
2. commit message 수정 방법
Git 은 한 번 완료한 커밋을 상당히 중요하게 생각하는 시스템입니다. 특히 push를 완료한 커밋은 더 복잡한 작업이 필요합니다. 커밋 하나로도 수많은 팀원들과의 동기화가 망가질 수 있기 때문이죠.
그래도 사람인지라 누구든 실수는 하기 나름입니다. 커밋 메시지에 오타를 냈으면 재빨리 수정하고 팀에 알리면 그만입니다. 어서 그 방법을 알아보겠습니다.
먼저, 현재 브랜치의 가장 최근 커밋을 수정할 때 사용하는 명령어입니다.
$ git commit --amend -m "새로운 커밋 메시지"
실제 실행 결과를 보겠습니다.
커밋 메시지 2.md created
가 2.md created!!!
로 수정되었습니다.
그런데 여기서 꼭 알고 넘어가야 할 부분이 있습니다.
같은 위치의 두 커밋의 해시값이 다릅니다.
수정되기 전에는 7d16880
이고, 수정된 후에는 ff55e3d
입니다.
수정하는 것처럼 보였지만, Git 은 내부적으로 기존 커밋을 제거하고 같은 변경사항과 새 메시지를 가진 새로운 커밋을 등록한 것이었습니다.
따라서, 기존 커밋으로 새로운 작업을 시작한 팀원들과 conflict 가 발생할 가능성이 있습니다. 이를 방지하기 위해, 커밋 메시지를 수정한 경우에도 새로운 커밋과 동일하게 팀에 알려야 합니다.
2.1. 주의할 점: 만약 기존 커밋을 이미 push 한 상태라면?
만약 수정하기 전 커밋을 이미 push 한 상태에서 로컬의 기존 커밋을 --amend
옵션으로 수정했다면, 로컬 repo 와 원격 repo 사이에 차이가 발생합니다.
이 때는 현재 로컬 repo 를 push 할 수 없게 됩니다. 원격 repo 에서 사전에 커밋이 꼬이지 않도록 방지하는 것이죠.
이 경우 아래와 같은 에러 메시지가 출력될 것입니다.
$ git push origin main
! [rejected] your-branch -> your-branch (non-fast-forward)
error: failed to push some refs to 'your-remote-repository-url'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
이 상황을 해결하는 방법은 2가지입니다.
2.2. 강제 push (force-push) 로 해결하기
첫번째 방법은, 원격 repo 의 거부를 무시하고 강제로 현재 로컬 repo 를 push 하는 방법입니다. 나 혼자 운영하는 프로젝트이거나, 모든 팀원이 현재 이슈를 알고 있는 상황이라면 이 방법이 간단할 수 있습니다.
다음 명령어 하나면 해결되기 때문입니다.
$ git push -f
혹은
$ git push --force
그러나, 원격 repo에 새로운 커밋이 등록되었다거나, 기존 커밋을 기반으로 작업중인 팀원이 있는 등 그외의 모든 상황에서는 커밋 히스토리를 꼬이게 만들어서, 상당한 민폐를 끼칠 수 있는 가능성도 있습니다.
따라서, 정말 안전한 상황에서 이 방법을 사용하길 바랍니다.
2.3. pull 과 merge 로 해결하기
두번째 방법은 종 더 안전한 방법입니다. 원격 repo에서 기존 커밋을 가진 히스토리를 pull 한 다음, 로컬 커밋 히스토리와 merge 혹은 rebase 하는 방법입니다. 평소에 merge 하는 방법대로 말이죠. 그 이후에 다시 push를 하면 됩니다.
이 방법은 안전하지만 몇 가지 부가 작업이 필요한 방법이기도 합니다. 그래도 협업을 하고 있다면 두번째 방법이 적합합니다.
pull 과 merge, rebase 명령어에 대한 자세한 내용은, 각각의 포스팅에서 살펴보시기 바랍니다.
3. commit 취소 방법
다음은 commit 취소 방법입니다. 이 방법을 사용하면 기존 커밋은 사라지지만, 기존 커밋에 등록되었던 변경사항은 돌아와서 modified 이자 unstaged 상태가 됩니다.
명령어는 다음과 같습니다.
$ git reset HEAD~1
실제 실행 화면으로 확인해보겠습니다.
우선, 2.md
파일 내용을 새로운 텍스트로 교체한 후, add
, commit
을 이어갔습니다.
커밋을 완료한 후 git status
명령어로 확인해보니 더 이상 변경사항이 존재하지 않게 되었습니다. (nothing to commit
)
그 후, 해당 커밋을 취소하기 위해 git reset HEAD~1
명령어를 실행하였고 2.md
파일의 변경사항이 unstaged change 상태로 돌아왔습니다. (unstaged changes after reset
)
커밋 취소 후 확인한 status 에서도 2.md
파일이 modified 상태임을 확인할 수 있습니다.
마지막으로, git log{:shell}:
명령어를 실행하니 위에서 등록한 1ac689b
커밋이 사라진 것을 확인할 수 있습니다.
이렇게 변경사항을 모두 유지하며 commit 취소를 할 수 있습니다.
4. commit 삭제 방법
다음은 commit 삭제 방법입니다. 이 방법을 사용하면 기존 커밋과 이 커밋에 등록되었던 변경사항을 함께 삭제합니다. 변경사항이 필요하지 않을 때 섹션 3의 취소 방법 대신 사용할 수 있습니다.
명령어는 다음과 같습니다.
$ git reset --hard HEAD~1
실제 실행 화면으로 확인해보겠습니다.
git status
명령어로 확인하면 현재 staging되지 않은 변경사항이 있습니다. git add
명령어로 staging한 후, 커밋까지 완료합니다. 로그를 확인하면 새로운 c2072b8
해시의 커밋이 등록된 걸 알 수 있습니다.
이제 가장 최근 커밋을 삭제하기 위해 git reset ---hard HEAD~1
명령어를 입력합니다. 돌아온 변경사항도 없고 히스토리를 보면 c2072b8
커밋은 정상적으로 삭제되었습니다.
5. push 완료한 commit 취소하기
섹션 2.1에서 한 번 봤지만 원격 repo 에 push 완료한 커밋은 git reset
명령어로 취소 혹은 삭제하기 쉽지 않습니다.
로컬 repo와 원격 repo의 커밋 히스토리가 달라 원격 repo 에서 push를 거절하기 때문입니다.
이를 해결하기 위한 한 가지 방법으로 섹션 2.2의 force-push 방법이 있지만 추천하는 방법이 아니라고 말씀드렸습니다. 이 방법 대신 Git은 명시적으로 이전 커밋을 취소하고 새 커밋을 등록하는 명령어를 제공합니다.
명령어는 다음과 같습니다.
$ git revert HEAD --no-edit
이 명령어는 이전 커밋의 모든 변경사항을 되돌리는 새로운 커밋을 생성합니다. 이러한 작업은 개발자가 수동으로 할 수도 있지만, 개발자도 인간인지라 단 한 줄이라도 빠뜨릴 수 있는 가능성이 존재합니다. 따라서, 위 명령어로 해당 작업을 수행하길 추천합니다.
실제 실행 화면을 보겠습니다.
현재 새로 커밋한 상황입니다.
방금 새로 등록한 커밋을 명시적으로 취소하는 새 커밋을 등록해보겠습니다.
새로운 커밋이 만들어지고, 기존 커밋을 되돌렸다는 메시지가 자동으로 작성되었습니다. 만약 --no-edit
옵션을 주지 않으면 vi
같은 기본 텍스트 에디터가 열려서 원하는 커밋 메시지를 작성할 수 있습니다.
6. 기타 알아두면 개발이 편해지는 옵션들
지금까지의 예제 코드에서 git commit
명령어의 -m
옵션과 --amend
옵션은 충분히 살펴보았는데요. 다음은 기타 옵션들입니다.
6.1. git add -u 명령어가 필요 없는 -a 옵션
git commit -a
옵션을 사용하면 따로 git add
명령어를 사용하지 않아도 모든 변경사항을 자동으로 staging한 후, 커밋합니다.
주의할 점은 untracked 상태의 파일들은 제외되다는 점입니다. 다음과 같이 사용합니다,
$ git commit -a -m "add 없이 commit합니다"
6.2. empty commit 을 위한 --allow-empty 옵션
때로는, 새로운 변경사항 없이도 여러 가지 커밋을 남기고 싶은 경우가 있습니다. 이 옵션은 아무 변경사항이 없을 때 커밋을 남길 수 있도록 하는 옵션입니다.
다음과 같이 사용합니다.
$ git commit --allow-empty -m "No changes"
6.3. pre-commit, commit-msg hook 을 무시하는 --no-verify 옵션
Git hook은 특정 이벤트가 발생했을 때 정해진 스크립트를 실행하는 이벤트 트리거입니다. 새 commit 이 등록되는 과정에서 pre-commit hook
과 commit-msg hook
이 동작합니다.
--no-verify
옵션은 이러한 hook 을 무시하고 새 커밋을 생성하는 방법입니다. 다음과 같이 사용합니다.
$ git commit --no-verify -m "hook을 무시합니다"
7. 마치며
이번 포스팅에서는 git commit
명령어와 관련한 거의 모든 것을 다뤄보았습니다. 커밋하는 방법만큼 중요한 것이 커밋을 수정하고 삭제하는 방법을 아는 것이라는 생각이 듭니다.
이번 포스팅에서 다룬 git reset
과 git revert
명령어를 더 심화적으로 다루는 포스팅도 준비해보겠습니다.