4. Git commit 정리

정현우·2021년 9월 12일
2

Git Basic to Advanced

목록 보기
4/5
post-thumbnail

Git commit 정리

commit 정리를 왜해?

  • 앞선 (3)장에 이어, commit을 정리 하는 방법에 대해서는 rebase / commit --amend / merge --squash (스쿼시 머지) 정도로 볼 수 있다. 물론 때에 적절한 reset은 당연하다. 위 사항에 대해 rebase를 중점적으로 시작해 살펴보자.
  • commit 을 정리하는 것은 우리가 언제든지 쉽게 rollback 할 수 있는 포인트를 만드는 것과 같다. 그리고 commit 이 난잡하면 코드 트레이싱을 하는 것 또한 굉장히 난해해 진다.
  • 나 '혼자' 라면 commit 을 나만 알아보게 하면 된다. 하지만 여러명에서 다 같이, diff 를 살펴보고, 어떤 기능이 추가 되었는지, 또 에러가 발생할 경우 추적이 용이하게 하려면 이쁜 commit 을 만드는 것은 너무나 당연하다. 우린 그 부분을 체크해 볼 것 이다.

아직 와닿지 않는다면, (4)는 뛰어 넘고 우선 '원격 저장소' 까지 다 파악하고, 브랜치 몇개씩 파 보고, 작업 다양하게 해 본 뒤에, 다시 돌아오자!


Git rebase

Git rebase target-branch

  • git rebase는 단순하게 말하면 merge의 또다른 방법 중 하나이다. 그리고 conflict 해결에 아주 유용하다.
  • rebase의 존재 목표는 "git branch들의 그래프를 선형으로 만드는 것" 이다.


  • 실제 commit/merge graph를 살펴보면 merge와 차이점이 더 와닿는다.


  • 위 두 그림의 커밋 지점이 어떻게 되어 있는지 보면 와닿는다.
    • 1번 사진은 일반적인 merge(non-fast-forward)방식이고,
    • 2번이 master기점으로 rebase방식으로 branch를 합쳐서 commit이 2번째 사진과 같이 남는다.

Git rebase로 conflict 해결까지

  • git rebase target-branch
  • rebase 병합을 하려는 브랜치로 가서, 위 명령어를 치면 된다. conflict를 가정하고, 시뮬레이션을 해보자!


  • rebase를 통해 target-branch의 'commit(snap shot)'기점으로 병합을 하나씩 시작한다. 충돌이 있을 경우, 해당 커밋에서 stop된 상태로 status에서 나오는 사진과 같이 해당 conflict를 하고, 다음 commit지점으로 병합을 진행할 수 있다.


  • 충돌을 해결하고, add, commit을 해주고, git rebase --continue를 통해서 다음 commit 지점으로 병합을 계속 진행할 수 있다.
  • rebase를 진행하니 함부로 conflict부분을 수정하고 해결하기 힘들 수 있다.
  • 그럴때는 git rebase --abort 로 rebase진행을 취소하고 제대로 체크한 뒤 다시 진행하자. (물론 무지성 continue후 revert(or reset)을 통해 되돌릴 수 있다.) --abort 옵션은 merge에서도 사용가능하다.

  • 허나 rebase를 반대하는 의견들도 있다. 그 이유는 이력이 단순해지면서, 원래 커밋 이력이 변경되기 때문이다. 그래서 '누가 어떻게 무엇을' 한 아주 정확한 이력을 남길 필요가 있다면, rebase는 사용을 권하지 않는다.
  • rebase -i option 에 대해 자세히 알아보자

Git merge --squash

  • squash 는 말 그대로 여러 커밋을 한 덩어리로 뭉쳐주는 방식이다. 작업 브랜치에서 여러 개의 커밋을 만들었지만, 최종적으로는 하나의 커밋으로 병합하고 싶을 때 유용하다.

  • 특히 기능 단위 개발 후 main 또는 dev 브랜치로 병합할 때, 너무 많은 커밋이 남지 않도록 정리하는 데 자주 사용된다. (하나로 뭉치기 때문에 merge commit 기반으로 찾기도 주관적으로, 너무 편하다!)

Git merge opiton(--squash)

git checkout main
git merge --squash feature/my-feature
  • 위처럼 --squash 옵션을 사용하면, feature/my-feature 브랜치의 변경 내용이 main 브랜치에 적용되지만, 커밋은 자동으로 생성되지 않는다. 이어서 아래 명령어로 최종 커밋을 만들어줘야 한다.
git commit -m "feat: 기능 A 개발 완료 (squash)"
  • 즉, --squash는 단순히 변경 사항만 반영하고, 커밋은 직접 만들어야 하는 방식이다. 결과적으로 하나의 커밋만 생성되므로, 커밋 로그가 깔끔하게 정리된다.

아니 github 의 squash and merge 랑 달라요?

  • 예! 다릅니다. https://git-scm.com/docs/merge-options/2.22.1 를 체크 해볼까요?!

  • 일단 git merge --squash <대상 브랜치> 는 대상 브랜치의 변경 사항을 현재 브랜치에 적용하지만, 자동으로 커밋을 생성하지 않는다. 사용자는 변경 사항을 검토하고, git commit 명령어를 사용하여 수동으로 커밋을 생성해야 한다.

  • HEAD는 변경되지 않고, 병합 이력도 남지 않는다!! (non-merge commit).

