상세 컨텐츠

본문 제목

[git] git rebase로 commit 정리하기 기록용

프로그래밍/기타

by jisooo 2024. 1. 14. 17:04

본문

 

 

 

#git #rebase #commithistory #gitrebase #merge #gitmerge #commit정리 #기록용 #pr #review

 

 

 

 

최근 여러 프로젝트를 진행하면서,

PR을 활발하게 공유하며 리뷰를 하고 있는데,

 

파트 내 스터디를 진행하면서 리뷰하기 좋은 PR은 무엇인가?

라는 내용을 주제로 많은 고민을 했었다.

리뷰어들이 리뷰하기 좋은 PR과 커밋을 만든다는 것은 생각해보면 당연하고 기본적인 일이였다.

그리고 이런 작업을 하기위한 고민들도 충분히 고민할 만한 가치가 있었다!

 

 

예전에는 깊은 고민 없이 없이 리뷰를 받거나, 리뷰를 할 때에는

각 커밋 히스토리를 일일이 확인하지 않고,

File Changed 탭에 가서 코드의 수정사항들만 쭉 확인하고 리뷰를 하곤 했다.

 

근데 대형 프로젝트의 경우 코드 변경 사항이 어마어마하게 많아지면,

이 File changed 탭에 몇십건 이상의 수많은 변경 사항들이 생겨나게 되고,

리뷰어의 입장으로서는 File changed 탭만 모고 상당히 많은 변경 사항들을 리뷰하기에

너무나도 번거로운 작업이었다.

 

그래서 "리뷰어"의 입장에서 commit을 기반한 원활한 코드 리뷰를 위해

어떤식으로 PR을 쪼개고, commit 메시지를 작성하고 히스토리를 정리할지에 대한 고민을 해보고

제대로 배워볼 수 있는 소중한 경험을 사내 스터디 중에 하게 되었다.

 

우선 Pull Request의 단위는 일단 컴파일이 가능한 단위를 기본으로,

변경 범위가 너무 클 경우, 기능 별로, 작업 별로 쪼개서

각 PR을 한눈에 파악하기 쉽고 쉽게 리뷰할 수 있도록 개선했다.

 

그리고 commit에 feature / fix / test / refactor / enhance 등

commit의 성격을 명시하고, 해당 커밋을 통해 고민한 점, 개선한 점 등을 커밋메시지에 적어

리뷰어가 작업 commit들을 기반으로 편하게 리뷰할 수 있도록 진행하였다.

 

그리고 commit을 기반으로 리뷰하려면

기본적으로 commit 히스토리가 보기좋게 유지되어있어야 한다.

 

 

사실 처음부터 완벽한 commit 히스토리를 만들 수는 없는게..

코드를 작성하다보면 이미 remote 저장소에 push한 상태인데,

 

테스트하면서 버그 수정이나 구조 변경사항들이 많이 생기게 되고,

그러면서 수정 커밋이 뒤에 계속 들어가고,

 

설계를 고민하다가 아키텍처가 변경되어

큰 작업의 리팩토링이 추후 진행되기도 한다.

 

 

이런 예측 불가능한 수많은 상황들 때문에 변수가 많아서

초기에 push한 commit들은 나중에 바뀌어서

초기 commit을 보고 리뷰하는 일은 리뷰어 입장에서 쓸모 없는 일이 되버리곤 했다.

 

 

그래서 feature 작업 브랜치에서 대대적인 commit history 정리가 필요했다.

이미 remote 저장소에 push된 오래되고 정리 안된 커밋들을 재단장하여 순서를 바꾸고,

 

기존 커밋들과 특정 커밋의 작업을 통합하고 커밋 메시지를 수정하게 되는 등의 과정들을 겪으면서

git rebase 기능을 아-주 유용하게 활용할 수 있었다.

 

 

기록용으로 rebase의 특징과 rebase의 각 옵션 별 특징들,

그리고 reset을 통해 commit을 재정리하는 방법까지 정리해본다!

 

 

 

 

 

 

 

우선 rebase의 개념, 그리고 흔히 비교되는 rebase와 merge의 차이점에 대해서 알아보자.

