최근 수정 시각 : 2026-08-15 14:26:12

Git/rebase


#!if top = 문서명1 != null ? 문서명1 : calleeTitle != null ? (i = calleeTitle.lastIndexOf("/")) != -1 ? calleeTitle.substr(0, i) : '상위 문서' : '상위 문서'
[[파일:상위 문서 아이콘.svg|align=left&width=21.6px]] {{{#!html  }}} 상위 문서: [[]]


1. 개요2. 기본 개념 및 특징3. 브랜치 rebase4. interactive rebase5. 왜 중요한가?

1. 개요

rebase, rebasing

일련의 커밋들의 기반(base) 커밋을 교체하고, 그 이후의 역사를 재계산하여 새 역사를 만드는 Git 내 매커니즘을 통칭한다.

Git이 어려운 진짜 이유

2. 기본 개념 및 특징

일단 commit history가 어떻게 만들어지고 Git 내부적으로 저장되는지에 대한 기본적인 이해를 가정한다. 만약 어렵다면, three-way-merge는 잠시 잊고 Git의 커밋들은 단순히 엄청 긴 링크드 리스트라고 추상적으로 떠올리면 되며, 이전 커밋을 해시해서 다음 커밋에서 참조하기 때문에 한번 앞쪽이 바뀌면 뒤쪽이 참조하는 데이터가 전부 stale된다는 사실 정도는 알아야 한다. Merkle tree 등 자료구조에 대한 이해가 있으면 더 좋다.

기본적으로 빨간색을 현재 브랜치 포인터로 보고 거기서부터
커밋 A -> B -> C
부모 A B

이렇게 생긴 선형 히스토리가 있을 때, 커밋 B를 B'이 되도록 수정하고 싶다고 하자.
커밋 A -> B -> C
부모 [math(\searrow)] A B
커밋 B'
부모 A

일단 B'는 원본인 B와 같이 A를 참조하게 된다. 다만 이 경우 B'은 내용이 바뀌었으므로 해시가 바뀌는데, 정작 C는 여전히 B의 해시를 참조하고 있어 빨간색 브랜치 기준 B'에 도달할 수가 없다.

때문에 C 또한 부모로 B'을 참조하도록 다시 바꿔줘야 한다는 결론이 나온다. 이를 반영하여 복제 커밋 C'을 생성하면,
커밋 A -> B -> C
부모 [math(\searrow)] A B
커밋 B' -> C'
부모 A B'

다음과 같이 되며 기존 A -> B -> C로 이어지던 히스토리가 A -> B' -> C'으로 복제되며, 이 때 C 커밋이 새로운 커밋 B' 위로(onto) rebase되었다고 표현한다. 마지막으로 이렇게 복제된 경우, 기존 빨간색 브랜치가 가리키는 커밋은 여전히 C이므로 새 복제본을 보도록 포인터를 옮겨주면 rebasing 작업이 끝이 난다.
커밋 A -> B -> C
부모 [math(\searrow)] A B
커밋 B' -> C'
부모 A B'

다만 여기서 주의해야 할 점은, 빨간색 브랜치가 C'을 본다고 C가 사라지거나 없어지는 것은 딱히 아니며, 나머지 브랜치도 다 같이 따라가는 건 아니라는 점이다. 따라서 빨간색을 포함해 파란색 등 C를 보는 모든 브랜치를 전부 바꾸어 준 다음 C쪽 dangling history를 GC drop해야 정말 교체가 완벽하게 이루어지지만, 만약 파란색 등 브랜치가 만악의 근원remote에 push된 경우라면 상황이 망했어요좀더 복잡해진다. 기본적으로 remote에 올라간 브랜치가 참조하는 커밋은 다른 누군가 보고 있거나, local fetch했거나, 실제로 그 위에서 자신만의 작업을 하고 있을 가능성이 있으며, 이 경우 상대방의 브랜치까지 동기화시키는 것은 상당한 수고를 필요로 한다.

이런 경우 관리에 실패하면 복제된 두세 히스토리가 이곳저곳에 펼쳐지는 끔찍한 상황이 펼쳐지기 십상이며, 이런 상황을 막기 위해선 remote에 올라간 히스토리는 rebase하지 않는 것이 좋다. 적어도 병합할 개별 feature 브랜치 등을 병합 전 기존 업스트림 브랜치에 맞추어 rebase하는 등 방향의 작업이 좋으며, 반대 방향은 위와 같은 문제를 야기하기 쉽다.

기본적으로 기존의 선형 히스토리 일부를 특정 기점으로 '수정'하는 상황을 생각했지만, 사실 반드시 흔히 떠올리는 수정일 필요는 없다. 기존의 커밋 사이 새 커밋을 집어넣거나, 여러 커밋을 하나의 커밋으로 압축하거나(squash), 특정 커밋 자체를 솎아내는(drop) 등의 편집도 생각할 수 있으며, 핵심은 앞의 커밋을 바꾸면 뒤 커밋까지 줄줄히 바뀐다는 것 하나뿐이다.

3. 브랜치 rebase

가장 일반적으로 흔히 생각하는 rebase. 자주 보이는 예시로 다음과 같이 파란색에서 분기된 빨간색 브랜치에 새 커밋들(X, Y)이 있고, 그 와중에 파란색 브랜치도 추가적인 업데이트(C, D)가 있는 상황이라 하자.
커밋 A -> B -> C -> D
부모 A [math(\searrow)] B C
커밋 X -> Y
부모 B X

이 경우 three-way-merge를 하지 않고 빨간색 브랜치의 변경사항들을 마치 D 커밋 이후에 작성한 것처럼 만들어, 히스토리를 선형으로 유지하고 싶다고 하자. 즉, X가 부모로 B 대신 D를 가지도록 '수정'할 것이다. 이 경우 앞서와 같이 X는 정보가 바뀌므로 X'이라는 복제 커밋을 만들어야 한다.
커밋 A -> B -> C -> D -> X'
부모 A [math(\searrow)] B C D
커밋 X -> Y
부모 B X

Y가 그대로이니 마찬가지로 Y가 부모로 X'을 가리키도록 같이 수정해야 하며, 이 경우 복제 커밋 Y'이 만들어진다.
커밋 A -> B -> C -> D -> X' -> Y'
부모 A [math(\searrow)] B C D X'
커밋 X -> Y
부모 B X

이제 새 히스토리가 짜여졌으니 빨간색 브랜치 포인터가 새 leaf인 Y'을 가리키도록 옮기기만 하면 rebase가 완료되며, 이 때 기존 브랜치 빨간색이 커밋 D를 새 기반(new base)으로 가지도록 rebase되었다고 표현한다.
커밋 A -> B -> C -> D -> X' -> Y'
부모 A B C D X'

히스토리가 선형이 되었으니 머지를 원할 경우 파란색 브랜치를 빨간색 쪽으로 FF하기만 해도 되며, 이 경우 merge commit을 별도로 생성할 필요가 없어진다.

보통 브랜치 rebase를 먼저 local에서 돌리고 난 다음 push하는 게 일반적인데, 같은 브랜치의 경우 remote 쪽의 변경사항을 pull 하는 시점에서부터 자동으로 rebase하는 것도 흔하다.

4. interactive rebase

5. 왜 중요한가?