Git 리베이스 vs. 병합: 차이점 이해 및 사용 시점
두 가지 기본적인 Git 명령인 `git rebase`와 `git merge`를 명확히 이해해 봅시다. 이 문서는 이들의 핵심 기능, 커밋 기록에 미치는 영향(선형 대 비선형), 그리고 각각을 사용해야 할 시점에 대한 명확한 지침을 설명합니다. 특히 공유 브랜치에서 작업할 때 깨끗하고 협업적인 프로젝트 기록을 유지하고 일반적인 함정을 피하기 위한 모범 사례를 배우십시오.
Git Rebase vs. Merge: 차이점 이해와 사용 시기
git merge와 git rebase는 모두 한 브랜치의 작업을 다른 브랜치와 통합하지만, 그 결과로 남는 히스토리는 매우 다릅니다. 공유 브랜치에서 잘못된 명령을 선택하면 이미 커밋을 가져간 모든 팀원의 작업을 어렵게 만들 수 있습니다.
이 가이드는 각 명령어가 무엇을 하는지, 커밋 히스토리를 어떻게 변경하는지, 그리고 팀 워크플로우에서 언제 merge 또는 rebase를 사용해야 하는지 설명합니다.
git merge란?
git merge는 한 브랜치의 변경 사항을 다른 브랜치로 통합하는 가장 일반적이고 직관적인 방법입니다. 브랜치 B를 브랜치 A에 병합할 때, Git은 A와 B 사이의 공통 조상 커밋을 찾습니다. 그런 다음 A 브랜치에 두 개의 부모( A의 끝과 B의 끝)를 가진 새로운 커밋(병합 커밋)을 생성합니다. 이 병합 커밋은 공통 조상 이후 B에서 도입된 모든 변경 사항을 포함합니다.
git merge의 주요 특징:
- 히스토리 보존:
git merge는 병합 커밋을 생성하여 두 브랜치가 언제, 어디서 결합되었는지 명시적으로 기록합니다. 이는 개발의 역사적 맥락을 보존하여 브랜칭 및 병합 지점을 보여줍니다. - 비파괴적: 기존 커밋을 다시 쓰지 않습니다. 두 브랜치의 원래 커밋은 그대로 유지됩니다.
- 병합 커밋 생성: 각 병합은 새로운 커밋을 생성하므로, 여러 브랜치가 분기되고 합쳐지는 그래프로 시각화되는 더 복잡하고 비선형적인 커밋 히스토리가 생성될 수 있습니다.
git merge 예시:
main 브랜치가 있고 여기서 feature 브랜치를 만들었다고 가정해 보겠습니다. feature 브랜치에 몇 개의 커밋을 만들고, 그 사이에 main 브랜치에 새로운 커밋이 추가되었습니다.
# 초기 상태:
# A -- B -- C (main)
# \
# D -- E (feature)
# main 브랜치로 전환
git checkout main
# feature 브랜치를 main에 병합
git merge feature
# 결과 상태:
# A -- B -- C -- F (main)
# \ /
# D -- E (feature)
# 여기서 F는 부모가 C와 E인 병합 커밋입니다.
이 시나리오에서 커밋 F는 E의 변경 사항을 main으로 가져오는 병합 커밋입니다. feature 브랜치는 여전히 독립적으로 존재합니다.
git rebase란?
반면 git rebase는 커밋 히스토리를 다시 작성하여 한 브랜치의 변경 사항을 다른 브랜치에 통합하는 방법입니다. 브랜치 B를 브랜치 A에 리베이스할 때, Git은 B에만 있는 커밋을 가져와 임시로 저장하고, B를 A의 끝으로 재설정한 다음, 저장된 커밋을 A 위에 하나씩 다시 적용합니다.
git rebase의 주요 특징:
- 히스토리 다시 작성:
git rebase는 원래 커밋과 동일한 내용을 가졌지만 새로운 커밋 ID를 가진 새 커밋을 생성합니다. 이로 인해 마치 기능 브랜치가 대상 브랜치의 최신 변경 사항 이후에 순차적으로 개발된 것처럼 커밋 히스토리가 선형으로 보입니다. - 병합 커밋 방지: 일반적으로 병합 커밋을 생성하지 않아 더 깔끔하고 선형적인 히스토리를 만듭니다.
- 파괴적일 수 있음: 히스토리를 다시 작성하므로, 특히 다른 사람과 공유한 브랜치에서는
git rebase를 주의해서 사용해야 합니다.
git rebase 예시:
위와 동일한 시나리오를 사용합니다:
# 초기 상태:
# A -- B -- C (main)
# \
# D -- E (feature)
# feature 브랜치로 전환
git checkout feature
# feature 브랜치를 main에 리베이스
git rebase main
# 결과 상태:
# A -- B -- C (main)
# \
# D' -- E' (feature)
# 여기서 D'와 E'는 D와 E와 동일한 내용을 가진 새 커밋입니다.
feature를 main에 리베이스한 후, 커밋 D와 E는 커밋 C 위에 재생됩니다. feature 브랜치는 이제 main의 최신 커밋에서 시작하며 히스토리는 선형입니다. 원래 커밋 D와 E는 사실상 폐기됩니다(일정 시간 동안 복구 가능).
Rebase vs. Merge: 주요 차이점 요약
| 특징 | git merge |
git rebase |
|---|---|---|
| 히스토리 | 원래 히스토리 보존; 병합 커밋 생성 | 히스토리 다시 작성; 선형 히스토리 생성 |
| 커밋 ID | 원래 커밋은 변경되지 않음 | 새 커밋 생성; 이전 커밋은 폐기됨 |
| 협업 | 공유 브랜치에 안전함 | 공유 브랜치에 위험; 로컬/개인 브랜치에 사용 |
| 복잡성 | 복잡하고 비선형적인 히스토리로 이어질 수 있음 | 더 단순하고 선형적인 히스토리 생성 |
| 목적 | 맥락을 유지하면서 변경 사항 통합 | 변경 사항을 순차적으로 다시 적용하여 통합 |
git merge를 사용해야 할 때
git merge는 일반적으로 더 안전하고 일반적인 선택이며, 특히 오래 지속되는 브랜치에 변경 사항을 통합하거나 공유 브랜치에서 팀과 협업할 때 유용합니다.
main/master에 통합: 완료된 기능 브랜치를 메인 개발 라인(main또는master)으로 가져올 때 병합이 선호되는 경우가 많습니다. 이는 기능 브랜치 개발의 맥락을 보존하고 통합 지점을 명시적으로 표시합니다.- 공유 브랜치: 다른 팀원과 공유하는 브랜치에서 작업하는 경우
git merge가 거의 항상 올바른 선택입니다. 공유 브랜치를 리베이스하면 협력자가 이미 작업의 기반으로 삼았을 수 있는 히스토리를 다시 작성하여 심각한 문제를 일으킬 수 있습니다. - 릴리스 히스토리 보존: 릴리스 브랜치와 같은 중요한 브랜치의 경우 병합 커밋이 있는 명확하고 변경 불가능한 히스토리를 유지하면 감사 및 과거 릴리스 이해에 도움이 될 수 있습니다.
시나리오: 완료된 기능을 main에 병합
# 'main' 브랜치에 있고 기능 브랜치가 최신 상태라고 가정
git checkout main
git merge feature-branch-name
이렇게 하면 feature-branch-name의 모든 변경 사항을 통합하는 병합 커밋이 main에 생성됩니다.
git rebase를 사용해야 할 때
git rebase는 로컬 브랜치를 메인 브랜치의 최신 상태로 유지하고 공유하기 전에 자신의 커밋 히스토리를 정리하는 데 강력합니다.
- 로컬 기능 브랜치 업데이트: 기능 브랜치를 만들었는데
main브랜치가 앞으로 나아간 경우, 기능 브랜치를main에 리베이스하면 즉시 병합 커밋을 생성하지 않고도 업스트림 변경 사항을 통합할 수 있습니다. 이렇게 하면 기능 브랜치 커밋이 논리적으로 순차적으로 유지됩니다. - 로컬 히스토리 정리(대화형 리베이스):
git rebase -i(대화형 리베이스)는 푸시하기 전에 자신의 커밋을 정리하는 데 매우 유용합니다. 여러 개의 작은 커밋을 하나로 스쿼시하거나, 커밋 순서를 변경하거나, 커밋 메시지를 편집하거나, 커밋을 삭제할 수도 있습니다. - 선형 프로젝트 히스토리 유지: 팀에서 깔끔하고 선형적인 히스토리를 우선시하는 워크플로우를 채택하는 경우, 병합 전에 기능 브랜치를
main에 리베이스하면 이를 달성할 수 있습니다. 그러나 이를 위해서는 공유 브랜치를 리베이스하지 않는다는 규칙을 엄격히 준수해야 합니다.
시나리오: 업스트림 변경 사항으로 기능 브랜치 업데이트
# 'feature' 브랜치에 있고 'main'에 새 커밋이 있다고 가정
git checkout main # main으로 전환
git pull origin main # main이 최신 상태인지 확인
git checkout feature # 기능 브랜치로 다시 전환
git rebase main # 최신 main 위에 기능 커밋 재생
이제 feature 브랜치는 최신 main을 기반으로 하며, 최종적으로 feature를 main에 병합할 때 (리베이스 이후 main에 새 커밋이 없으면) 빨리 감기 병합이 됩니다(병합 커밋 불필요).
시나리오: 로컬 커밋 정리(대화형 리베이스)
# 기능 브랜치에 여러 개의 작은 커밋을 만들었다고 가정
git checkout feature-branch-name
git rebase -i HEAD~3 # 마지막 3개 커밋을 대화형으로 리베이스
그러면 편집기가 열리며 커밋에 대해 pick, reword, edit, squash, fixup 또는 drop을 선택하여 더 의미 있는 집합으로 통합할 수 있습니다.
모범 사례 및 경고
- 공유/공개 브랜치는 리베이스하지 마십시오: 다른 사람이 이미 가져와서 작업을 기반으로 삼은 브랜치를 리베이스하면 해당 히스토리가 여러분의 히스토리와 달라집니다. 팀에 명시적인 강제 푸시 워크플로우가 없는 한 공개 또는 공유 브랜치에는
git merge를 사용하십시오. - 자신의 브랜치에서 리베이스: 리베이스는 로컬 개인 기능 브랜치를 깔끔하고 최신 상태로 유지하는 데 탁월합니다. 로컬 변경 사항이 만족스러우면 공유 브랜치에 병합할 수 있습니다.
- 영향 이해:
git rebase를 실행하기 전에 히스토리를 다시 작성한다는 점을 이해했는지 확인하십시오. 확실하지 않다면git merge가 항상 더 안전한 선택입니다. - 팀의 워크플로우 고려: 팀과 어떤 전략(merge vs. rebase)을 선호하는지 또는 정의된 워크플로우가 무엇인지 논의하십시오.
- 깔끔한 히스토리는 중요합니다:
git merge는 히스토리를 보존하지만, 작고 중요하지 않은 병합 커밋으로 가득 찬 히스토리는 복잡해질 수 있습니다.git rebase는 특히 병합 전 기능 브랜치의 경우 더 깔끔하고 읽기 쉬운 히스토리를 만드는 데 도움이 될 수 있습니다.
결론
공유 히스토리를 보존하는 것이 중요할 때는 git merge를 사용하십시오. 다른 사람이 의존하기 전에 자신의 로컬 브랜치를 업데이트하거나 정리하려면 git rebase를 사용하십시오. 다른 사람이 자신의 브랜치에서 작업을 기반으로 했는지 확실하지 않은 경우 리베이스 대신 병합하십시오.