1) rebase란? rebase와 merge의 차이?

 

 

 

1-1) git merge

 

위 이미지를 보면 main 브랜치가 있고,

main을 base로 하여 Feature 브랜치가 신규로 생성되었다.

 

하얀색 커밋 끝에서 feature 브랜치가 따져서 초록색 커밋들로 작업을 진행하고 있는데,

이후 중간 중간에 base인 main 브랜치의 하늘색 작업 커밋들이 추가되었다.

 

이때 feature의 입장에서 main의 최신 commit들을 가져와야 하는 상황이라고 가정하자.

그럼 feature branch로 이동해서 main 브랜치의 작업들을 merge 해주는 방법이 있다.

 

>> git checkout feature
>> git merge main
 

 

 

* 참고로 merge에는 아래와 같은 옵션들이 있다.

 

1) fast-forward 옵션 (--ff)

  • merge의 기본 옵션이다.
  • fast-forward merge는 기준 브랜치의 HEAD가 통합하려는 브랜치의 마지막 HEAD 위치로 이동하게 된다.
  • 중간에 다른 commit이 껴있고, 해당 commit이 같은 부분을 수정했다면 conflict이 일어나 수정해줘야함.
  • 추가 옵션을 지정하지 않으면 별도의 merge commit은 생성되지 않는다.

 

2) non fast-forward (--no--ff)

- fast forward merge와 다르게 추가된 merge commit이 제일 HEAD 위치에 추가 생성된다.

 

3) squash (--squash)

- squash 옵션으로 merge를 하게 되면,feature 브랜치에서 작업한 모든 커밋들을 하나의 커밋으로 합쳐서 base 브랜치에 통합한다.

 

 

 

 

 

보통 작업 브랜치에 base 최신 변경사항을 가져오거나,

작업 브랜치를 base branch에 적용할때 merge 기능을 위주로 사용했었는데,

 

이렇게 관리하면 파생이 시작된 base branch의 이후 commit 히스토리부터 시작해서,

feature 브랜치의 작업 commit들, base branch의 최신 커밋들과 merge 커밋들이 히스토리에 마구 섞이면서,

git 이력 관리가 지저분해진다는 단점이 있다.

 

 

 

1-2) git rebase

 

 

 

 

위 예시처럼 하나의 feature branch가

main branch에서 신규로 따지고 작업을 이어가는 상황이다.

 

이후 main branch에서 이후 추가 변경사항이 있는 경우에

main의 변경사항을 feature에 가져와야 한다.

 

이때 rebase를 사용하면 base가 main 브랜치의 끝에서 시작하도록

전체 feature 브랜치를 이동하여 main에서 모든 새 커밋을 통합한다.

 

 

즉 기존에는 feature 브랜치의 base가 기존엔 하얀색 제일 끝 커밋에서부터 시작됐는데,

main의 가장 마지막 하늘색 커밋에서부터 시작하도록 base를 바꾸고,

전체 feature 브랜치와 작업을 그 뒤에 새로 작성한다는 것이다.

 

 

 

 

rebase는 merge 처럼 병합 커밋을 사용하는 대신

원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어

프로젝트 기록을 다시 작성한다.

 

 

이렇게 rebase를 하면 파생된 브랜치의 커밋 이력이 기준 브랜치와 같아지므로

작업 순서대로 커밋 이력이 남게 된다.

따라서 commit history를 시간 순서대로 반영시킬 수 있다.

 

또한 앞에 merge에서 살펴본 불필요한 병합 커밋을 제거하여 히스토리를 깔끔하게 유지할 수 있다.

rebase는 마치 하나의 브랜치에서 작업된 것처럼

병합이 아닌 선형의 commit history가 남기 때문에

 

사용하기는 merge보다 훨씬 까다로운 감은 있지만,

리뷰 전 commit 정리가 필요하거나 이력 관리를 위해서는 rebase를 사용하라고 한다.

 

 

 

 

 

일반적으로 이렇게 rebase와 merge 모두

두 개의 브랜치 작업을 합쳐서 히스토리를 적용하는 용도로 많이 사용한다.

 

하지만 그 외에도,

