LabHub
배우기 러닝패스 코스

Git実戦

リベースはN回のマージだ

LabHub 에서 이어서 보기

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

한 줄 요약

커밋 객체 안에는 부모 해시가 들어 있다. 부모가 달라지면 커밋 해시는 반드시 달라진다. 즉 리베이스는 커밋을 이동시키는 명령이 아니라 재작성하는 명령이다.

概念マップ: 남이 보고 있는 커밋은 다시 쓰지 않는다.・원격이 내가 마지막으로 본 상태와 같을 때만・rerere・--onto

왜 이게 필요했나

"이미 푸시한 커밋은 리베이스하지 마라"는 조금 부정확하다. 본질은 푸시 여부가 아니라, 다른 사람의 저장소에 그 커밋 객체가 있는지, 그리고 그 위에 누군가의 작업이 얹혀 있는지다. 그래서 황금률의 정확한 형태는 이렇다. 다른 사람의 작업이 그 위에 쌓여 있는 커밋은 재작성하지 않는다.

혼자 쓰는 원격 브랜치를 정리하려고 리베이스하는 것은 문제가 아니다. 문제는 남이 그 위에서 일하고 있을 때다.

어떻게 동작하나

같은 충돌이 리베이스에서 반복되는 이유도 구조에서 나온다. 머지는 양쪽 끝점과 공통 조상 세 지점만 비교하지만, 리베이스는 커밋을 하나씩 다시 적용하며 그때마다 삼자 병합을 새로 한다. 리베이스는 한 번의 병합이 아니라 N번의 병합이다. 그래서 커밋 다섯 개를 리베이스하면 같은 충돌을 다섯 번 볼 수 있다. rerere.enabled true 를 켜 두면 한 번 해결한 충돌 해법을 기억해 다시 적용한다.

충돌 상태란 인덱스에 한 경로의 후보가 셋(1 공통 조상 / 2 ours / 3 theirs) 들어 있는 상태다. merge.conflictStyle zdiff3 를 권하는 이유가 여기 있다. 상대가 무엇을 바꿨는지가 아니라 둘 다 무엇에서 출발했는지를 알아야 올바르게 합칠 수 있다.

대화형 리베이스의 동사는 여섯 개다. pick(유지), reword(메시지만), edit(내용 수정), squash(합치고 메시지도 합침), fixup(합치고 메시지는 버림), drop(삭제).

--force-with-lease 는 원격 추적 참조가 내가 마지막으로 본 값과 같은지 확인한다. 다만 구멍이 있다. IDE 가 방금 fetch 했다면 참조가 이미 갱신돼 있어서, 내가 보지 못한 커밋이 들어왔는데도 검사를 통과한다. push.useForceIfIncludes true 로 보완한다.

현장에서 만나는 모습

리베이스 중 충돌이 났을 때 가장 흔한 실수는 한쪽을 통째로 골라 버리는 것이다. --ours--theirs 로 빠르게 넘어가면 상대의 작업이 조용히 사라진다. 게다가 리베이스 중에는 ours 와 theirs 의 의미가 직관과 반대로 느껴진다. 재생되는 커밋 쪽이 theirs 이기 때문이다.

또 하나는 중단하는 법을 모르는 것이다. git rebase --abort 는 시작 전 상태로 정확히 되돌린다. 막혔을 때 억지로 밀고 나가는 것보다 중단하고 다시 계획하는 편이 거의 항상 낫다.

언제 리베이스하고 언제 병합하나

규칙은 하나입니다. 남이 보고 있는 커밋은 다시 쓰지 않는다.

상황 무엇을 쓰나
내 로컬 브랜치를 최신 main 위로 rebase — 이력이 깔끔해진다
이미 push 한 공유 브랜치 merge — 다시 쓰면 남의 이력이 깨진다
PR 을 main 에 넣을 때 팀 규칙(squash·rebase·merge 중 하나로 통일)
되돌리기(공개된 것) revert — 새 커밋으로 취소한다

이미 푸시한 브랜치를 꼭 다시 써야 한다면 --force-with-lease 를 씁니다. --force 와 달리 원격이 내가 마지막으로 본 상태와 같을 때만 밀어냅니다. 그 사이 누가 푸시했으면 거절되므로 남의 커밋을 지우지 않습니다.

충돌을 줄이는 도구

rerere — 같은 충돌을 다시 만나면 앞서 푼 대로 자동 적용합니다. 긴 브랜치를 여러 번 리베이스할 때 값이 큽니다.

git config --global rerere.enabled true

--onto — 브랜치의 밑동만 옮깁니다. 기능 브랜치에서 갈라져 나온 브랜치를 main 위로 옮길 때 씁니다.

A---B---C  main
           D---E  feature
                       F---G  hotfix     ← D,E 없이 F,G 만 main 위로

git rebase --onto main feature hotfix

autosquash — 리뷰 지적을 고칠 때 git commit --fixup <해시> 로 표시해 두고, git rebase -i --autosquash 로 한 번에 접습니다. 어느 커밋에 합칠지 손으로 고르지 않아도 됩니다.

사고가 났을 때 되찾기

리베이스 중에 잘못해도 대개 되찾을 수 있습니다. git 은 커밋을 바로 지우지 않습니다.

git reflog                    # HEAD 가 거쳐 온 모든 자리
git reset --hard HEAD@{5}     # 그중 하나로 되돌아간다

# 리베이스 중이라면 그냥 그만둘 수 있다
git rebase --abort

reflog 는 기본 90일 보관됩니다. "커밋이 사라졌다" 는 대개 사실이 아니고, 가리키는 브랜치가 없어졌을 뿐입니다. 해시만 알면 되찾습니다.

git fsck --lost-found 는 어디에서도 가리키지 않는 객체까지 찾아 줍니다. reflog 에도 없을 때의 마지막 수단입니다.

다음 실습에서 할 것

갈라진 두 브랜치를 만들어 리베이스 전후 해시를 직접 비교하고, 빨리 감기로 일직선 이력을 만들고, 충돌을 일부러 만들어 양쪽 변경을 모두 보존하며 해결하고, 한 번은 일부러 중단해 원상 복구를 확인합니다. 그다음 대화형 리베이스로 커밋 세 개를 하나로 합치고 하나를 버린 뒤, 황금률을 자기 문장으로 정리합니다.