Git과 CLI (6) - CLI로 rebase 하기
리베이스 사용하기
이전 장의 3-way 병합을 하면 병합 커밋이 생성된다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --all --graph -n4
* 65352c8 (HEAD -> feature1) Merge branch 'master' into feature1
|\
| * ef34ef4 (origin/master, master, hotfix) hotfix 실습
* | 247cb34 새로운 기능 1 추가
|/
* db8ebff (tag: v0.1) mybranch1의 두 번째 커밋병합 커밋이 생성되면 지저분해질 수 있으므로, 트리를 깔끔하게 하고 싶다면 Rebase(재배치) 할 수 있다.
Rebase의 원리를 살펴보면 아래와 같다.
HEAD와 대상 브랜치의 공통 조상을 찾는다
공통 조상 이후에 생성한 커밋들을 대상 브랜치 뒤로 재배치한다.
예를 들어 위 git log를 확인해보면, ef34ef4 (hotfix)의 조상은 db8ebff (tag: v0.1)이다. 그렇다면 병합 커밋을 만드는 대신에 247cb34 뒤로 재배치하는 것도 가능했을 것이다.
다만 이렇게 리베이스를 하는 것에 장점만이 있는 것은 아니다. 재비치된 커밋은 커밋 체크섬이 바뀌기 때문이다. rebase 명령어는 로컬 브랜치를 깔끔하게 정리하기 위함이고, 원격에 Push한 브랜치를 rebase하는 경우 다른 사용자가 혼란을 겪을 수 있으니 적절하지 않다.
우선 이전의 병합 커밋ㅇ르 되돌리고 rebase 해 보겠다. feature1 브랜치를 한 단계 되돌리기 위해 git reset --hard 명령어를 사용해 보겠다.
우선 feature로 체크아웃한다.
yegang@yegangs:~/hello-git-cli$ git checkout feature1
Switched to branch 'feature1'다음으로 하드리셋을 실행한다. 여기서 HEAD~는 HEAD에서 한 칸 전이라는 뜻이다.(HEAD~1과 동일)
yegang@yegangs:~/hello-git-cli$ git reset --hard HEAD~
HEAD is now at 247cb34 새로운 기능 1 추가로그를 확인해보면 한 단계 하드리셋 된 것을 확인할 수 있다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --graph --decorate --all -n3
* ef34ef4 (origin/master, master, hotfix) hotfix 실습
| * 247cb34 (HEAD -> feature1) 새로운 기능 1 추가
|/
* db8ebff (tag: v0.1) mybranch1의 두 번째 커밋이제 rebase해 보겠다.
yegang@yegangs:~/hello-git-cli$ git rebase master
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
error: could not apply 247cb34... 새로운 기능 1 추가
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".안 되잖아? 뭐가 문제인지 확인해 보겠다.
우선 247cb34 커밋을 rebase 실패했다. 내용을 보자면, 충돌을 해결(Resolve all conflicts)한 다음 스테이지에 추가할 것을 알려준다. 그리고 git rebase --continue 명령어를 사용하라고 한다.
우선 어디에서 충돌이 발생했는지 확인해 보겠다.
yegang@yegangs:~/hello-git-cli$ git status
interactive rebase in progress; onto ef34ef4
Last command done (1 command done):
pick 247cb34 새로운 기능 1 추가
No commands remaining.
You are currently rebasing branch 'feature1' on 'ef34ef4'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: file1.txt
no changes added to commit (use "git add" and/or "git commit -a")file1.txt를 수정하면 되겠다.
vi file1.txt 명령어를 이용하여 수정한다.
hello test
second
third - mybranch1
fourth - mybranch1
<<<<<<< HEAD
핫픽스
=======
기능 1 추가
>>>>>>> 247cb34 (새로운 기능 1 추가)수정을 완료했다면 스테이징한다.
git add file1.txt이제 status를 확인해 보겠다.
yegang@yegangs:~/hello-git-cli$ git status
interactive rebase in progress; onto ef34ef4
Last command done (1 command done):
pick 247cb34 새로운 기능 1 추가
No commands remaining.
You are currently rebasing branch 'feature1' on 'ef34ef4'.
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt모든 충돌이 수정(all conflicts fixed)라고 한다.
이제 위 설명에 따라 "git rebase --continue" 명령을 사용하겠다.
yegang@yegangs:~/hello-git-cli$ git rebase --continue
[detached HEAD b30409f] 새로운 기능 1 추가
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/feature1.다음으로 로그를 확인해 보겠다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --decorate --all --graph -n4
* b30409f (HEAD -> feature1) 새로운 기능 1 추가
* ef34ef4 (origin/master, master, hotfix) hotfix 실습
* db8ebff (tag: v0.1) mybranch1의 두 번째 커밋
* 0e4f33b mybranch1의 첫 번째 커밋merge와는 달리 병합 커밋도 없고 히스토리도 한 줄기로 깔끔해졌다. 다만 feature1 브랜치가 가리키는 커밋의 체크섬 값이 b30409f로 바뀐 것을 볼 수 있다. 이렇기 때문에 여러 작업자가 동시에 작업할 때 원격저장소에 리베이스 하는 것은 권장되지 않다.
rebase와 merge의 차이?
3-way 병합은 기존 커밋의 변경 없이 새로운 병합 커밋을 하나 생성한다. 따라서 충돌도 한 번만 발생한다. 충돌 수정 완료 후 "git commit" 명령어를 사용하면 merge 작업이 완료된다.
그러나 rebase는 재배치 대상이 여러 개일 경우 여러 번 충돌이 발생할 수 있다. 또한 기존의 커밋을 하나씩 단계별로 수정하기 때문에 "git rebase --continue" 명령을 사용하면 충돌로 중지된 리베이스를 재개하게 되는 것이다. 여러 커밋에 충돌이 여러 개 발생했다면 충돌을 해결할 때마다 매번 continue 해야 한다. 이러한 경우에는 리베이스를 하는 것보다 그냥 3-way 병합을 하는 것이 더 나을 수 있다.
3-way 병합:머지 커밋 생성
한 번만 충돌 발생
트리가 지저분해짐
rebase:현재 커밋들을 수정하면서 대상 브랜치 위로 재배치함
깔끔한 히스토리
여러 번 충돌이 발생할 수 있음
뻗어나온 가지 없애기
master 브랜치만 사용하고 있더라도, 가끔 씩 순환선처럼 가지가 뻗어 나오는 경우가 있다.
이런 경우는 다른 사람이 push 한 내용을 pull 하지 않은 상태에서 커밋을 하게 되면 이전 커밋을 부모로 한 커밋이 생기게 된다. 그 상황에서 뒤늦게 pull을 하게 되면 3-way 병합이 되는 것이다.
이 경우에 rebase를 활용하면 된다.
우선 뻗어나온 가지 커밋을 만들기 위해 일반적인 커밋을 생성해 보겠다.
yegang@yegangs:~/hello-git-cli$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.yegang@yegangs:~/hello-git-cli$ echo "master1" > master1.txt
yegang@yegangs:~/hello-git-cli$ git add master1.txt
yegang@yegangs:~/hello-git-cli$ git commit -m "master 커밋 1"
[master 36040df] master 커밋 1
1 file changed, 1 insertion(+)
create mode 100644 master1.txt
yegang@yegangs:~/hello-git-cli$ git push일단 커밋을 하나 생성했다. 그리고 원격저장소에 푸쉬까지 완료했다. 이제 reset --hard를 이용해서 한 단계 전 커밋으로 이동하겠다. "master1 커밋"을 다른 사람이 커밋하여 push하였지만 나는 그 내용을 pull하지 않은 상황을 연출하기 위함이다.
yegang@yegangs:~/hello-git-cli$ git reset --hard HEAD~
HEAD is now at b30409f 새로운 기능 1 추가해당 상황이 연출되었고, 이제 뻗어나온 가지 커밋을 만들어 보겠다.
yegang@yegangs:~/hello-git-cli$ echo "master2" > master2.txt
yegang@yegangs:~/hello-git-cli$ ls
file1.txt master2.txt
yegang@yegangs:~/hello-git-cli$ git add master2.txt
yegang@yegangs:~/hello-git-cli$ git commit -m "master2 커밋"
[master 43871cd] master2 커밋
1 file changed, 1 insertion(+)
create mode 100644 master2.txt커밋이 정상적으로 완료 되었고, 이제 로그를 그래프로 출력해 보겠다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --graph --all -n3
* 43871cd (HEAD -> master) master2 커밋
| * 4f6f70b (origin/master) master 커밋 1
|/
* b30409f (feature1) 새로운 기능 1 추가지금 상황에서 master2 작성자는 push 하기 전 pull을 하게 된다. git pull은 "git fetch + git merge" 이기 때문에, 가지를 병합하기 위해서 병합 커밋이 생기고 커밋 히스토리가 지저분해진다.
yegang@yegangs:~/hello-git-cli$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.다만 여기에서는 pull이 진행되지 않았다. merge할 것인지 rebase할 것인지 선택해야 하기 때문이다. 우선 내용의 진행을 위해 merge로 선택하겠다.
yegang@yegangs:~/hello-git-cli$ git config pull.rebase false
yegang@yegangs:~/hello-git-cli$ git config pull.rebase
false다음으로 다시 머지를 해 보겠다.
yegang@yegangs:~/hello-git-cli$ git pull
Merge made by the 'ort' strategy.
master1.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 master1.txt로그를 확인해보면 역시 지저분하다.
* a17d535 (HEAD -> master) Merge branch 'master' of https://github.com/yeganghwang/h
ello-git-cli
|\
| * 4f6f70b (origin/master) master 커밋 1
* | 43871cd master2 커밋
|/
* b30409f (feature1) 새로운 기능 1 추가그렇다면 이제 가지를 없애 보겠다. 우선 1단계 하드리셋을 진행한다.
yegang@yegangs:~/hello-git-cli$ git reset --hard HEAD~
HEAD is now at 43871cd master2 커밋다음으로 origin/master를 리베이스한다.
yegang@yegangs:~/hello-git-cli$ git rebase origin/master
Successfully rebased and updated refs/heads/master.그리고 로그를 확인하면 재배치된 것을 확인할 수 있다. 만약 충돌이 일어난다면 어쩔 수 없이 충돌을 한땀한땀 해결해야 한다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --all --graph -n3
* 53684a2 (HEAD -> master) master2 커밋
* 4f6f70b (origin/master) master 커밋 1
* b30409f (feature1) 새로운 기능 1 추가가지가 사라지고 재배치가 완료되었다.
Rebase 시 주의사항
원격 저장소에 push한 브랜치는 rebase하지 않는 것이 원칙이다.
예를 들어 A1 커밋을 원격에 푸시하고 rebase를 하게 되면 원격에는 A1이 존재하고 로컬에는 다른 커밋인 A1'이 생성된다. 이 때 내가 아닌 다른 사용자는 원격에 있던 A1을 병합할 수도 있다. 그런데 변경된 A1'도 언젠가는 원격에 푸시되고 그럼 원격에는 같은 커밋이었던 A1과 A1'이 동시에 존재하게 된다. 이 상황에서 또 다른 작업자가 충돌을 해결하기 위해 merge와 rebase를 하게 되면 불필요한 작업으로 인해 혼란이 발생한다. 동일한 커밋이 여러 개 존재하고, 충돌도 발생하고, 히스토리도 꼬여진다.
따라서 가급적 rebase는 원격에 존재하지 않는 로컬의 브랜치들에만 적용하길 권장하고 있다.
임시 브랜치 사용
임시 브랜치를 사용하면 히스토리가 꼬이거나 다른 작업자에게 민폐를 덜 끼칠 수 있다. 원래 작업하려던 브랜치의 커밋으로 임시 브랜치를 만들고 자유롭게 사용해도 아무런 문제가 발생하지 않는다. 나중에 그 브랜치를 병합하고 삭제하면 모든 내용이 원상 복구되는 것이다.
그렇다면 새로 임시 브랜치를 만들어 보겠다. feature1 브랜치에서 test 브랜치를 생성한다.
yegang@yegangs:~/hello-git-cli$ git branch test feature1
yegang@yegangs:~/hello-git-cli$ git checkout test
Switched to branch 'test'다음으로 파일을 하나 새로 만들어서 스테이징 후 커밋해 보겠다.
yegang@yegangs:~/hello-git-cli$ echo "테스트 브랜치 임시 작업" > test.txt
yegang@yegangs:~/hello-git-cli$ ls
file1.txt test.txt
yegang@yegangs:~/hello-git-cli$ git add .
yegang@yegangs:~/hello-git-cli$ git commit -m "임시 커밋"
[test a316e49] 임시 커밋
1 file changed, 1 insertion(+)
create mode 100644 test.txt로그를 확인해보면 다음과 같다.
yegang@yegangs:~/hello-git-cli$ git log --oneline --decorate --graph --all -n 4
* a316e49 (HEAD -> test) 임시 커밋
| * 53684a2 (origin/master, master) master2 커밋
| * 4f6f70b master 커밋 1
|/
* b30409f (feature1) 새로운 기능 1 추가이제 이 브랜치를 병합하든지 삭제하든지 하면 된다.
참 쉽죠?
참고로 삭제하기 위해서는 다른 브랜치로 체크아웃 해야 한다.
yegang@yegangs:~/hello-git-cli$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.브랜치 삭제 명령어는 "git branch -D [브랜치이름]" 이다.
yegang@yegangs:~/hello-git-cli$ git branch -D test
Deleted branch test (was a316e49).
댓글
댓글 쓰기