현재 작업중인 브랜치의 최신 커밋을 가리키는 HEAD 포인터를 이동시킬 수 있다는 특성을 이용해서

과거 커밋 히스토리를 수정하는 데에도 사용할 수 있다.

 

나는 후자의 목적으로 rebase를 활용하였다.

 

 

 

 

2) git rebase -i [commit hash]

 
// 특정 commit 이후의 commit부터 commit을 재단장할 수 있다.
// 즉 바꾸고 싶은 commit의 바로 직전 commit hash를 기반으로 rebase를 진행하면 된다.
main >> git rebase -i {{수정을 진행할 커밋의 직전 commit hash}}

// HEAD를 기준으로 rebase를 잡을 수도 있다.
// 예를 들어 아래와 같이 rebase를 진행하면, head로부터 4개 전까지의 commit까지 로그를 가져와 재단장할 수 있다.
main >> git rebase -i HEAD~4
 

 

 

-i (interactive) 옵션을 사용하면 브랜치의 커밋 기록에 대한 전체적인 제어를 제공하므로

자동화된 rebase보다 훨씬 더 강력하다고 한다.

일반적으로 기능 브랜치를 main으로 병합하기 전에 복잡한 기록을 정리하는 데 사용되는 옵션이다.

 

해당 옵션을 사용하여 아래의 각 명령어들을 적용하여 각 commit log들을 재단장할 수 있다.

다른 명령어들도 몇개 더 있지만,,

내가 commit history 정리 중에는 아래 것들만 활발히 사용했어서

사용한 것들 위주로만 기록을 남겨봄!

 

 

 

 

 

  • pick
    • 해당 commit을 수정하지 않고 그대로 사용하겠다.
    • 해당 커밋에 대해 따로 변경사항이 없다면 기본으로 작성되어있는 pick 상태로 그냥 에디터에서 유지시켜두면 된다.
// rebase -i를 입력하면 아래와 같은 내용이 vim editor에 나오는데, 
// 이게 commit_hash 이후로 1 -> 2 -> 3 -> 4번 commit 히스토리가 쌓인 상황이다.
// editor를 수정하여 필요에따라 명령을 바꿔서 원하는 기능을 사용할 수 있다.
// 아래처럼 pick이 default이다.

pick 472ad37 test commit1
pick f3348cb test commit2 
pick cdbbb2c test commit3
pick aef042t test commit4


// case 1: 단순히 커밋 순서 바꾸기
pick 472ad37 test commit1
pick cdbbb2c test commit3   
pick f3348cb test commit2 
pick aef042t test commit4

// 3번째 커밋을 1번과 2번 커밋 사이에 위치 시켜 커밋 순서를 바꿀 수 있다.
// 이때 커밋 메시지나 작업 내용을 바꿀 니즈는 없으므로 pick 명령어를 그대로 사용한다.
 

 

 

 

  • reword
    • 해당 commit의 message를 수정할 수 있다.
// 특정 커밋의 커밋 메시지만 변경하기
pick 472ad37 test commit1
pick f3348cb test commit2 
pick cdbbb2c test commit3
reword aef042t test commit4

// 이렇게 하면 4번 commit의 메시지를 변경할 수 있는 에디터가 뜬다. vim editor를 원하는 메시지로 수정해주면 끝!
 

 

 

  • edit
    • 해당 commit의 작업 내용 자체와 message를 수정할 수 있다.
pick 472ad37 test commit1
edit f3348cb test commit2 
pick cdbbb2c test commit3
pick aef042t test commit4


// 이렇게 하면, 2번 커밋에 git commit --amend 옵션으로 추가 작업사항을 합치라는 문구가 뜬다.
f3348cb... test commit2 위치에서 멈췄습니다
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
 
 

여기까지 보면, edit 명령을 추가한 commit으로 HEAD가 위치한 것을 볼 수 있다.

이제 이 HEAD 위치에서 추가 작업을 하여 기존 commit에 합치고 메시지도 변경할 수 있다.

>> git log

commit 733b6d17cbfc847d1394d743d6eb286da517df4b (HEAD)
Author: jisoo
Date:   Thu Oct 12 19:45:47 2023 +0900

    test commit2 
 

 

 

// 신규 작업을 추가한다.
>> git add ./

