Pro git - 7.9
Updated:
Pro git - Rerere
1. Rerere
git rerere
기능은 약간 숨겨진 기능이다.- “reuse recorded resolution” 이라고 해서 기록한 해결책 재사용하기란 뜻의 이름이고 이름 그대로 동작한다.
-
Git은 충돌이 났을 때 각 코드 덩어리를 어떻게 해결했는지 기록을 해 두었다가 나중에 같은 충돌이 나면 기록을 참고하여 자동으로 해결한다.
- 이 기능을 사용하면 재미있는 시나리오가 가능하다.
- 문서에서 드는 예제 중 하나는 긴 호흡의 브랜치를 깔끔하게 Merge 하고 싶은데 Merge 커밋은 많이 만들고 싶지 않을 때 사용하는 것이다.
rerere
기능을 켜고 자주 Merge를 해서 충돌을 해결하고 Merge 이전으로 돌아간다.- 이 과정을 반복해서 기록을 쌓아두면
rerere
기능은 나중에 한 번에 Merge 할 때 기록을 참고한다. -
자동으로 충돌이 날 만한 부분을 다 해결해주시니 몸과 마음이 평안하다.
- 브랜치를 Rebase 할 때도 같은 전략을 사용할 수 있다.
- 쌓인 충돌 해결 기록을 참고하여 Git은 Rebase 할 때 발생한 충돌도 최대한 해결한다.
-
충돌 덩어리들을 해결하고 Merge 했는데 다시 Rebase 하기로 마음을 바꿨을 때 같은 충돌을 두 번 해결할 필요 없다.
- 또 다른 상황을 생각해보자. 뭔가를 개선한 토픽 브랜치가 여러 개 있을 때 이것을 테스트 브랜치에 전부 다 Merge 해야 한다.
- Git 프로젝트 자체에서 자주 이렇게 한다.
- 테스트가 실패하면 해당 Merge를 취소하고 테스트가 실패한 토픽 브랜치만 빼고 다시 Merge한다.
-
한 번 해결한 충돌은 다시 손으로 해결하지 않아도 된다.
rerere
기능은 간단히 아래 명령으로 설정하여 활성화한다.
$ git config --global rerere.enabled true
- 저장소에
.git/rr-cache
디렉토리를 만들어 기능을 켤 수도 있다. -
config
명령을 사용하는 방법이 깔끔하고 Global로 설정할 수도 있다. - 간단한 예제를 하나 더 살펴보자. 위에서 살펴본 예제와 비슷하다.
- 아래와 같은
hello.rb
파일 하나가 있다.
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
- 이전 예제와 마찬가지로 한 브랜치에서는 “hello” 를 “hola” 로 바꿨다.
- 그리고 다른 브랜치에서는 “world” 를 “mundo” 로 바꿨다.
- 이런 상황에서 이 두 브랜치를 Merge 하면 당연히 충돌이 발생한다.
$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.
- Merge 명령을 실행한 결과에
Recorded preimage for FILE
라는 결과를 눈여겨봐야 한다. - 저 말이 없으면 평소처럼 그냥 충돌이 난다.
- 지금은
rerere
기능 때문에 몇 가지 정보를 더 출력했다. - 보통은
git status
명령을 실행해서 어떤 파일에 충돌이 발생했는지 확인한다.
$ git status
# On branch master
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add <file>..." to mark resolution)
#
# both modified: hello.rb
#
git rerere status
명령으로 충돌 난 파일을 확인할 수 있다.
$ git rerere status
hello.rb
- 그리고
git rerere diff
명령으로 해결 중인 상태를 확인할 수 있다. - 얼마나 해결했는지 비교해서 보여준다.
$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
#! /usr/bin/env ruby
def hello
-<<<<<<<
- puts 'hello mundo'
-=======
+<<<<<<< HEAD
puts 'hola world'
->>>>>>>
+=======
+ puts 'hello mundo'
+>>>>>>> i18n-world
end
rerere
기능에 포함된 것은 아니지만git ls-files -u
명령으로 이전/현재/대상 버전의 해시를 확인할 수도 있다.
$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1 hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2 hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3 hello.rb
- 이제는
puts 'hola mundo'
내용으로 충돌을 해결하자. - 마지막으로
git rerere diff
명령을 실행하면 rerere가 기록할 내용을 확인할 수 있다.
$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
#! /usr/bin/env ruby
def hello
-<<<<<<<
- puts 'hello mundo'
-=======
- puts 'hola world'
->>>>>>>
+ puts 'hola mundo'
end
-
간단하게 말해서 Git은
hello.rb
파일에서 충돌이 발생했을 때 한쪽엔 “hello mundo” 이고 다른 한쪽에는 “hola world” 이면 이를 “hola mundo” 로 해결한다. -
이제 이 파일을 해결한 것으로 표시한 다음에 커밋한다.
$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'
- 커밋을 쌓고 나면 “Recorded resolution for FILE” 이라는 메시지를 결과에서 볼 수 있다.
- 이제 Merge를 되돌리고 Rebase를 해서 master 브랜치에 쌓아 보자
git reset
명령을 사용하여 브랜치가 가리키는 커밋을 되돌린다.
$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello
- 이렇게 Merge 하기 이전 상태로 돌아왔다. 이제 토픽 브랜치를 Rebase 한다.
$ git checkout i18n-world
Switched to branch 'i18n-world'
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word
- 예상대로 Merge 했을 때와 같은 충돌이 발생한다.
- 하지만, Rebase를 실행한 결과에
Resolved 'hello.rb' using previous resolution
메시지가 있다. - 이 파일을 열어보면 이미 충돌이 해결된 것을 볼 수 있다.
- 파일 어디에도 충돌이 발생했다는 표시를 찾아볼 수 없다.
#! /usr/bin/env ruby
def hello
puts 'hola mundo'
end
- 또
git diff
명령을 실행해보면 Git이 자동으로 해결한 결과도 확인할 수 있다.
$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
git checkout
명령으로 충돌이 발생한 시점의 상태로 파일 내용을 되돌릴 수도 있다.
$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
- 이때
git rerere
명령을 실행하면 충돌이 발생한 코드를 자동으로 다시 해결한다.
$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby
def hello
puts 'hola mundo'
end
- 강제로 충돌이 발생한 상황으로 되돌리고
rerere
명령으로 자동으로 충돌을 해결했다. - 이제 충돌을 해결한 파일을 추가하고 Rebase를 완료하기만 하면 된다.
$ git add hello.rb
$ git rebase --continue
Applying: i18n one word
- 이처럼 여러 번 Merge 하거나, Merge 커밋을 쌓지 않으면서도 토픽 브랜치를 master 브랜치의 최신 내용으로 유지하거나, Rebase를 자주 한다면
rerere
기능을 켜두는 게 여러모로 몸과 마음에 도움이 된다.