ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Git] Branch 전략 - merge, rebase, squash란
    저장소/git 2022. 2. 27. 16:52

    git에서 브랜치를 합치는 전략으로 merge, rebase, squash가 있습니다.

    merge

    merge는 가장 간단한 병합 방법으로 크게 2가지의 방식이 많이 사용됩니다.

    Fast-forward

    develop 브랜치에서 test-child라는 브랜치를 생성했다고 가정합니다. test-child 브랜치에서 작업을 하고 서버에 푸시까지 완료가 됐다면 아래와 같이 develop 브랜치보다 커밋 포인터가 한칸 위로 올라가있는 것을 볼 수 있습니다.

    * commit af320b4bc8b555b23e1340db4604958992be9836 (HEAD -> test-child, origin/test-child)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:23:40 2022 +0900
    | 
    |     test
    | 
    * commit edff14eff1d5b506ff6f420df9beea9bed30db72 (origin/develop, develop)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 6 20:49:59 2022 +0900
    | 
    |     test1

    develop 브랜치를 활성화 하고 있는 상태에서 test-child 브랜치를 merge 하고자 하면 develop 브랜치의 위치가 test-child 브랜치로 이동하는 것을 확인할 수 있습니다.

    * commit af320b4bc8b555b23e1340db4604958992be9836 (HEAD -> develop, origin/test-child, origin/develop, test-child)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:23:40 2022 +0900
    | 
    |     test
    | 
    * commit edff14eff1d5b506ff6f420df9beea9bed30db72
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 6 20:49:59 2022 +0900
    | 
    |     test1

    이처럼 test-child 브랜치를 merge 할 때, 부모 브랜치인 develop에 변경사항이 없다면 develop 브랜치를 바로 test-child 브랜치로 이동시키는 것을 Fast-forward라 합니다.

    3-way Merge

    develop 브랜치 기반으로 test-child1 브랜치를 생성한 다음 test-child1 브랜치와 develop 브랜치 모두 변경 사항이 있다면 그래프는 아래와 같은 형태를 띕니다.

    * commit 1379080651fcb41c6ce06b5efba15d4929e13c4a (HEAD -> develop, origin/develop)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:43:38 2022 +0900
    | 
    |     develop commit 3
    |   
    | * commit 299c6e63bef9100f3c2c50aff0405ae052da2da7 (origin/test-child1, test-child1)
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 15:42:35 2022 +0900
    |   
    |       child test 1
    | 
    * commit b8280298dd62afeafa85f7f8f70eccfabdeb653b
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:35:29 2022 +0900
    | 
    |     develop 커밋2

    여기서 develop 브랜치를 활성화한 상태에서 test-child1 브랜치를 merge 하고자 하면 아래와 같이 브랜치가 합쳐지는 모습을 볼 수 있습니다.

    *   commit 2e07dd2b8749f29524fe3b2a8b779f490c2324f8 (HEAD -> develop, origin/develop)
    |\  Merge: 1379080 299c6e6
    | | Author: brownbear <brownbear@gmail.com>
    | | Date:   Sun Feb 27 15:47:32 2022 +0900
    | | 
    | |     Merge remote-tracking branch 'origin/test-child1' into develop
    | | 
    | * commit 299c6e63bef9100f3c2c50aff0405ae052da2da7 (origin/test-child1, test-child1)
    | | Author: brownbear <brownbear@gmail.com>
    | | Date:   Sun Feb 27 15:42:35 2022 +0900
    | | 
    | |     child test 1
    | | 
    * | commit 1379080651fcb41c6ce06b5efba15d4929e13c4a
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 15:43:38 2022 +0900
    |   
    |       develop commit 3

    이처럼 각 브랜치의 최신 커밋과 부모 브랜치 커밋을 비교하고 새로움 커밋을 만들어 merge 하는 전략입니다.

    merge는 변경 내용의 커밋 내역을 그대로 남기고 merge 시 merge comment를 새로 생성합니다.

    위의 예시에서는 부모 브랜치인 develop에서 test-child1 브랜치 1개만 분기가 되었지만 갈라지는 브랜치가 많아진다면 그래프 모양이 복잡해지므로 추적이 어려워 집니다. 이러한 문제를 해결하기 위해 rebase와 squash를 사용합니다.

    rebase

    rebase는 base 브랜치로 합치고자 하는 브랜치의 커밋 내용을 추가하여 그래프 모양을 단순화시킵니다.

    * commit 688e4eb009079fc19c06fbe7364b87bc9b5d63bb (HEAD -> develop, origin/develop)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:55:10 2022 +0900
    | 
    |     base 커밋
    |   
    | * commit 58d327152bab01f0bd57ad449e872a9b77551eee (origin/test-child2, test-child2)
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 15:54:28 2022 +0900
    |   
    |       child 커밋2
    |   
    *   commit 2e07dd2b8749f29524fe3b2a8b779f490c2324f8

    위와 같이 develop 브랜치에서 test-child2 브랜치가 갈라지고 develop, test-child2 브랜치 모두 변경사항이 있다고 가정합니다.

    여기서 develop 브랜치를 base로 두고 test-child2 브랜치를 rebase하고자 하면 아래와 같이 develop브랜치의 커밋으로 추가가 됩니다.

    $ git checkout test-child2
    $ git rebase develop
    $ git log --graph --all
    
    * commit 117abc9e297a63dbbf44fbd9410c2bdaf0c895a7 (HEAD -> test-child2)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:54:28 2022 +0900
    | 
    |     child 커밋2
    | 
    * commit 688e4eb009079fc19c06fbe7364b87bc9b5d63bb (origin/develop, develop)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:55:10 2022 +0900
    | 
    |     base 커밋
    |   
    | * commit 58d327152bab01f0bd57ad449e872a9b77551eee (origin/test-child2)
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 15:54:28 2022 +0900
    |   
    |       child 커밋2
    |   
    *   commit 2e07dd2b8749f29524fe3b2a8b779f490c2324f8

    rebase를 실행하면 base가 되는 develop 브랜치에 test-child2 커밋 내역을 merge 한 것이 아니기 때문에 develop 브랜치에서 test-child2 브랜치를 Fast-forward merge해야 합니다.

    $ git checkout develop
    $ git merge test-child2
    $ git push -u origin develop
    $ git log --graph --all
    
    * commit 117abc9e297a63dbbf44fbd9410c2bdaf0c895a7 (HEAD -> develop, origin/develop, test-child2)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:54:28 2022 +0900
    | 
    |     child 커밋2
    | 
    * commit 688e4eb009079fc19c06fbe7364b87bc9b5d63bb
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 15:55:10 2022 +0900
    | 
    |     base 커밋
    |   
    | * commit 58d327152bab01f0bd57ad449e872a9b77551eee (origin/test-child2)
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 15:54:28 2022 +0900
    |   
    |       child 커밋2
    |   
    *   commit 2e07dd2b8749f29524fe3b2a8b779f490c2324f8

    이것처럼 rebase를 실행하면 merge와 다르게 merge commit이 생성되지 않고 1개의 브랜치에서 작업한 것처럼 그래프가 보이므로 히스토리를 간결하게 유지할 수 있습니다.

    하지만 test-child2 브랜치의 커밋 내역이 너무 많다면 지저분해 보이게 되는데 이를 해결하기 위해선 squash를 진행해야 합니다.

    squash

    squash는 여러개의 commit을 1개의 commit으로 만들어 줍니다.

    * commit f200fda3c965f3e3220bde225721c3a945e65cda (HEAD -> develop, origin/develop)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 16:07:45 2022 +0900
    | 
    |     develop 커밋 포인터 이동
    |   
    | * commit 4e589b13846fdb2888ee45cd5aa19a25818048cc (origin/test-child3, test-child3)
    | | Author: brownbear <brownbear@gmail.com>
    | | Date:   Sun Feb 27 16:07:08 2022 +0900
    | | 
    | |     커밋3
    | | 
    | * commit 84b1c7eea3c11e7ec3ea2bd305af5b82cf8c4e8d
    | | Author: brownbear <brownbear@gmail.com>
    | | Date:   Sun Feb 27 16:06:57 2022 +0900
    | | 
    | |     커밋2
    | | 
    | * commit bc2e971a2292fa5375e1bc0e909a3a0ea3f659c8
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 16:06:39 2022 +0900
    |   
    |       커밋1
    | 
    * commit 117abc9e297a63dbbf44fbd9410c2bdaf0c895a7 (test-child2)

    위와 같이 develop 브랜치 기반으로 test-child3 브랜치가 생성 후, 커밋1, 커밋2, 커밋3 과 같이 여러 커밋이 추가되었다고 가정합니다. 여기서 커밋1, 커밋2, 커밋3을 합쳐보도록 합니다.

    $ git checkout test-child3
    $ git rebase -i 117abc9e297a63dbbf44fbd9410c2bdaf0c895a7
    
    pick bc2e971 커밋1
    pick 84b1c7e 커밋2
    pick 4e589b1 커밋3
    
    # Rebase 117abc9..4e589b1 onto 117abc9 (3 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
    #                    commit's log message, unless -C is used, in which case
    #                    keep only this commit's message; -c is same as -C but
    #                    opens the editor
    # x, exec <command> = run command (the rest of the line) using shell
    # b, break = stop here (continue rebase later with 'git rebase --continue')
    # d, drop <commit> = remove commit
    # l, label <label> = label current HEAD with a name
    # t, reset <label> = reset HEAD to a label
    # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
    # .       create a merge commit using the original merge commit's
    # .       message (or the oneline, if no original merge commit was
    # .       specified); use -c <commit> to reword the commit message
    #
    # These lines can be re-ordered; they are executed from top to bottom.

    git rebase -i 커밋ID 를 진행하면 위와 같은 화면을 볼 수 있습니다. s 또는 squash 옵션을 사용해 commit을 합칠 수 있고 pick으로 선택한 커밋을 사용할 수도 있습니다.

    여기서는 커밋1 을 선택하고 나머지는 합치도록 해봅니다.

    pick bc2e971 커밋1
    s 84b1c7e 커밋2
    s 4e589b1 커밋3

    저장을 하면 다음 화면을 볼 수 있습니다.

    # 커밋 3개가 섞인 결과입니다.
    # 1번째 커밋 메시지입니다:
    
    커밋1
    
    # 커밋 메시지 #2번입니다:
    
    커밋2
    
    # 커밋 메시지 #3번입니다:
    
    커밋3
    
    # 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
    # 줄은 무시되고, 메시지를 입력하지 않으면 커밋이 중지됩니다.
    #
    # 시각:      Sun Feb 27 16:06:39 2022 +0900
    #
    # 대화형 리베이스 진행 중. 갈 위치는 117abc9
    # 최근 완료한 명령 (3개 명령 완료):
    #    squash 84b1c7e 커밋2
    #    squash 4e589b1 커밋3
    # 명령이 남아있지 않음.
    # 현재 'test-child3' 브랜치를 '117abc9' 위로 리베이스하는 중입니다.
    #
    # 커밋할 변경 사항:
    #       수정함:        tests/test_pytest.py
    #
    # 추적하지 않는 파일:

    여기서 커밋2, 커밋3은 주석처리 후, 커밋1의 내용을 아래와 같이 변경하고 저장합니다.

    # 커밋 3개가 섞인 결과입니다.
    # 1번째 커밋 메시지입니다:
    
    커밋1, 커밋2, 커밋3 스쿼시 
    
    # 커밋 메시지 #2번입니다:
    
    # 커밋2
    
    # 커밋 메시지 #3번입니다:
    
    # 커밋3

    저장을 했다면 아래와 같이 head와 origin이 갈라지는 것을 볼 수 있습니다.

    여기서 강제 push를 통해 이전 커밋 내용을 제거해줍니다.

    $ git push -f origin test-child3
    $ git log --graph --all
    
    * commit 1815a6d474254ea89cb0da832e732968f70c7e47 (HEAD -> test-child3, origin/test-child3)
    | Author: brownbear <brownbear@gmail.com>
    | Date:   Sun Feb 27 16:06:39 2022 +0900
    | 
    |     커밋1, 커밋2, 커밋3 스쿼시
    |   
    | * commit f200fda3c965f3e3220bde225721c3a945e65cda (origin/develop, develop)
    |/  Author: brownbear <brownbear@gmail.com>
    |   Date:   Sun Feb 27 16:07:45 2022 +0900
    |   
    |       develop 커밋 포인터 이동
    | 
    * commit 117abc9e297a63dbbf44fbd9410c2bdaf0c895a7 (test-child2)

    댓글