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 명령어를 더 심화적으로 다루는 포스팅도 준비해보겠습니다.
