깃은 처음부터 리눅스 오픈 소스를 관리하기 위해 만들어졌습니다. 2005년에 그간 리눅스 소스를 관리하던 툴(BitKeeper)을 공급하던 회사와 관계가 안 좋아 지면서 자체적으로 사용할 수 있는 버전 관리 시스템이 필요하게 되었죠. 리눅스를 처음 만든 리누스 토발즈(Linus Torvalds)도 깃 개발에 적극적으로 참여하게 되고요. 깃 개발의 시작과 과정이 리눅스라는 복잡한 소프트웨어와 수많은 컨트리뷰터를 위한 것이다 보니 여러 상황에 대처할 수 있는 유용한 기능이 많이 있습니다.
깃을 사용하면서 알아두면 큰 자신감을 얻게되는 유용한 팁들을 알아보겠습니다. 관련된 내용은 오픈 소스 팀 채팅 소프트웨어인 줄립의 문서에서 많은 부분 참고하였습니다.
커밋 내용 수정
마지막 커밋 수정
메시지 수정
커밋하고 나서 커밋 메시지를 수정해야할 때는 커밋 명령어에 --amend
옵션을 사용합니다. git commit --amend -m "New Message"
명령어를 사용해서 마지막 커밋 메시지를 변경해 보겠습니다. git log로 지금 로그를 확인해 보고 수정 명령어를 실행한 뒤 결과를 확인해 본 예입니다.
마지막 커밋 수정
이상하게도 꼭 커밋을 하고 나면 바로 버그가 하나 튀어 나옵니다. 수정은 항상 단 한 줄로 해결이 되고요. 다시 커밋을 해야하는데, 그냥 이전 커밋에 포함이 되었더라면 좋았을 것을 하는 생각이 들죠. 이때는 git commit --amend
라고 명령어를 실행하면 됩니다.
- 수정이 필요한 파일을 수정합니다.
git add
명령어로 파일을 추가합니다.git commit --amend
를 실행합니다.
지나간 커밋 수정
인터렉티브 깃 리베이스 명령어를 사용해서 수정을 할 수 있습니다. ‘깃 리베이스 사용하기’에서도 일부 다루었던 기능입니다. git rebase -i HEAD~n
명령어를 사용하게 됩니다. 여기서 n은 수정할 커밋을 포함하는 최근 커밋 갯수입니다. 상황에 맞도록 어떻게 사용하는지 보겠습니다.
커밋 메시지 수정
- 최근 5개의 커밋을 수정하고자 한다면
git rebase -i HEAD~5
라고 실행합니다. - 에디터가 열리면서 5개의 커밋이 각각 줄로 표시되면서
pick
이라고 첫 단어로 나옵니다. 수정하고자 하는 커밋을reword
라고 변경하고 저장합니다. 그리고 에디터를 종료합니다. - 새로 에디터가 열리면서 메시지를 수정하라고 합니다. 메시지를 수정하고 저장합니다.
커밋을 삭제
- 최근 5개의 커밋에 삭제할 커밋이 포함되어 있다면
git rebase -i HEAD~5
라고 실행합니다. 커밋의 갯수에따라 숫자를 변경하면 됩니다. - 에디터가 열리면 삭제하고자 하는 커밋의
pick
이라고 되어있는 것을drop
이라고 변경하고 저장합니다.
커밋을 합치기(squashing)
깃으로 개발하는 과정에서는 가능하면 자주 커밋을 해서 변경사항을 실수로라도 잃어버리는 일이 없도록 습관을 들이는게 좋습니다. 단지 이러고 나면 커밋 갯수가 너무 많아져서 좀 합쳐서 간단하게 보이고 싶은 경우가 있습니다. 어떤때는 그냥 통째로 하나의 커밋으로 만들고 싶을 때도 있고요.
git rebase -i HEAD~n
라고 실행합니다. 여기서 n은 합치고자 하는 커밋의 갯수에따라 숫자로 넣으면 됩니다.- 에디터가 열리면 보존하고 싶은 커밋은 그대로 두고 합치고 싶은 커밋을
pick
대신squash
라고 써 줍니다. 저장합니다.
커밋 순서 변경
git rebase -i HEAD~n
라고 실행합니다. 여기서 n은 합치고자 하는 커밋의 갯수에따라 숫자로 넣으면 됩니다.- 에디터가 열리면 원하는 대로 커밋 순서를 변경합니다. 저장합니다.
변경 후 푸시
이미 푸시가 된 커밋 내용을 이렇게 변경하고 나서 다시 푸시를 하려면 에러가 납니다. 깃 푸시는 로컬이 푸시를 하려고 하는 리모트의 커밋을 기반으로 하지 않으면 푸시를 거부하기때문이죠. 로컬의 커밋을 임의로 변경을 했기때문에 이미 리모트와는 다른 히스토리를 갖고 있어 푸시를 할 수 없는 겁니다. 이때는 강제로 푸시를 해 줘야하는데요. 푸시하는 브랜치의 이름 앞에 +
를 붙여 주면 됩니다. 예를 들어 new_feature
브랜치를 푸시하려고 한다면 git push origin +new_feature
라고 실행하면 됩니다. 여기서 아주 주의를 할 필요가 있습니다. 만일 해당 브랜치가 혼자만 사용하는 것이 아니라면 다른 개발자가 리모트의 브랜치를 받아서 소스를 수정한 후 푸시를 했을 수도 있기때문이죠. 이때 강제로 푸시를 하게되면 동료가 힘들게 개발한 내용이 사라지게 됩니다.
force-with-lease
강제로 푸시를 하는 푸시하는 브랜치의 이름 앞에 +
를 붙여 주는 방법은 팀 동료가 수정한 내역을 강제로 삭제하는 문제를 발생시킬 수 있습니다. 이를 방지하기 위한 안전대책으로 --force-with-lease
옵션을 사용할 수 있습니다. 리모트 브랜치가 현재 로컬 브랜치에서 푸시를 한 후에 변경된 내역이 없을 때만 강제로 푸시를 해 줍니다. 그렇지 않으면 에러를 내려줘서 실수로 수정 내용을 삭제하는 것을 방지해 줍니다.
변경 내용 확인
로컬 변경 사항
파일을 수정하고 아직 스테이징 영역에 넣지 않은 상태에서 변경사항을 보는 경우입니다. 즉 아직 git add
명령어 실행전입니다.
git add
를 해서 스테이징 영역에 있는 파일과 커밋되어 있는 내용을 비교하려면 --cached
옵션을 사용합니다.
아직 스테이징 영역에 있지 않은 수정사항과 git add
로 스테이징 영역에 들어있는 파일을 마지막 커밋된 내용과 비교할 수 있습니다.
같은 브랜치안에서
현재 브랜치 내에서 두 커밋을 비교할 수 있습니다. 각 커밋의 해쉬(git-ref)를 이름으로 사용하면 됩니다.
마지막 커밋과 그 바로 앞 커밋을 비교하는 경우입니다.
커밋 해쉬를 이용해서 임의의 두 커밋을 비교할 수 있습니다.
다른 브랜치
두개의 서로 다른 브랜치간의 비교를 하려면 브랜치 이름을 이용합니다.
새 브랜치가 만들어지고 난 후부터 master 브랜치에 변경된 내역을 보려면 아래와 같이 하면 됩니다.
리모트에 있는 브랜치와도 비교를 할 수 있습니다. origin에 있는 master와 현재 로컬 브랜치를 비교하는 경우입니다.
깃 리셋
상당히 위험한 명령어이지만, 개발을 하다보면 한번쯤은 꼭 쓰게되는 명령어입니다. git reset
명령어와 커밋 레퍼런스 해쉬로 원하는 커밋 상태로 리셋을 해 줍니다. 몇가지 옵션을 줄 수 있는데 가장 많이 사용하게 되는 --hard
와 --merge
옵션을 사용해 보겠습니다.
하드 모드
말 그대로 하드한 리셋입니다. 만일 수정한 소스가 있는데 아직 커밋을 하기 전이라면 해당 수정 사항은 영원히 잃어버리게 되죠. 여기서 커밋 전이라는 것은 스테이지 영역에 있는 것을 포함하고 있으니 각별히 주의를 해야합니다. 예를 들어 보겠습니다. 리뷰 관련 기능을 추가 중입니다. 일부 기능을 수정하면서 2번에 거쳐 커밋을 했습니다. 그리고 또 다른 기능을 수정하고 있었는데 갑자기 새로 구현하고 있는 기능이 필요가 없어졌습니다. 현재 상황을 볼까요.
git status
로 보니 review.py를 수정중이었습니다. git reflog
로 보니 최근 2개의 commit을 한게 보입니다. 기능이 필요가 없어졌으니 5dc0734
커밋 상태로 변경을 하면 됩니다. git reset --hard 5dc0734
명령어를 실행해 보겠습니다.
리멧 명령어를 실행하니 HEAD를 옮겼다는 메시지가 나옵니다. git reflog
명령어로 보니까 현재 HEAD가 옮겨졌다는 내용과 이전에 원래 있었던 2개의 커밋이 보입니다. 하지만 실제로 git log
를 해보면 2개의 커밋을 사라졌다는 것을 알 수 있습니다. 주의할 점은 앞서 수정 중이던 review.py는 수정했던 내용이 없어지고 리셋이 되었다는 것입니다.
머지 모드
하드리셋과 머지 리셋의 다른 점은 커밋하지 않은 수정 내역을 다루는 방식입니다. 앞서 하드 리셋은 커밋하지 않은 작업 내용을 과감하게 리겟해 버리는 효과가 있었습니다. 하지만 머지 리셋은 작업을 했던 파일을 그대로 보존합니다. 그려면 앞서 예에서 보여줬던 review.py 파일의 경우는 어떻게 될까요? 앞의 두 커밋이 모두 review.py 파일을 수정한 것이었기때문에 리셋을 하면 현재 review.py의 수정 내역을 보존 할 수 없습니다. 그래서 에러를 내고 리셋을 하지 않습니다. 혹시 모를 실수를 방지할 수 있겠네요.
커밋 다시 살리기
만일 리셋을 한 상황에서 커밋을 다시 살리고 싶다면 어떻게 할 수 있을까요? 이때는 git cherry-pick
명령어를 사용하면 됩니다. cherry-pick 명령어 뒤에 다시 살리고 싶은 커밋의 해쉬를 적어주면 됩니다. git log를 해 보면 해당 커밋이 다시 살아난 것을 볼 수 있습니다.
몇개 안되는 명령어이지만 깃을 사용하다보면 이 명령어를 사용할 일이 꼭 생깁니다. 한번씩 연습 삼아서 해 보는 것도 좋습니다. 물론 연습용 깃 리포짓토리를 새로 만들고 말이죠.
레퍼런스 로그
git reflog
는 HEAD가 변경되는 이력을 보여주는 명령어입니다. 레퍼런스 로그(줄여서 reflog)는 로컬 리포짓토리에서 브랜치에 변화가 생긴 내용을 기록하고 있습니다. git log
은 현재 브랜치의 히스토리를 보여주면서 단순히 이전 커밋을 순서대로 보여줍니다. 하지만 레퍼런스 로그를 이해하면 소스가 한번 커밋이 되고 나면 절대 데이터를 잃어버릴 일이 없다는 것을 알게 됩니다.
로그의 3번재 줄 내용은 git reset --hard
를 한 명령어입니다. 눈에 보이는 소스를 보면 5dc0734에서 두번 커밋을 한 내용이 순간 사라진 것을 알 수 있습니다. 하지만 reflog로 보니 삭제된 것으로 보였던 2개의 커밋 해쉬를 알 수 있습니다. 그래서 git cherry-pick
명령어를 사용해서 사라졌던 내용을 다시 살릴 수 있었습니다. 실제로 깃은 한번 커밋된 정보는 완전히 버리지 않고 보관을 하고 있었던 것이죠. git reflog
로 확인하면 커밋 해쉬를 확인할 수 있습니다.
깃 실전 가이드
참고
- 줄립 문서: Git Guide
- 깃허브: Changing a commit message
- 프로 깃: A Short History of Git