항목git merge --squash (로컬 명령어)GitHub의 "Squash and merge" (PR 버튼)
커밋 병합 방식여러 커밋을 스쿼시해서 하나로 만들고 직접 커밋해야 함여러 커밋을 스쿼시해서 하나로 만든 상태로 자동 커밋됨
브랜치 그래프기존 브랜치 이력은 남지 않음 (단일 커밋으로 처리)마찬가지로 브랜치 이력이 단일 커밋으로 합쳐짐
사용 시점로컬에서 직접 병합하기 전PR을 GitHub에서 머지할 때
커밋 작성사용자가 직접 작성GitHub에서 자동 생성 또는 수정 가능
  • 반면 github 에서의 Squash and Merge 는 해당 브랜치의 모든 커밋을 하나의 커밋으로 압축(squash) 한 뒤, 기준 브랜치(e.g. main)에 병합하고, 이 과정에서 GitHub가 자동으로 커밋을 생성해 주며, 메시지도 편집 가능 하다.

Git commit --amend

  • 앞선 장에서도 다뤘다. 해당 커밋을 '수정'하는 것이다. 우리는 보통 commit을 남길때 '어떤 규칙'을 만들어둔다. 예를 들어 접두사에 C,R,U,D등을 붙여 어떤 작업인지 바로 쉽게 체크가 가능하게라던지, 누가 했는지 commit에 그냥 남겨버린다던지 등을 말이다.
  • 그래서 commit에 메시지를 '잘' 남기는 것이 곧 이력 추적을 용이하게 하고, 곧 버전관리의 사소하지만 디테일한 부분이라는 것이다.
git commit --amend
  • 실행하면 이전 커밋 메시지를 수정할 수 있는 편집창이 뜬다. 메시지만 바꾸거나, 혹은 변경된 파일이 있으면 그것까지 포함하여 커밋을 수정할 수 있다.

  • 예를 들어 커밋 후 아래처럼 오타가 보였다고 하자.

git add .
git commit -m "fix: bux fix in login page"  # 오타!
  • 이럴 땐 아래와 같이 해결할 수 있다.
git commit --amend -m "fix: bug fix in login page"  # 메시지 수정
  • 제일 중요한 포인트는 메시지뿐만 아니라, 스테이징된 파일도 함께 바뀐다.
  • 그렇기 때문에 이미 푸시한 커밋을 amend한 경우 git push --force 가 필요하므로, 공동 작업 브랜치에서는 주의해야 한다!!

Git 취소하기

실수하지 않는 개발자는 없다. 중요한 건 실수했을 때 되돌릴 수 있는 능력이다. Git은 그걸 가능하게 해주는 강력한 도구다. 여기선 revert, reset, restore 를 중심으로, 언제 어떤 도구를 써야 하는지 살펴보자.

Git revert

  • revert이미 커밋된 내용을 되돌리는 방법 이다. 하지만 reset 처럼 커밋 자체를 없애는 게 아니라, "되돌리는 새 커밋을 만든다." (이게 정말 핵심이다.)

  • 실수로 main 브랜치에 잘못된 커밋을 푸시했을 때, 공동 작업자가 많은 상황이라면 revert 가 안전한 선택이다.

git revert <commit-hash>
# 위 명령은 해당 커밋의 변경사항을 취소하는 커밋을 새로 만든다.

  • git 로그 상에서는 이전 커밋은 그대로 유지되고, 그 커밋을 무효화하는 반대 커밋이 새로 생긴다.

Git reset

  • reset커밋 자체를 삭제하거나, HEAD 포인터를 이전 상태로 되돌리는 방식이다.
명령어설명
git reset --soft <commit>해당 커밋 시점으로 이동, 이후 커밋은 스테이징 상태로 보존
git reset --mixed <commit>기본 옵션. 커밋은 삭제하고 작업 파일은 유지
git reset --hard <commit>커밋도 삭제, 작업 파일도 해당 시점으로 완전 초기화
git reset --hard HEAD~2  # 마지막 두 커밋 제거 + 파일 상태도 되돌림

Git restore (Git 2.23 이상)

  • restore는 파일 하나 또는 일부 파일의 변경을 취소할 때 사용한다. (checkout 에서 분리된 명령어다.)
  • 아직 커밋되지 않은 상태에서, 특정 파일만 이전 상태로 되돌리고 싶을때 사용한다.
  • 즉, 작업 디렉토리(working directory)스테이징 영역(index) 을 각각 따로 조작할 수 있게 해준다.
  • 전통적으로는 checkout, reset 등 여러 명령어가 혼재되어 있었는데, 이를 명확히 분리해 가독성과 직관성을 높였다.
git restore <파일명>          # 작업 파일을 마지막 커밋 기준으로 되돌림
git restore --staged <파일명> # 스테이징 상태를 해제 (unstage)

# ex)
git restore hello.py         # hello.py를 마지막 커밋 상태로 롤백
  • 💡 restore는 reset보다 훨씬 안전하게 파일 단위 복원이 가능하다! 참고로 예전에는 git checkout <file> 로 사용헀었다!
profile
🔥 도메인 중심의 개발, 깊이의 가치를 이해하고 “문제 해결의 본질” 에 몰두하는 software/product 개발자, 정현우 입니다.

0개의 댓글