1. Base commit
  2. 병합이란?
  3. 리베이스란?
  4. 병합과 리베이스의 차이점
  5. 체리픽이란?
  6. 충돌과 해결 방법

Base commit

Reference of base commit
  • Base 커밋이란 한국어로 공통 조상 커밋이라고 표현할 수 있으며 두 개 이상의 브랜치나 커밋이 공유하는 최신 공통 커밋을 의미한다. 주로 병합 및 리베이스의 기준 역할을 한다.

병합이란?

Base 커밋을 기준으로 현재 작업 중인 브랜치에 지정된 브랜치의 커밋을 모두 통합하는 것이다. 이러한 병합에는 두 가지 방식이 존재한다.

Fast-forward 병합
Reference of Fast-forward merge
  • 위의 그림과 같이 현재 작업 중인 브랜치의 최신 커밋이 병합을 지정한 브랜치의 시작 커밋과 연결되어있을 경우 브랜치가 분기되어 있지만 마치 순차적으로 생성된 커밋의 흐름처럼 보인다. 이런 경우엔 새로운 병합 커밋을 생성하지 않고 현재 작업 중인 브랜치의 HEAD를 병합을 지정한 브랜치의 최신 커밋으로 이동만 시키는데 이러한 병합 방식을 Fast-forward 병합이라고 한다.
  • 일반적으로 혼자 프로젝트를 진행할 경우 접하게 되는 병합 방식이다.


3-way 병합
Reference of Three-way merge
  • 위의 그림과 같이 base 커밋을 기준으로 세 종류의 서로 다른 커밋들을 현재 작업 중인 브랜치에 새로운 하나의 커밋으로 병합하는 방식을 3-way 병합이라고 한다.
  • 다수의 개발자와 협업하여 프로젝트를 진행할 경우 접하게 되는 병합 방식이다.
  • 새로운 병합 커밋은 두 개의 부모 커밋을 가지며, 커밋을 생성할 때 언제나 그랬듯이 병합 커밋을 생성할 때도 커밋 메시지를 작성해야한다.


병합 명령어
git merge <파생 브랜치 이름>
  • merge 명령어는 현재 작업 중인 원본 브랜치를 기준으로 지정된 파생 브랜치의 커밋을 병합하여 가져오는 것이다. 그러므로 명령어 사용 시 대상이 되는 원본 브랜치로 switch 명령어로 이동 후 merge 명령어에 파생 브랜치 이름을 입력하여 실행한다.

  • 명령어 실행 전 Git log before git command merge

  • 명령어 실행 Git command to merge target branch into working branch

  • 명령어 실행 후 Git log after git command merge


병합 진행 옵션
git merge --continue
  • 현재 진행 중인 병합을 계속 진행하는 옵션으로 충돌 등이 발생했을 때 문제 해결 후 병합을 계속 진행하고 싶을 경우 사용할 수 있다. Git command to continue merging


병합 중지 옵션
git merge --abort
  • 현재 진행 중인 병합을 중지하는 옵션으로 충돌 등이 발생했을 때 병합을 중지하고 싶을 경우 사용할 수 있다. Git command to abort merging

리베이스란?

말 그대로 base 커밋을 다시 설정하는 것으로 지정된 브랜치의 base 커밋을 최신 커밋으로 이동시키는데, 과거 base 커밋과 연결되어 있던 현재 작업 중인 브랜치 또한 이동한 새로운 base 커밋으로 연결된다. 또한 현재 작업 중인 브랜치의 이동한 커밋들의 해시값모두 변경된다. Interactive 옵션을 사용 시 커밋의 순서와 위치까지도 변경할 수 있으며 이는 저장소가 공개된 경우 다른 개발자들과의 협업 시 혼돈을 야기시킬 수 있기 때문에 사용에 유의해야한다.

Reference of rebase


리베이스 명령어
git rebase <목적 브랜치 이름>
  • rebase 명령어는 현재 작업 중인 브랜치를 기준으로 지정된 목적 브랜치를 리베이스한다. 이것은 현재 작업 중인 브랜치의 커밋을 목적 브랜치의 최신 커밋 위에 다시 적용하는 것을 의미한다. 따라서 명령어 사용 시 목적 브랜치에 적용하고자 하는 브랜치로 switch 명령어로 이동 후 rebase 명령어에 목적 브랜치 이름을 입력하여 실행한다.

  • 명령어 실행 전 Git log before git command rebase

  • 명령어 실행 Git command to rebase target branch to working branch

  • 명령어 실행 후 Git log after git command rebase


