LabHub
배우기 러닝패스 코스

Git実戦

リベースとコンフリクトの解消

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

리베이스가 커밋을 옮기는 것이 아니라 재작성한다는 사실을 해시로 확인하고, 충돌 해결과 중단, 대화형 리베이스의 squash/drop 까지 손으로 해 봅니다. 산출물은 /root/gitx3/ 아래에 둡니다.

왜 중요한가

커밋 객체 안에는 부모 해시가 들어 있습니다. 부모가 달라지면 커밋 해시는 반드시 달라집니다. 이 한 문장에서 나머지가 전부 따라옵니다. 남이 그 커밋 위에 작업을 쌓아 두었다면 재작성은 그 사람의 이력을 깨뜨립니다. 그래서 황금률의 정확한 형태는 "이미 푸시했으면 하지 마라"가 아니라 "다른 사람의 작업이 그 위에 쌓여 있는 커밋은 재작성하지 않는다"입니다. 충돌이 반복되는 이유도 같은 구조에서 나옵니다. 머지는 세 지점만 비교하지만 리베이스는 커밋을 하나씩 다시 적용하며 매번 삼자 병합을 하므로, 리베이스는 한 번의 병합이 아니라 N번의 병합입니다.

단계

전역 git 신원이 없습니다. 새 저장소마다 로컬 user.email / user.name 을 설정하세요.

  1. /root/gitx3/repo 에 저장소를 만듭니다. main 에 커밋 3개를 쌓고 거기서 topic 브랜치를 만들어 커밋 2개를 추가한 뒤, 다시 main 으로 돌아와 커밋 1개를 더 쌓아 갈라진 상태를 만듭니다(topic 의 총 커밋은 4개 이상). 그리고 /root/gitx3/diverged.txt 에 세 줄을 적습니다: main_ahead=1, topic_ahead=2, merge_base=<두 브랜치의 공통 조상 해시>.
  2. topicmain 위로 리베이스합니다. 리베이스 전에 topic 의 끝 해시를 기록해 두고, /root/gitx3/rebased.txtbefore=<리베이스 전 해시>after=<리베이스 후 해시> 두 줄을 적습니다. 두 값은 서로 달라야 하고, after 는 현재 topic 의 끝과 일치해야 하며, 리베이스가 진행 중인 상태로 남아 있으면 안 됩니다.
  3. main 에서 topic빨리 감기로 병합합니다. 결과: main 의 머지 커밋 0개, main 의 총 커밋 6개 이상, maintopic같은 커밋을 가리킬 것.
  4. /root/gitx3/conflict 에 새 저장소를 만듭니다. mainshared.txt 를 두고, conflict-topic 브랜치에서 그 파일을 고쳐 from-topic 이라는 문자열이 들어가게 커밋합니다. 그다음 main 에서도 같은 자리를 고쳐 from-main 이 들어가게 커밋합니다. 이제 conflict-topicmain 위로 리베이스하면 충돌이 납니다. 두 변경을 모두 남기도록 해결하세요. 완료 조건: 리베이스가 끝나 있을 것, 인덱스에 미해결 경로가 없을 것, <<<<<<< 같은 충돌 표식이 파일에 남아 있지 않을 것, shared.txtfrom-mainfrom-topic둘 다 있을 것, 작업 트리가 깨끗할 것.
  5. /root/gitx3/abort 에 새 저장소를 만들고 risky 브랜치와 main 이 같은 파일을 서로 다르게 고치도록 커밋합니다. 리베이스를 시작하기 전에 risky 의 끝 해시를 /root/gitx3/abort-before.txt 에 저장합니다(해시 문자열만). 그다음 리베이스를 시작해 충돌을 만나고 중단합니다. 완료 조건: 리베이스가 진행 중이 아닐 것, risky 의 끝이 기록해 둔 해시와 정확히 같을 것, reflog 에 rebase 기록이 있을 것.
  6. /root/gitx3/squash 에 새 저장소를 만들고 feature/three 브랜치에 커밋 3개를 쌓습니다(각각 s1.txt, s2.txt, s3.txt 추가). 대화형 리베이스로 3개를 1개로 합칩니다. 완료 조건: main..feature/three 커밋 수가 1개, 그 커밋 제목에 feat: combined 포함, s1.txt/s2.txt/s3.txt 셋 다 존재.
  7. /root/gitx3/drop 에 새 저장소를 만들고 feature/four 브랜치에 커밋 4개를 쌓습니다: d1.txt, d2.txt, 그리고 제목이 debug: temp log 이고 debug.txt 를 추가하는 커밋, 마지막으로 d3.txt. 대화형 리베이스로 디버그 커밋만 버립니다. 완료 조건: main..feature/four3개, debug: temp log 커밋 없음, debug.txt 없음, d1/d2/d3.txt 모두 존재.
  8. /root/gitx3/rebase.md 를 씁니다. 반드시 포함할 것: --force-with-lease 설명과 --force 와의 차이, 언제 리베이스하면 안 되는지(공유·푸시·다른 사람 중 한 표현 포함), 왜 해시가 바뀌는지(해시·재작성 중 한 표현 포함), 그리고 merge_commits_on_main= 줄에 /root/gitx3/repomain 머지 커밋 개수를 실제로 세어 적습니다.

참고

갈라진 두 브랜치 만들기

/root/gitx3/repo 에 저장소를 만듭니다. main 에 커밋 3개를 쌓고 거기서 topic 브랜치를 만들어 커밋 2개를 추가한 뒤, 다시 main 으로 돌아와 커밋 1개를 더 쌓아 갈라진 상태를 만듭니다(topic 의 총 커밋은 4개 이상). 그리고 /root/gitx3/diverged.txt 에 세 줄을 적습니다: main_ahead=1, topic_ahead=2, merge_base=<두 브랜치의 공통 조상 해시>.

