Merging vs. rebasing
git rebase
명령은 초보자가 거리를 둬야 하는 마법같은 Git 주문으로 유명하지만, 사실은 개발 팀에서 신중하게 사용할 경우 훨씬 쉽게 사용할 수 있습니다. 이 문서에서는 관련된 git merge
명령으로 git rebase
를 비교하고 잠재적인 모든 기회를 식별하여 일반 Git 워크플로로 rebase를 통합합니다.
Conceptual overview
git 기준 재지정
에 대해 알아야 할 첫 번째는 git 병합
과 동일한 문제를 해결한다는 것입니다. 이러한 명령 모두는 하나의 브랜치에서 다른 브랜치로 변경 사항을 통합하도록 설계되었습니다.—매우 다른 방식으로 이를 수행합니다.
전용 브랜치에서 새 기능으로 작업을 시작할 때 발생하는 사항을 고려하십시오. 다른 팀원은 새 커밋으로 main
브랜치를 업데이트합니다. Git을 공동 작업 도구로 사용하는 개발자에게 익숙할 수 있는 포크된 기록이 발생합니다.
이제 main
의 새 커밋이 작업 중인 기능과 관련되어 있다고 가정하겠습니다. 새 커밋을 feature
브랜치에 통합하기 위해서는 병합 또는 rebase라는 두 가지 옵션이 있습니다.
관련 자료
전체 Git 리포지토리를 이동하는 방법
솔루션 보기
Bitbucket Cloud에서 Git에 대해 알아보기
The merge option
가장 쉬운 옵션은 다음과 같은 방법으로 main
브랜치를 feature 브랜치에 병합하는 것입니다.
git checkout feature
git merge main
또는 다음과 같이 한 줄로 압축할 수 있습니다.
git merge feature main
이렇게 하면 feature
브랜치에 두 브랜치의 기록을 연결하는 새로운 “병합 커밋”이 만들어져 다음과 같은 브랜치 구조를 얻을 수 있습니다.
병합은 비파괴적 운영이므로 적절합니다. 기존 브랜치는 어떤 방식으로든 변경되지 않습니다. 이는 기준 재지정의 잠재적인 모든 위험을 방지합니다(아래에서 논의됨).
반면에 feature
브랜치에는 업스트림 변경 사항을 통합해야 할 때마다 잘못된 병합 커밋이 있음을 의미하기도 합니다. main
이 매우 활동적인 경우 기능 브랜치 기록의 효율을 다소 떨어뜨릴 수 있습니다. 이 이슈를 고급 git log
옵션으로 완화할 수 있지만 다른 개발자가 프로젝트의 기록을 이해하기 어렵게 만들 수 있습니다.
The rebase option
병합하는 대신 다음 명령을 사용하여 feature
브랜치를 main
브랜치로 rebase할 수 있습니다.
git checkout feature
git rebase main
이것은 main
브랜치의 끝에서 시작하도록 전체 feature
브랜치를 이동하여 main
에서 모든 새 커밋을 통합합니다. 하지만 병합 커밋을 사용하는 대신 rebase는 원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어 프로젝트 기록을 다시 작성합니다.
기준 재지정의 주요 이점은 프로젝트 히스토리를 더 명확하게 얻을 수 있다는 것입니다. 먼저 git 병합
에서 필요로 하는 불필요한 병합 커밋을 제거합니다. 두 번째로 위의 다이어그램에서 볼 수 있듯이 기준 재지정을 통해 완벽한 선형 프로젝트 히스토리를 야기합니다.—포크 없이 프로젝트의 시작까지 기능
의 팁을 따를 수 있습니다. 이를 통해 git 로그
, git bisect
및 gitk
등의 명령으로 프로젝트를 더 쉽게 탐색할 수 있습니다.
하지만 이 원시 커밋 히스토리에 대한 안전 및 추적 가능성의 두 가지 균형이 있습니다. 기준 재지정의 황금률을 따르지 않는 경우 프로젝트 히스토리 재작성은 협업 워크플로우에 잠재적으로 치명적일 수 있습니다. 그리고 이보단 덜 중요하게 기준 재지정은 병합 커밋에서 제공하는 컨텍스트를 손상시킵니다.—업스트림 변경 사항이 기능에 통합된 를 볼 수 없습니다.
Interactive rebasing
대화형 rebase는 새 브랜치로 이동될 때 커밋을 변경할 기회를 제공합니다. 브랜치의 커밋 기록에 대한 전체적인 제어를 제공하므로 자동화된 rebase보다 훨씬 더 강력합니다. 일반적으로 기능 브랜치를 main
으로 병합하기 전에 복잡한 기록을 정리하는 데 사용됩니다.
대화형 rebase 세션을 시작하려면 다음과 같이 i
옵션을 git rebase
명령으로 전달합니다.
git checkout feature
git rebase -i main
이렇게 하면 다음과 같이 이동하려는 모든 커밋을 나열하는 텍스트 편집기가 열립니다.
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
이 목록은 rebase를 수행한 후 브랜치가 어떻게 보이는지 정확하게 정의합니다. pick
명령을 변경하거나 항목을 재정렬하여 브랜치의 기록을 원하는 대로 보이게 할 수 있습니다. 예를 들어, 두 번째 커밋이 첫 번째 커밋의 작은 문제를 해결한다면 다음과 같이 fixup
명령을 사용하여 두 커밋을 단일 커밋으로 축소할 수 있습니다.
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
파일을 저장하고 닫으면 Git이 설명에 따라 rebase를 수행하므로 다음과 같은 프로젝트 기록이 만들어집니다.
이와 같이 중요하지 않은 커밋을 제거하면 기능의 히스토리를 더 쉽게 이해할 수 있습니다. 이는 git 병합
이 간단하게 수행할 수 없는 항목입니다.
The golden rule of rebasing
기준 재지정에 대해 이해하고 나면 알아야 할 가장 중요한 것은 이를 수행하지 않는 경우입니다. git 기준 재지정
의 황금률은 공용 브랜치에서 절대 사용하지 않는 것입니다.
예를 들어, 다음과 같이 main
브랜치를 feature
브랜치로 rebase한 경우를 생각해보겠습니다.
rebase는 main
에 있는 모든 커밋을 feature
끝으로 옮깁니다. 문제는 사용자의 리포지토리에서만 이 작업이 이뤄진다는 것입니다. 다른 모든 개발자는 여전히 원래 main
을 사용하여 작업하고 있습니다. rebase를 사용하면 새로운 커밋이 생기기 때문에 Git은 main
브랜치의 기록이 다른 모든 개발자의 기록에서 분기했다고 여길 것입니다.
두 개의 main
브랜치를 동기화하는 유일한 방법은 함께 다시 병합하는 것이며 이로 인해 추가 병합 커밋 및 동일한 변경 사항을 포함하는 두 커밋 집합(원본 커밋 및 rebase된 브랜치의 커밋)이 발생합니다. 매우 혼란스러운 상황입니다.
이제 git rebase
를 실행하기 전에 항상 “다른 개발자가 이 브랜치를 보고 있는가?” 스스로에게 물어보세요. 대답이 '예'인 경우 키보드에서 손을 떼고 변경 작업을 수행하는 비파괴적인 방법(예: git revert
명령)을 생각해봐야 합니다. 그렇지 않으면 원하는 만큼 기록을 다시 쓰는 것이 안전합니다.
Force-pushing
rebase된 main
브랜치를 다시 원격 리포지토리로 푸시를 시도하는 경우 원격 main
브랜치와 충돌하므로 Git이 이 작업의 수행을 막습니다. 하지만 다음과 같이 --force
플래그를 전달하여 통과하도록 푸시를 강제로 시행할 수 있습니다.
# Be very careful with this command! git push --force
이렇게 하면 리포지토리에서 rebase된 항목과 일치하도록 원격 main
브랜치를 덮어쓰며 나머지 팀에서 매우 혼동을 느끼게 됩니다. 따라서 수행하는 작업이 무엇인지 정확하게 아는 경우에만 이 명령을 사용하도록 각별히 주의해야 합니다.
강제 푸시를 실행해야 하는 유일한 시점 중 하나는 개인 기능 브랜치를 원격 리포지토리로 푸시한 이후 로컬 정리를 수행한 경우입니다(예: 백업 목적). 이건 마치 "기능 브랜치의 원본 버전을 푸시할 생각이 없었어요. 대신 현재 항목을 가져가세요."라고 말하는 것과 습니다. 다시 말하지만, 기능 브랜치의 원본 버전에서 아무도 커밋을 제거하지 않는 것이 중요합니다.
Workflow walkthrough
rebase는 팀이 편하게 사용할 수 있는 정도에 따라 기존 Git 워크플로에 많게 또는 적게 통합할 수 있습니다. 이 섹션에서는 기능 개발의 다양한 단계에서 rebase가 제공할 수 있는 이점에 대해 살펴보겠습니다.
git rebase
를 활용하는 워크플로의 첫 번째 단계는 각 기능의 전용 브랜치를 만드는 것입니다. 이렇게 하면 rebase를 안전하게 활용하는 데 필요한 브랜치 구조가 만들어집니다.
Local cleanup
워크플로에 rebase를 통합하는 가장 좋은 방법은 진행 중인 로컬 기능을 정리하는 것입니다. 대화형 rebase를 주기적으로 수행하여 기능의 각 커밋에 중점을 두고 의미가 있는지 확인할 수 있습니다. 이렇게 하면 코드를 분리된 커밋으로 나눌 걱정 없이 작성할 수 있으며 추후에 수정할 수 있습니다.
git rebase
를 호출하는 경우 새 기반에 대한 두 가지 옵션이 있습니다. 즉, 기능의 상위 브랜치(예: main
) 또는 기능의 이전 커밋입니다. 대화형 rebase 섹션에서 첫 번째 옵션에 대한 예를 확인했습니다. 후자 옵션은 마지막 몇 개의 커밋을 수정해야 하는 경우에만 적합합니다. 예를 들어 다음 명령은 3개의 커밋에 대한 대화형 rebase만 시작합니다.
git checkout feature git rebase -i HEAD~3
HEAD~3
을 새 기준으로 지정하면 실제로 브랜치를 이동하지 않습니다.—따라오는 3개의 커밋을 대화형으로 재작성할 뿐입니다. 이는 업스트림 변경 사항을 기능 브랜치로 통합하지 않습니다
.
이 방법을 사용하여 전체 기능을 재작성하려는 경우 git 병합-기반
명령이 기능
브랜치의 원본 기반을 찾는 데 유용할 수 있습니다. 다음은 원본 기반의 커밋 ID를 반환하여 git 기준 재정의
에 전달할 수 있습니다.
git merge-base feature main
대화형 기준 재정의 사용은 로컬 브랜치에만 영향을 주므로 git 기준 재정의
를 워크플로우에 도입하는 탁월한 방법입니다. 다른 개발자가 확인하는 유일한 항목은 완료된 제품이며 명확하고 따르기 쉬운 기능 브랜치 히스토리여야 합니다.
하지만 이는 개인 기능 브랜치에만 적용됩니다. 동일한 기능 브랜치를 통해 다른 개발자와 협업 중인 경우 해당 브랜치는 공용이며 히스토리 재작성이 허용되지 않습니다.
로컬 커밋을 대화형 기준 재정의로 제거하기 위한 git 병합
대안이 없습니다.
Incorporating upstream changes into a feature
개념 개요 섹션에서는 기능 브랜치가 git merge
또는 git rebase
중 하나를 사용하여 main
의 업스트림 변경 사항을 통합하는 방법을 살펴보았습니다. 병합은 리포지토리의 전체 기록을 보존하는 안전한 옵션이며, rebase는 기능 브랜치를 main
끝으로 이동하여 선형 기록을 만듭니다.
이 git rebase
사용은 로컬 정리와 유사하며 동시에 수행할 수 있지만 프로세스에서 main
의 업스트림 커밋을 통합합니다.
main
대신 원격 브랜치에서 rebase하는 것은 완전히 합법적입니다. 또다른 개발자와 동일한 기능에 대해 공동 작업하는 경우 발생할 수 있으며 변경 사항을 리포지토리에 통합해야 합니다.
예를 들어, 자신과 John이라는 다른 개발자가 feature
브랜치에 커밋을 추가한 경우, John의 리포지토리에서 원격 feature
브랜치를 가져온 후 여러분의 리포지토리가 다음과 같이 표시될 수 있습니다.
main
에서 업스트림 변경 사항을 통합하는 방식과 완전히 동일하게 이 포크를 해결할 수 있습니다. 즉, 로컬 feature
를 john/feature
와 통합하거나, 로컬 feature
를 john/feature
의 끝에서 rebase합니다.
로컬 기능 커밋만 이동하기 때문에 rebase의 황금률
은 위반하지 않습니다. 이전의 모든 내용은 그대로 유지됩니다. "John이 이미 수행한 작업에 내 변경 사항을 추가해 주세요."라는 것과 같습니다. 대부분의 경우 이 작업이 병합 커밋을 통해 원격 브랜치와 동기화하는 것보다 더 직관적입니다.
기본적으로 git pull
명령은 병합을 수행하지만 --rebase
옵션을 전달하여 강제로 원격 브랜치를 rebase와 통합하도록 할 수 있습니다.
Reviewing a feature with a pull request
코드 검토 프로세스의 일부로 풀 리퀘스트를 사용하는 경우 풀 리퀘스트를 생성한 다음 git 기준 재정의
사용을 피해야 합니다. 풀 리퀘스트를 만드는 즉시 다른 개발자는 커밋을 보게 되며 이는 공용 브랜치임을 의미합니다. 히스토리를 재작성하면 Git 및 팀 구성원이 기능에 추가된 후속 커밋을 추적할 수 없습니다.
다른 개발자의 변경 사항은 git 기준 재정의
대신 git 병합
과 통합해야 합니다.
이러한 이유로 풀 리퀘스트를 제출하기 전에 대화형 기준 재정의로 코드를 정리하는 것은 일반적으로 좋은 생각입니다.
Integrating an approved feature
팀이 기능을 승인한 후 메인 코드 베이스로 기능을 통합하도록 사용자는 git merge
를 사용하기 전에 main
브랜치의 팁으로 기능을 rebase할 수 있습니다.
업스트림 변경 사항을 기능 브랜치로 통합하는 것과 유사한 상황이지만 main
브랜치에서 커밋을 다시 작성하도록 허용되지 않으므로 git merge
를 사용하여 기능을 통합해야 합니다. 그러나 병합 전에 rebase를 수행하면 빨리 감기 병합이 적용되어 완벽한 선형 기록이 됩니다. 또한 풀리퀘스트 동안 추가된 후속 커밋을 스쿼시할 수 있습니다.
git 기준
가 완전히 편하지 않다면 임시 브랜치에서 기준 재정의를 항상 수행할 수 있습니다. 이 방법으로 기능 히스토리를 우연히 복잡하게 만드는 경우 원본 브랜치를 확인하고 다시 시도할 수 있습니다. 예:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
요약
이것은 브랜치의 기준 재정의를 시작하는 데 알아야 한 모든 것입니다. 불필요한 병합 커밋 없이 명확한 선형 히스토리를 선호하는 경우 다른 브랜치에서 변경 사항을 통합할 때 git 병합
대신 git 기준 재정의
를 수행하려고 노력해야 합니다.
반면에 프로젝트의 전체 기록을 보존하고 공용 커밋을 다시 쓰는 위험을 피하려면 git merge
를 고수할 수 있습니다. 두 옵션 모두 완전히 유효하지만 적어도 이제 git rebase
의 이점을 활용할 수 있습니다.
이 문서 공유
다음 토픽
여러분께 도움을 드릴 자료를 추천합니다.
이러한 리소스에 책갈피를 지정하여 DevOps 팀의 유형에 대해 알아보거나 Atlassian에서 DevOps에 대한 지속적인 업데이트를 확인하세요.