-
[Git] Branch / Merge ( Fast-Forward, 3-Way-Merge ) / Rebase 의미 와 사용법SCM & Build & Deploy/Git 2024. 12. 24. 15:21
📌 목차
◦ Branch 란 ?
◦ 서로 다른 브랜치를 병합하는 방법 : Merge ( Fast-Forward / 3-Way-Merge )
◦ Merge 와 Rebase 의 차이 : Rebase 란 ?
📌 Branch 란
Branch 의 개념을 이해하기 전 Git 의 데이터 저장 방식에 대해 알아두면 이해가 더 쉽다.
Git 은 데이터를 Change Set 이나 Diff (변경사항) 으로 기록하지 않고,
일련의 SnapShot ( 특정 시점에서 파일, 폴더 또는 워크스페이스의 상태를 의미 ) 으로 기록한다.
변경 내용만 기록하는 것이 하닌 그 시점의 전체적인 상태를 기록하는 것이다.
델타 / 스냅샷 방식 차이 커밋 했을 때, Git 은 현재 Staging Area 에 있는
데이터의 스냅샷에 대한 포인터, 저자, 커밋 메시지와 같은 메타 데이터, 이전 커밋에 대한 포인터 등을 포함하는 커밋 Object 를 저장한다.
커밋에 따른 Snapshot git add Test git commit -m 'The initial commit of my project'
커밋을 했을 때, 루트 디렉토리와 각 하위 디렉토리의 트리 개체를 체크섬과 함께 저장소에 저장한다.
그 다음 커밋 개체를 만들고 메타 데이터와 루트 디렉토리 트리 개체를 가리키는 포인터 정보를 커밋 개체에 넣어 저장한다.
그리하여 필요하면 언제든지 스냅샷을 다시 만들 수 있는 것이다.
위의 작업 이후에 Git 저장소에는 다섯 개의 데이터 개체가 생성된다.
각 파일에 대한 Blob 3개, 파일과 디렉토리 구조가 들어 있는 트리 개체 1개,
메타데이터와 루트 트리를 가르키는 포인터가 담긴 커밋 개체 1개이다.
다시 파일을 수정하고 커밋하면 이전 커밋이 무엇인지도 저장을 하게 된다.
이미지의 Snapshot A, B, C 를 보면 각 이전 커밋 id 를 parent 항목에 담고 있다.
Git 의 Branch 는 커밋 사이를 자유롭게 이동할 수 있는 포인터와 같은 역할을 한다.
기본적으로 Git 은 Default Branch 로 Master 브랜치를 생성하는데,
다른 설정이 없다면 Master Branch 는 자동으로 가장 마지막 커밋을 가리킨다.
⚡ 새로운 브랜치 생성하기 : branch
git branch [branch명] -- 예시 git branch testing
새 브랜치 만들기 위와 같은 명령어로 새로운 브랜치를 생성한다면,
[ testing ] 브랜치는 최신의 마지막 커밋을 가리킨다.
지금 작업 중인 브랜치가 무엇인지, Git 은 'HEAD' 라는 특수한 포인터를 통해 파악을 한다.
이 포인터는 지금 작업하는 로컬 브랜치를 가리킨다.
[ testing ] 이라는 브랜치를 새로 만들었지만, 아직 Git 은 [ master ] 브랜치를 가리키고 있다.
git log --oneline --decorate -- 결과 f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface 34ac2 Fixed bug #1328 - stack overflow under certain conditions 98ca9 The initial commit of my project
git log 명령에 --decorate 옵션을 사용하면 브랜치가 어떤 커밋을 가리키는지 확인 할 수 있다.
[ master ] 와 [ testing ] 두 브랜치가 f30ab 커밋 옆에 위치하여 커밋을 가르키는 것을 확인 할 수있다.
⚡ 브랜치 이동하기 : checkout
git checkout testing
브랜치 이동하기 위와 같이 checkout 을 사용할 경우,
그림과 같이 HEAD 가 testing 을 가리키게 된다.
그 후에 커밋을 하게 되면 아래와 같이 새로운 커밋을 가리키게 된다.
87ab2 커밋이 새로 생기고 testing 브랜치를 통해 HEAD 가 가리키는 것이다.
vim test.rb git commit -a -m 'made a change'
다시 master 브랜치로 돌아오고 싶다면 동일한 방법으로 체크아웃하면 된다.
그 이후 master 브랜치에서 작업을 하고 커밋을 하게 되면 아래 그림과 같이 히스토리가 분리되는데,
이후 커밋 사이에서 자유롭게 작업하다가 병합이 필요한 시점에 Merge 한다.
실제로 Git 의 브랜치는 어떤 한 커밋을 가리키는 40글자의 SHA-1 체크섬 파일에 불과하기 때문에 만들고 지우기 쉽다.
새로 브랜치를 하나 만드는 것은 41Byte 크기의 파일을 (40자와 줄 바꿈 문자) 만드는 것에 불과하다.
다른 버전 관리 도구와 다르게 프로젝트를 통째로 복사할 필요가 없어 더 효율적일 수 있다.
📌 서로 다른 브랜치를 병합하는 방법 : Merge ( Fast-Forward / 3-Way-Merge )
실제로 새로운 브랜치를 만들어서 Merge 하는 예시는 아래와 같다.
1. 작업을 진행하는 사이트가 있다.
2. 이슈가 발생하여 해당 이슈에 대한 브랜치를 생성 후 해결하고자 한다.
3. 새로 만든 이슈 해결 브랜치에서 작업 후 검증을 확인한 뒤에 마스터에 병합을 한다.
[ master ] 브랜치에서 작업을 하다가 이슈가 발생하여
[ iss53 ] 브랜치를 생성하고 해당 브랜치에서 해결을 하려고 한다.
[ iss53 ] 브랜치로 checkout 하고 ( HEAD 는 iss53 브랜치를 가리킨다 )
이슈에 대한 작업을 진행한 뒤 커밋을 하면 C3 과 같은 히스토리가 생성된다.
이 떄, 만약 [ iss53 ] 브랜치 외에 다른 급한 버그를 해결하기 위한 [ Hotfix ] 브랜치를 새로 만들려고 한다.
그 때는 원래 운영 환경의 소스로 복구한 뒤 다시 새로운 작업을 해야하는데, 단순하게 [ master ] 브랜치로 돌아가면 된다.
만약 [ iss53 ] 브랜치에서 수정한 소스가 있다면 Checkout 할 브랜치와 충돌이 발생하므로 워킹 디렉토리를 정리해야 한다.
Stash, Commit Ammend 를 통해 Stashing, Cleaning 작업을 하고 [ master ] 브랜치로 돌아간다.
git checkout -b hotfix ( -b : 생성과 동시에 checkout ) ## 결과 Switched to a new branch 'hotfix' vim index.html git commit -a -m 'fixed the broken email address' ## 결과 [hotfix 1fb7853] fixed the broken email address 1 file changed, 2 insertions(+)
위의 명령을 통해 [ hotfix ] 브랜치가 생성되고 새로운 작업에 대한 커밋 C4 가 만들어진다.
수정한 내용으로 발생한 이슈가 해결되었다면,
[ master ] 브랜치에서 갈라져 나온 [ hotfix ] 브랜치를 운영 환경에 적용하기 위해 병합을 해야한다.
git checkout master git merge hotfix #결과 Updating f42c576..3a0874c Fast-forward index.html | 2 ++ 1 file changed, 2 insertions(+)
[ master ] 브랜치로 checkout 하고 [ hotfix ] 브랜치를 병합했다.
위의 그림과 같이 [ master ] 와 [ hotfix ] 는 C4 커밋을 가리키게 된다.
C4 커밋이 C2 커밋에 기반한 브랜치이기 때문에 브랜치 포인터는 Merge 과정 없이 그저 최신 커밋으로 이동한다.
이때 명령 결과를 보게 되면 Fast-Forward 라고 나오는데, 이 Merge 방식을 Fast-Forward 라고 부른다.
작업이 완료된 [ hotfix ] 브랜치는 삭제하고,
다시 진행중이던 [ iss53 ] 에서 추가적인 작업을 하고 C5 커밋을 진행했을 떄,
어떻게 병합이 되는지 알아보자.
지금의 상태에서 보면 C4 는 [ master ] 에 [ hotifx ] 가 merge 되었으며,
[ iss53 ] 에는 [ hotfix ] 가 포함되어 있지 않다.
[ hotix ] 가 포함된 [ master ] 를 [ iss53 ] 에 merge 하던가,
[ iss53 ] 의 작업을 완료한 뒤 [ master ] 에 merge 하여 병합하는 방법이 있다.
git checkout master # 결과 Switched to branch 'master' git merge iss53 # 결과 Merge made by the 'recursive' strategy. index.html | 1 + 1 file changed, 1 insertion(+)
위의 명령에 대한 결과를 보면 이전에 했던 Fast-Forward 와는 결과가 다른 것을 알 수 있다.
[ hotfix ] 를 merge 했을 때와 다른게,
현재 브랜치가 가리키는 커밋이 Merge 할 브랜치의 조상이 아니므로 Fast-Forward 방식을 사용하지 않는다.
이 경우에는 각 브랜치가 가리키는 두 커밋( C4, C5 )과 공통 조상( C2 )을 사용해 3-Way Merge 를 하게 된다.
단순하게 브랜치 포인터를 최신 커밋으로 옮기는 것이 아니라 3-Way Merge 의 결과를 별도의 커밋( C6 )으로 만들고,
해당 브랜치가 그 커밋을 가리키도록 이동시키는 것이다. 그리하여 Merge 커밋이라고도 부른다.
⚡ Confilct : 충돌
3-Way Merge 를 사용할 떄, 실패하는 경우도 있다.
Merge 하는 두 브랜치가 같은 파일의 부분을 수정한 경우 아래와 같이 충돌이 발생해 실패한다.
git merge iss53 # 결과 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.
Git 은 자동으로 Merge 하지 못하여 새로운 커밋이 생성되지 않고,
사용자가 직접 정리를 하여 재커밋 및 병합을 진행하여야 한다.
<<<<<<< HEAD:index.html <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53:index.html
위의 코드를 보면 상단은 HEAD 버전 [ master ], 하단은 [ iss53 ] 의 소스가 표시되고,
올바른 소스를 제외하고 삭제한 뒤 재진행 하면 된다.
⚡ git branch 명령 옵션
git branch 브랜치 목록을 보여준다.
* 가 붙어있는 브랜치가 현재 Checkout 하여 작업중인 브랜치를 표시한다.git branch -v 브랜치 목록을 보여주되, 마지막 커밋 메시지도 함께 보여준다. git branch --merged 현재 Checkout 작업 중인 브랜치를 기준으로 Merge 된 브랜치를 필터링할 수 있다.
* 가 붙지 않은 브랜치는 -d 옵션으로 삭제가 가능하다. ( 이미 다른 브랜치와 Merge 했기 때문에 )git branch --no-merged 현재 Checkout 작업 중인 브랜치를 기준으로 Merge 하지 않은 브랜치를 조회한다.
아직 Merge 하지 않은 커밋을 담고 있기 때문에 -d 명령으로 삭제가 불가능하다.git branch -d [ branch명 ] 브랜치 삭제 명령으로 강제 삭제가 필요한 경우 -D 옵션을 사용한다. 📌 Git Rebase 란 ?
Git 에는 서로 다른 브랜치를 합치기 위한 방법으로 Merge 와 Rebase 가 있다.
⚡ Merge 를 통한 브랜치 합치기
C2 에서 C3 / C4 로 생성된 커밋을 C5 로 합치는 3-Way Merge 방식이다.
3-Way Merge ⚡ Rebase 를 통한 브랜치 합치기
git checkout experiment git rebase master # 결과 First, rewinding head to replay your work on top of it... Applying: added staged command
Rebase 3-Way Merge 와 비슷한 결과를 얻는 방식으로 Rebase 가 있다.
Rebase 는 C3 커밋의 변경된 사항을 Patch 로 만들고 이를 다시 C4 에 적용시켜 C3 의 앞쪽에 커밋을 새로이 생성하는 방식이다.
** Pathch 는 리모트 저장소에서 받아오는 fetch 와 다른 의미로,
특정 커밋의 변경 사항을 파일로 저장하여 다른 저장소에 적용할 수 있도록 하는 유용한 기능
git checkout master git merge experiment
이후 [ master ] 로 이동하여 [ experiment ] 를 Merge 하게 되면 Fast-Forward 로 이어지는 것이다.
Merge 나 Rebase 모두 병합한다는 역할은 동일하지만,
Rebase 를 사용할 경우 조금 더 직관적인 히스토리를 만들 수 있다는 장점이 있다.
여러 작업자가 병렬로 동시에 진행하더라도 Rebase 는 모든 작업이 차례대로 진행된 것처럼 보인다.
++ 이후 추가 할 내용 > rebase onto 옵션, rebase 의 위험성 및 단점
📌 Reference
@ Git 공식 문서