git은 브랜치를 합치기 위해, merge와 rebase가 있다.
일반적으로 다음과 같이 브랜치와 커밋이 있다고 하고 develop 브랜치를 master 브랜치로 merge한다고 가정하자. 이 경우, 별도의 커밋(merge commit)을 만들고나서 해당 브랜치들이 커밋을 가리키도록 한다. 이런 머지 커밋의 부모커밋은 두가지가 된다.
이 때 같은 부분이 수정되었을 경우, 충돌이 발생할 수 있으며 적절히 해결해야 한다.
일반적으로 머지했다는 이력과 각 브랜치의 커밋이력이 모두 필요한 경우 사용한다. master 브랜치에 release 브랜치를 머지한다면 release 브랜치가 머지된 내역이 남아 있고, 어떤 부분들이 release되었는지 볼 수 있다면 좋을 것 같으므로 사용하면 좋을 것 같다.
git-merge를 사용할 경우 옵션 없이 위와 같은 브랜치와 커밋 상황이라면 3-way-merge가 발생한다. 또한 pull request에서 create merge commit
을 통해 머지 하는 것과 같다.
하지만 위처럼 develop브랜치가 master브랜치를 포함하고 있을 때 git-merge를 이용할 경우, fast forward merge가 이루어져 아래와 같아진다. fast-forward merge를 원하지 않는 경우, --no-ff
옵션을 통해, merge commit이 생성되도록 할 수 있다.
위와 같은 상황에서 develop브랜치를 master브랜치 squash merge 한다면, 아래와 같은 결과를 얻게 된다. 아래 처럼 develop 브랜치가 가지고 있던 c3, c4의 변경 내역이 master 브랜치의 새로운 커밋(c5)으로 추가되는 것을 의미한다.
git-merge를 사용해 다음과 같이 squash merge를 진행할 수 있다.
# develop 브랜치의 변경 내역을 staged area에 추가
master > git merge develop --squash
# 커밋하여 새로운 커밋이력 생성(c5 생성)
master > git commit -m "new feature....."
또한 pull request에서 squash and merge를 선택하여 위와 같은 결과를 얻을 수 있다.
머지하는 브랜치의 커밋로그가 이어지지 않기 때문에 feature 브랜치의 개발사항을 develop 브랜치에 추가하는 경우 사용하면 feature 브랜치의 커밋이력이 남지 않기 때문에 깔끔하게 커밋이력을 볼 수 있다.
위 와같은 상황에서 master브랜치로 develop브랜치를 rebase merge를 하는 경우, 아래와 같이 커밋 이력이 생성되는것을 알 수 있다. master브랜치와 develop브랜치의 base(공통되는 이전 커밋)는 c1커밋이다. 즉 git-rebase를 통해 master> git rebase develop
을 실행하는 경우, 'master 브랜치의 base를 develop으로 설정하겠다' 즉, c2커밋의 base를 develop 브랜치가 현재 가리키고 있는 곳으로 바꾸겠다는 것을 의미한다. 이 때, 단순히 커밋이 이동하는 것이 아닌 새로운 커밋이 생성되게 된다.(실제로 c2를 c5이후의 커밋으로 만들어 버저닝을 하고, c3 커밋을 c2 커밋이 새롭게 포함된 버전에 추가해 버저닝이 이루어진다. -> commit hash가 변경된다.)
즉, 반대로 develope 브랜치에서 git rebase master
를 실행한다면 develope브랜치의 변경사항이 master브랜치 뒤에 추가된다. 이를 rebase merge라 한다. develop에서 master로 가는 pull request에서도 rebase and merge를 설정하는 경우 동일하게 동작한다.
--onto
옵션을 통해 다른 브랜치에서 파생된 브랜치내용만 현재 브랜치로 가져오는 것도 가능하다.
추가적으로 git-rebase에는 -i 옵션이 있다.
rebase되려는 커밋목록을 만드는 옵션으로 커밋을 합칠 때 사용할 수 있다.
git rebase -i HEAD~2
를 입력하면 아래처럼 HEAD에서 2개의 rebase되려는 커밋목록이 나오게 되는데 아래 커맨드 옵션에 따라 선택해주면된다. (맨위 커밋을 두고 아래 커밋들의 pick을 s로 변경하면 해당 커밋들을 하나의 커밋으로 새롭게 만든다.)
rebase merge는 사용경험이 없어 어느시점에 사용해야할지 명확하게 감이오질 않는다. feature브랜치에서 개발 중, feature가 규모가 커 작게 브랜치 별로 나누어 제작할 때 사용하여 제작후 feature로 로컬에서 합칠 때 사용하면 좋을지도 모르겠다.
또는 메인 브랜치의 커밋을 깔끔하게 적용하고 싶을 때 사용할 수 있을 것 같다. git flow에 따라 feature브랜치에서 작업한 커밋들을 develop 브랜치에 squash 머지하게 되면, 개발된 기능들이 하나의 커밋으로 쌓이게 될 것 이다. 이 후, develop 브랜치에서 release브랜치로 그리고 release 브랜치에서 테스트 이후 master 브랜치로 머지 될 때, rebase merge를 사용한다면, master 브랜치의 커밋은 깔끔하게 한줄로 관리될 것 이다.
이미 원격 저장소에 push되어 있는 커밋을 rebase하여 다시 push하게 되면, 새로운 커밋이 추가되게 된다. 그러므로 함께 사용중인 다른 사용자는 다시 머지해야하고, 머지한 내용을 다시 pull할 경우, 커밋 히스토리가 꼬이게 될 것이다.