범위 지정 리베이스
git rebase <목적 브랜치 이름> <리베이스 기준점>
  • 리베이스 기준점에 입력되는 특정 범위의 커밋에 목적 브랜치를 리베이스할 수 있다. 리베이스 기준점에 브랜치 이름을 입력할 경우 현재 작업 중인 브랜치와 관계없이 rebase 명령을 수행할 수 있다. 따라서 브랜치를 이동할 필요 없이 rebase를 실행할 수 있다. 리베이스 기준점에는 아래의 커밋 범위에 해당하는 요소들이 들어갈 수 있다.
  • 커밋 범위
    • 브랜치 이름: 특정 브랜치의 최신 커밋을 참조할 수 있다.
    • 태그명: 특정 태그가 가리키는 커밋을 지정할 수 있다.
    • 상대 참조: 상대 참조를 사용하여 현재 위치에서 상대적인 커밋을 가리킬 수 있다. HEAD, HEAD~, HEAD^와 같은 상대 참조를 사용할 수 있다.
    • SHA-1 해시값: 각 커밋에 대해 고유한 해시값을 사용하여 특정 커밋을 지정할 수 있다. Git command to rebase target branch to specified scope


git rebase --onto <목적 브랜치 이름> <리베이스 기준점> <커밋 범위>
  • 다른 기준점에 특정 범위의 커밋목적 브랜치를 리베이스할 경우에 사용하는 옵션으로 예를 들어 리베이스 기준점에 브랜치 이름을 입력하고 커밋 범위를 지정하여 현재 작업 중인 브랜치가 아닌 다른 브랜치의 특정 범위의 커밋에 목적 브랜치를 리베이스 하도록 명령할 수 있다. 즉, 좀 더 자세한 특정 커밋들을 리베이스 하기 위한 옵션이다.

  • 명령어 실행 전 Git log before git command rebase --onto

  • 명령어 실행 Git command to rebase target branch to rebase starting point within commit scope

  • 명령어 실행 후 Git log after git command rebase --onto


리베이스 진행 옵션
git rebase --continue
  • 현재 진행 중인 리베이스를 계속 진행하는 옵션으로 충돌 등이 발생했을 때 문제 해결 후 리베이스를 계속 진행하고 싶을 경우 사용할 수 있다. Git command to continue rebasing


리베이스 건너뛰기 옵션
git rebase --skip
  • 리베이스 중 충돌 등이 발생한 경우 병합과 다르게 각 커밋별로 문제를 해결해야 한다. 문제 해결 중에 특정 커밋의 충돌을 건너뛰고 리베이스를 진행하고 싶을 경우 사용하는 옵션이다. Git command to skip rebasing


리베이스 중지 옵션
git rebase --abort
  • 현재 진행 중인 리베이스를 중지하는 옵션으로 충돌 등이 발생했을 때 리베이스를 중지하고 싶을 경우 사용할 수 있다. Git command to abort rebasing


대화형 리베이스
git rebase -i <커밋 범위>
git rebase --interactive <커밋 범위>
  • 사용자가 직접 커밋의 목록을 수정하여 리베이스를 실행하는 옵션이다. 실행 시 에디터가 열리고 사용자가 수동으로 명령어를 사용하여 커밋 분할, 합치기, 재배치 등이 가능하고 커밋 메시지 또한 수정할 수 있다.
  • 다양한 옵션이 존재하나 몇 가지만 간단히 알아보자.
    • pick: 해당 커밋을 그대로 적용한다.
    • edit: 해당 커밋을 적용하고, 그 이후의 커밋들을 수정할 수 있도록 중단한다.
    • squash: 해당 커밋을 이전 커밋과 합친다.
    • exec: 해당 커밋을 적용하기 전에 지정된 명령을 실행한다.
    • drop: 해당 커밋을 건너뛴다. Git command to rebase interactively

병합과 리베이스의 차이점

병합은 내부적으로 현재 작업 중인 브랜치와 지정된 브랜치의 각 커밋들을 순차적으로 비교(그림 1, 2번 과정)하여 최종적으로 병합 커밋을 생성한다.(그림 3번 과정) 즉, 충돌이 발생할 경우 모든 커밋의 비교가 완료된 후에 한 번에 충돌을 해결하고 병합 커밋을 생성한다.

Flow of merge to compare with rebase


리베이스는 두 브랜치를 비교하지 않으며 현재 작업 중인 브랜치의 각 커밋을 순차적으로 목적 브랜치에 병합 시도한다.(그림 1, 2번 과정) 즉, 충돌이 발생할 경우 각 커밋을 병합할 때마다 충돌을 해결하여 커밋을 생성하고 rebase를 continue하여 다음 커밋의 병합을 시작하는 식으로 반복 실행한다. 리베이스를 통해 이동한 커밋들의 해시값이 변하는 이유는 이와 같이 각 커밋을 따로 새로이 병합하기 때문이다.

Flow of rebase to compare with merge


결과적으로 3-way 병합은 병합에 대한 커밋을 따로 생성하지만 리베이스는 병합에 대한 커밋을 따로 생성하지 않으며,

병합의 경우 각 브랜치의 HEAD가 기본적으로 최신 커밋을 가리키고 있지만 리베이스는 목적 브랜치의 HEAD가 최신 커밋이 아니라 변경된 base 커밋을 가리키고 있다.