// 신규 작업이 추가되었는지 확인
>> git status

// --amend 옵션으로 기존 commit에 신규 작업 내용을 끼워 넣는다.
>> git commit --amend

// 그러면 최종 합쳐진 커밋 메시지를 수정할 수 있는 에디터가 나온다.
// 처음엔 기존 HEAD에 위치한 커밋 메시지가 나오고, 에디터로 추가 작업에 대한 내용을 추가하는 등의 수정 할 수 있다.

test commit2

# 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
# 줄은 무시되고, 메시지를 입력하지 않으면 커밋이 중지됩니다.
#
# 시각:      Thu Oct 12 19:45:47 2023 +0900
#
# 대화형 리베이스 진행 중. 갈 위치는 48c2028
...
...
 

 

 

 

  • squash
    • 작업을 상단 commit과 합친다. 단 해당 commit은 사라지고, 상단 commit 메시지를 변경할 수 있다.
    • 이전 commit과 합치되, 상단 commit 메시지에 합쳐진 변경사항을 추가해서 사용할 수 있다.
//  c특정 커밋 그룹을 합치고 커밋 메시지 변경하기. (1,2,3)
pick 472ad37 test commit1
squash f3348cb test commit2 
squash cdbbb2c test commit3
pick aef042t test commit4

// 이렇게 하면 2,3번 커밋의 작업이 1번에 합쳐지고, 1번 커밋의 메시지를 확인할 수 있는 에디터가 뜬다. 
// 이때 vim editor로 커밋 메시지도 수정할 수 있음!

// 멀리 떨어져있는 특정 커밋들을 합치고 커밋 메시지 변경하기.
pick 472ad37 test commit1
squash aef042t test commit4   // 4번 커밋의 위치를 1번 커밋 아래로 옮긴다.
pick f3348cb test commit2 
pick cdbbb2c test commit3

// 이렇게 하면 4번 커밋의 작업이 1번 커밋에 합쳐지게된다. 그리고 1번 커밋의 메시지를 확인하고 수정할 수 있는 에디터가 뜬다.
// 이때 에디터에서 메시지를 수정해주면 적용 완료!
 

 

 

  • fixup
    • 작업이 상단 commit과 합쳐져서 해당 commit log는 사라지고, 상단 commit 메시지만 그대로 사용한다.
//  특정 커밋 그룹을 단순히 합치기. (1,2,3)
pick 472ad37 test commit1
fixup f3348cb test commit2 
fixup cdbbb2c test commit3
pick aef042t test commit4

// 이렇게 하면 2,3번 커밋의 작업이 1번에 합쳐지고, commit message도 그대로 변경없이 1번만 남는다.
 

 

 

 

여기서 주목할 점은,

각 commit 행의 위치를 자유자재로 바꿔서

순서까지도 바꿀 수 있는 것이다.

 

내가 히스토리 정리 중 필요했던 케이스 중에는,

순서가 멀리 떨어져있는 2-3개의 commit을 하나로 묶고 메시지도 다시 수정하고 싶었다.

그래서 내가 딱 원하는 상황에 사용할 수 있었다. (특히 squash command을 활발히 사용함)

 

각 commit log의 순서까지 제어해서,

특정 commit 그룹끼리 묶어서 다시 하나의 commit으로 합칠 수도 있고,

이미 HEAD보다 한창 앞에 있는 commit에 다른 작업 내용을 추가할 수도 있고,

단순히 commit 순서만 바꾼다거나, commit 메시지를 바꿀 수 있다.

 

 

 

 

 

rebase 적용을 다 하고 나서,

적용이 끝났으면 --continue 옵션을 사용하면 된다.

 

이후 해당 내용을 push하여 remote에 반영하면 되고,

만약 rebase 작업을 하다 뭔가 꼬여서 되돌리고 싶다면

여태 작업한 rebase 내용을 --abort 옵션으로 취소할 수 있다.

 

// rebase 적용후 remote 저장소에 반영하기 (force push)
>> git rebase --continue
>> git push -f origin

// rebase 취소하기
>> git rebase --abort
 

 

 

 

 

3) git reset --mixed [commit hash]

 