/root/gitx3/repo 에 main 과 topic 이 서로 다른 커밋을 갖도록 만듭니다. 분기 이후 양쪽에 각각 커밋이 있어야 '갈라진' 상태입니다. 앞선 개수는 git rev-list --count A..B 로 셀 수 있습니다.

topic 을 main 위로 재작성

topicmain 위로 리베이스합니다. 리베이스 전에 topic 의 끝 해시를 기록해 두고, /root/gitx3/rebased.txtbefore=<리베이스 전 해시>after=<리베이스 후 해시> 두 줄을 적습니다. 두 값은 서로 달라야 하고, after 는 현재 topic 의 끝과 일치해야 하며, 리베이스가 진행 중인 상태로 남아 있으면 안 됩니다.

리베이스 전에 topic 의 끝 해시를 먼저 기록해 두어야 비교할 수 있습니다. 리베이스가 끝나면 공통 조상이 main 의 끝과 같아지고, 부모가 달라졌으므로 해시도 달라집니다.

빨리 감기로 일직선 만들기

main 에서 topic빨리 감기로 병합합니다. 결과: main 의 머지 커밋 0개, main 의 총 커밋 6개 이상, maintopic같은 커밋을 가리킬 것.

리베이스 뒤에는 main 이 topic 의 조상이므로 머지 커밋 없이 병합됩니다. --ff-only 를 쓰면 조건이 맞지 않을 때 거부해 주므로 안전합니다.

충돌을 양쪽 보존하며 해결

/root/gitx3/conflict 에 새 저장소를 만듭니다. mainshared.txt 를 두고, conflict-topic 브랜치에서 그 파일을 고쳐 from-topic 이라는 문자열이 들어가게 커밋합니다. 그다음 main 에서도 같은 자리를 고쳐 from-main 이 들어가게 커밋합니다. 이제 conflict-topicmain 위로 리베이스하면 충돌이 납니다. 두 변경을 모두 남기도록 해결하세요. 완료 조건: 리베이스가 끝나 있을 것, 인덱스에 미해결 경로가 없을 것, <<<<<<< 같은 충돌 표식이 파일에 남아 있지 않을 것, shared.txtfrom-mainfrom-topic둘 다 있을 것, 작업 트리가 깨끗할 것.

/root/gitx3/conflict 에서 같은 파일의 같은 자리를 양쪽이 고치게 만든 뒤 리베이스합니다. 해결은 한쪽을 고르는 것이 아니라 두 변경을 모두 남기는 것이고, 충돌 표식까지 지워야 끝난 것입니다.

리베이스 중단하고 원상 복구

/root/gitx3/abort 에 새 저장소를 만들고 risky 브랜치와 main 이 같은 파일을 서로 다르게 고치도록 커밋합니다. 리베이스를 시작하기 전에 risky 의 끝 해시를 /root/gitx3/abort-before.txt 에 저장합니다(해시 문자열만). 그다음 리베이스를 시작해 충돌을 만나고 중단합니다. 완료 조건: 리베이스가 진행 중이 아닐 것, risky 의 끝이 기록해 둔 해시와 정확히 같을 것, reflog 에 rebase 기록이 있을 것.

/root/gitx3/abort 에서 시작 전 브랜치 끝 해시를 파일에 적어 두고 충돌이 나는 리베이스를 시작한 뒤 중단합니다. 중단은 시작 전 상태로 정확히 되돌립니다.

커밋 3개를 1개로 합치기

/root/gitx3/squash 에 새 저장소를 만들고 feature/three 브랜치에 커밋 3개를 쌓습니다(각각 s1.txt, s2.txt, s3.txt 추가). 대화형 리베이스로 3개를 1개로 합칩니다. 완료 조건: main..feature/three 커밋 수가 1개, 그 커밋 제목에 feat: combined 포함, s1.txt/s2.txt/s3.txt 셋 다 존재.

/root/gitx3/squash 의 feature/three 를 main 기준으로 대화형 리베이스합니다. 합치더라도 변경 내용은 버려지지 않으므로 세 파일이 모두 남아야 합니다. 편집기를 띄우기 어렵다면 GIT_SEQUENCE_EDITOR 로 목록을 바꿀 수 있습니다.

커밋 하나 버리기

/root/gitx3/drop 에 새 저장소를 만들고 feature/four 브랜치에 커밋 4개를 쌓습니다: d1.txt, d2.txt, 그리고 제목이 debug: temp log 이고 debug.txt 를 추가하는 커밋, 마지막으로 d3.txt. 대화형 리베이스로 디버그 커밋만 버립니다. 완료 조건: main..feature/four3개, debug: temp log 커밋 없음, debug.txt 없음, d1/d2/d3.txt 모두 존재.

/root/gitx3/drop 의 feature/four 에서 디버그용 커밋만 없앱니다. 커밋을 drop 하면 그 커밋이 추가했던 파일도 함께 사라집니다. 나머지 세 커밋의 파일은 남아 있어야 합니다.

황금률 정리

/root/gitx3/rebase.md 를 씁니다. 반드시 포함할 것: --force-with-lease 설명과 --force 와의 차이, 언제 리베이스하면 안 되는지(공유·푸시·다른 사람 중 한 표현 포함), 왜 해시가 바뀌는지(해시·재작성 중 한 표현 포함), 그리고 merge_commits_on_main= 줄에 /root/gitx3/repomain 머지 커밋 개수를 실제로 세어 적습니다.

/root/gitx3/rebase.md 에 --force-with-lease 와 --force 의 차이, 언제 리베이스하면 안 되는지, 왜 해시가 바뀌는지를 쓰고 merge_commits_on_main= 줄에 3단계 결과를 실제로 세어 적습니다.