※ 헷갈리기 쉬운 명령어의 방향
Comparing that direction of command between merge and rebase
  • main 브랜치jisung 브랜치를 병합하는 상황을 가정해 보자.
  • 병합의 경우엔 main 브랜치로 이동해서 jisung 브랜치의 병합을 명령하고
    git switch main
    git merge jisung
    
  • 리베이스의 경우엔 jisung 브랜치로 이동해서 main 브랜치의 리베이스를 명령한다.
    git switch jisung
    git rebase main
    

체리픽이란?

체리나 과일에서 좋은 것만 골라서 선택하는 행위를 뜻하는 단어로 Git에서는 다른 브랜치에서 선택적으로 원하는 커밋을 골라내어 현재 브랜치로 가져오는 명령어를 의미한다.

Reference of cherry-pick


체리픽 명령어
git cherry-pick <커밋 해시값>
  • 명령어 실행 전 Git log before git command cherry-pick

  • 명령어 실행 Git command to pick commit optionally to current branch from another branch

  • 명령어 실행 후 Git log after git command cherry-pick


체리픽 진행 옵션
git cherry-pick --continue
  • 현재 진행 중인 체리픽을 계속 진행하는 옵션으로 충돌 등이 발생했을 때 문제 해결 후 체리픽을 계속 진행하고 싶을 경우 사용할 수 있다. Git command to continue proceeding cherry-pick


체리픽 건너뛰기 옵션
git cherry-pick --skip
  • 체리픽 중 충돌 등이 발생한 경우 문제 해결 중에 특정 커밋의 충돌을 건너뛰고 체리픽을 진행하고 싶을 경우 사용하는 옵션이다. Git command to skip proceeding cherry-pick


체리픽 중지 옵션
git cherry-pick --abort
  • 현재 진행 중인 체리픽을 중지하는 옵션으로 충돌 등이 발생했을 때 체리픽을 중지하고 싶을 경우 사용할 수 있다. Git command to abort proceeding cherry-pick

충돌과 해결 방법

충돌이란?
Reference of conflict
  • 동일한 파일에서 동일한 위치의 코드를 두 명 이상이 서로 다르게 수정했을 때 병합을 시도할 경우 충돌이 발생한다.
  • 충돌이 발생할 경우 병합이 중지되고 수동으로 해결할 때까지 커밋이 생성되지 않는다. 병합이 중지된 경우 “–continue” 옵션으로 병합을 지속하거나 “–abort” 옵션으로 병합을 중단할 수 있다.
  • 리베이스의 경우엔 “–skip” 옵션을 통해 일부 커밋의 충돌을 무시하고 리베이스를 진행할 수 있다.


해결 방법
  1. 먼저 충돌이 발생한 파일들을 목록으로 확인한다.
    git ls-files -u
    
    git ls-files --unmerged
    
    • 병합하지 않은 파일들을 출력하는 명령어로 충돌이 해결되지 않은 파일들의 목록을 확인할 수 있다. Git command to show list of unmerged files
    • 목록의 파일 실행 방법
      • 목록의 파일 위에 커서를 올려두고 “command 버튼 + 클릭”하여 해당 파일을 실행
      • 아래와 같은 에디터 실행 명령어로 해당 파일을 실행
      • vim <파일 경로>
        

        Reference of vim editor

      • code <파일 경로>
        

        Reference of vscode editor

      • open -a Xcode <파일 경로>
        

        Reference of xcode editor

  2. 목록에서 충돌이 발생한 파일을 실행하면 아래와 같은 충돌 마커가 생성되어 있다. 에디터를 이용하여 실제 파일의 코드를 수정해야 한다.
    <<<<<<< HEAD
    원본 브랜치의 HEAD가 가리키는 커밋의 내용
    =======
    파생 브랜치의 diff 내용
    >>>>>>> 브랜치 이름 등의 참조 이름
    
    • 충돌이 발생한 파일의 수정을 완료하기 위해선 생성되어 있는 충돌 마커를 삭제해야한다. 당연하게도 충돌한 파일의 내용은 개발자의 적절한 판단하에 수정하여야 한다. Reference of resolving conflicted file
  3. 충돌한 모든 파일의 수정이 완료되면 병합 커밋을 생성하여 최종적으로 충돌을 해결할 수 있다. 먼저 add 명령어를 사용하여 수정이 완료된 파일들을 스테이지 상태로 만든다.
    • 직접 commit 명령어를 사용하여 commit을 생성할 수 있다.
    • 병합 또는 리베이스 명령어를 “–continue” 옵션과 함께 사용하여 진행할 경우 충돌 해결이 완료된 상태일 때 자동으로 commit을 생성한다.

마무리하며…

이번 포스트에서는 병합과 리베이스 그리고 그 도중에 발생할 수 있는 충돌에 대하여 알아보았다. 작성을 시작할 때는 아는 한도 내에서 쉽게 풀어서 작성해 보자고 다짐했는데 막상 읽어보니 쉬운 게 맞나..? 의구심이 들어서 몇 번을 더 수정했던 것 같다. 다음 포스트에서는 작업 중 발생하는 실수를 되돌릴 수 있게 해주는 복구 명령어들을 알아보자.