단, 너무 변경사항이 많다면...

reset --mixed 옵션으로 파일 변경사항들만 그대로 로컬에 유지시킨뒤,

커밋을 싹 새로 만드는 것도 방법이 있다.

 

이번에 커밋을 정리할때 rebase와 reset을 같이 활용했는데,

특정 커밋 이후부터 파일 변경사항은 로컬에 남겨두고,

전체 커밋과 메시지 자체를 싹 재정비하고 싶을때 reset --mixed 옵션을 사용하여

커밋을 다시 정리했다.

 

 

정리된 마지막 commit까지 유지시키려면,

해당 마지막 커밋의 hash로 reset 명령을 날려,

commit log를 해당 커밋으로 되돌린다.

 

 

 

 

reset 명령어에서 사용할 수 있는 옵션은 아래 3가지다.

보통 커밋과 파일 변경사항까지 싹 날리고 싶을 때 hard 옵션을 많이 사용하고,

위의 케이스처럼 파일은 그대로 유지시킨뒤 commit만 재정비 하고 싶으면

mixed 옵션을 사용하면 된다.

 

 

  • --mixed

이후 커밋들에서 작성/수정한 파일 변경사항들을 로컬에 올려두자.

그럼 커밋만 사라진 상태로 변경사항들만 그대로 파일들이 unstaged 올라와있다.

 

  • --hard

참고로 아예 파일 변경사항까지 싹다 없애려면 --hard 옵션을 사용하면 되고,

 

  • --soft

mixed 옵션과 비슷하지만,

이후 커밋들은 reset된 상태에서

변경사항들이 staged 상태로 올라와있다.

 

 

 

feature >> git reset --mixed {{되돌아갈 commit_hash}}
리셋 뒤에 스테이징하지 않은 변경 사항:
{{commit_hash 이후의 파일 변경사항들 리스트가 출력됨}}       


// 커밋 로그를 확인해보면 되돌아갈 commit이 제일 HEAD에 위치해서 되돌아간 것을 확인할 수 있음.
feature >> git log
commit {{commit_hash}} (HEAD -> feature)
Author: jisoo
Date:   Wed Oct 18 18:05:42 2023 +0900

    main commit
 

 

 

 

 

 

 

 

 

 

+

마지막은 git-scm.com 공식사이트에 나온

히스토리에 대한 의미를 되짚는 인상깊었던 내용들로 마무리:)

https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-Rebase-%ED%95%98%EA%B8%B0

 

 

 

히스토리를 보는 관점 중에 하나는 작업한 내용의 기록으로 보는 것이 있다.

작업 내용을 기록한 문서이고, 각 기록은 각각 의미를 가지며, 변경할 수 없다.

이런 관점에서 커밋 히스토리를 변경한다는 것은 역사를 부정하는 꼴이 된다.

언제 무슨 일이 있었는지 기록에 대해 거짓말 을 하게 되는 것이다.

이렇게 했을 때 지저분하게 수많은 Merge 커밋이 히스토리에 남게 되면 문제가 없을까?

역사는 후세를 위해 기록하고 보존해야 한다.

 

히스토리를 프로젝트가 어떻게 진행되었나에 대한 이야기로도 볼 수 있다.

소프트웨어를 주의 깊게 편집하는 방법에 메뉴얼이나 세세한 작업내용을 초벌부터 공개하고 싶지 않을 수 있다.

나중에 다른 사람에게 들려주기 좋도록

Rebase 나 filter-branch 같은 도구로 프로젝트의 진행 이야기를 다듬으면 좋다.

 

 

 

 

 

 

 

 

 

 

* 관련 내용 이해와 실습, 포스팅 작성에 참고 링크:

 

https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-Rebase-%ED%95%98%EA%B8%B0

병합과 기준 재지정(rebase) 비교 | Atlassian Git Tutorial

 

 
이미지 썸네일 삭제
Git Rebase --Interactive 옵션 알아보기 - 재그지그의 개발 블로그

대화형으로 Git 커밋 히스토리를 수정할 수 있게 해주는 Interactive 옵션에 대해 알아봅니다.

wormwlrm.github.io

관련글 더보기

댓글 영역