[GIT] 고급 Merge 기술과 충돌 해결 전략

Merge 충돌

  • Git에서 Merge를 수행할 때 충돌이 발생할 수 있습니다.
  • 먼저, Merge하기 전에 워킹 디렉토리를 정리하는 것이 좋습니다. 작업 중이던 파일을 임시로 저장하거나 Stash를 사용하여 변경사항을 백업합니다.
  • function hello() {
      console.log('hello world');
    }
    
    hello();
  • hello wolrd를 출력하는 javascript 파일이 있습니다.
  • mundo 브랜치를 생성 후 hello mundo로 변경 후 커밋합니다.
  • // mundo 브랜치
    function hello() {
      console.log('hello mundo');
    }
    
    hello();
  • master 브랜치에서 hello master로 변경 후 커밋합니다.
  • // master 브랜치
    function hello() {
      console.log('hello master');
    }
    
    hello();
  • master 브랜치에서 mundo 브랜치를 merge하면 충돌이 발생합니다.
  • $git merge mundo
    Auto-merging hello.js
    CONFLICT (content): Merge conflict in hello.js
    Automatic merge failed; fix conflicts and then commit the result.

Merge 되돌리기

  • merge 후 자신의 상태를 체크합니다.
  • $git status --sb
    ## master
     M hello.js
  • merge를 중단합니다.
  • $git merge --abort
  • 중단 후 자신의 상태를 체크합니다.
  • $git status --sb
    ## master
  • 만약 Merge 이전에 워킹 디렉토리에 Stash하지 않았거나 커밋하지 않은 변경사항이 있는 경우에는 --abort 명령어를 수행할 수 없습니다.
  • fatal: There is no merge to abort (MERGE_HEAD missing).
  • 이럴 경우에는 reset으로 되돌려야합니다.
  • $git reset --hard HEAD
  • 변경사항은 저장되지 않지만 따로 임시 저장하여 작업해야합니다.

Merge 공백 무시하기

  • $ git merge -Xignore-space-change whitespace mundo
  • 이 명령은 모든 공백 변경을 무시하고 Merge를 시도합니다.
  • 이 옵션은 팀원 중 누군가가 스페이스를 탭으로 바꾸거나 탭을 스페이스로 바꾼 경우에 유용하게 사용될 수 있습니다.

수동 Merge

  • 각 버전의 파일 가져오기
    • stages 숫자를 사용하여 각 버전의 파일을 가져옵니다.
    • $git show :1:hello.js > hello.common.js
      $git show :2:hello.js > hello.ours.js
      $git show :3:hello.js > hello.theirs.js
    • Plumbing 명령을 사용하여 각 버전의 파일을 가져올 수 있습니다.
    • $ git ls-files -u
      100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1	hello.js
      100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2	hello.js
      100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3	hello.js
      
      $ git show ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 > hello.common.js
      $ git show 36c06c8752c78d2aff89571132f3bf7841a7b5c3 > hello.ours.js
      $ git show e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd > hello.theirs.js
  • Merge 수행
    • 이제 세 파일을 가지고 수동으로 충돌을 해결하고 Merge를 수행합니다.
    • $ git merge-file -p hello.ours.js hello.common.js hello.theirs.js > hello.js
  • 변경 사항 확인
    • Merge를 완료한 파일을 양쪽 부모와 비교하여 변경 사항을 확인할 수 있습니다.
    • $ git diff --ours
      $ git diff --theirs -b
      $ git diff --base -b
  • 불필요한 파일 제거
    • $ git clean -f

충돌 파일 Checkout

  • 충돌이 일어난 후 checkout을 통해 해결을 해봅니다.
  • $ git checkout --conflict=diff hello.js
    Recreated 1 merge conflict
  • 위 명령을 실행하면 충돌이 발생한 파일 hello.js의 충돌 부분을 아래와 같이 표시하여 보여줍니다.
  • // greeting
    function hello() {
    <<<<<<< ours
      console.log('hello world2');
    ||||||| base
      console.log('hello world');
    =======
      console.log('hello mundo');
    >>>>>>> theirs
    }
    
    hello();
  • 이때 ours는 현재 브랜치 내용 base는 공통 조상의 내용 theirs는 Merge하려는 브랜치의 내용입니다.
  • hello.js에서 충돌 해결 후 커밋하면 됩니다.

Merge 로그

  • Triple Dot 문법을 사용하여 Merge에 사용된 양 브랜치의 모든 커밋 목록을 얻습니다. 각 커밋은 왼쪽 또는 오른쪽 브랜치에 속하는지에 따라 < 또는 > 기호로 표시됩니다.
  • $git log --oneline --left-right HEAD...MERGE_HEAD
    < f1270f7 update README
    < 9af9d3b add a README
    < 694971d update phrase to hola world
    > e3eb223 add more tests
    > 7cff591 add testing script
    > c3ffff1 changed text to hello mundo
  • --merge 옵션을 사용하여 충돌이 발생한 파일이 속한 커밋만 보여줍니다.
  • $git log --oneline --left-right --merge
    < 694971d update phrase to hola world
    > c3ffff1 changed text to hello mundo
  • -p 옵션을 추가하여 충돌이 발생한 파일의 변경사항을 보여줍니다.
  • $git log --oneline --left-right --merge -p
    < 0cec34d (HEAD -> master) 2
    diff --git a/hello.js b/hello.js
    index 6fda48f..4351403 100644
    --- a/hello.js
    +++ b/hello.js
    @@ -1,6 +1,6 @@
     // greeting
     function hello() {
    -  console.log('hello world');
    +  console.log('hello world2');
     }
    
     hello();
    \ No newline at end of file
    < d52d4a2 greeting
    diff --git a/hello.js b/hello.js
    index f5c4135..6fda48f 100644
    --- a/hello.js
    +++ b/hello.js
    @@ -1,3 +1,4 @@
    +// greeting
     function hello() {
       console.log('hello world');
     }
    > 66a9861 hello mundo
    diff --git a/hello.js b/hello.js
    index f5c4135..fce9e06 100644
    --- a/hello.js
    +++ b/hello.js
    @@ -1,5 +1,5 @@
     function hello() {
    -  console.log('hello world');
    +  console.log('hello mundo');
     }
    
     hello();
    \ No newline at end of file

Combined Diff 충돌 해결

  • 충돌이 발생한 부분은 `<<<<<<<`와 `>>>>>>>`로 표시됩니다. 이는 해당 코드가 어느 브랜치에서 추가되었는지를 표시합니다.
  • $ git diff
    diff --cc hello.rb
    index 0399cd5,59727f0..0000000
    --- a/hello.rb
    +++ b/hello.rb
    @@@ -1,7 -1,7 +1,11 @@@
      #! /usr/bin/env ruby
    
      def hello
    ++<<<<<<< HEAD
     +  puts 'hola world'
    ++=======
    +   puts 'hello mundo'
    ++>>>>>>> mundo
      end
    
      hello()
  • 충돌이 발생한 부분을 수정하여 원하는 결과물을 얻을 수 있도록 합니다. 이때 충돌 마커를 제거하고 양쪽 브랜치에서 필요한 부분을 조합합니다.
  • 충돌을 해결한 후에는 수정한 내용을 스테이징 영역에 추가하고 커밋하여 변경사항을 저장합니다.

Merge 되돌리기

  • Refs 수정
    • 로컬 저장소에서만 Merge를 잘못한 경우, 브랜치를 원하는 커밋으로 옮기는 것이 가장 빠르고 쉬운 방법입니다.
    • $git reset --hard HEAD~
  • 커밋 되돌리기
    • 모든 변경 사항을 취소하는 새로운 커밋을 만들어서 실수를 되돌릴 수 있습니다.
    • $git revert -m 1 HEAD

다른 방식의 Merge

  • 기본적으로 Merge를 수행할 때 Git은 충돌이 발생하면 사용자에게 충돌이 발생했음을 알려줍니다. 그러나 충돌을 직접 해결하는 대신 -Xours 또는 -Xtheirs와 같은 옵션을 추가하여 어느 쪽을 선택할지 미리 지정할 수 있습니다.
  • $git merge -Xours mundo
  • Merge를 할 때 다른 브랜치의 변경 사항을 무시하고 현재 브랜치의 코드를 그대로 사용합니다.
  • $git merge -s ours mundo
  • 서브트리 Merge는 하나의 프로젝트를 다른 프로젝트의 하위 디렉토리로 Merge하는 것을 말합니다.
  • $git read-tree --prefix=rack/ -u rack_branch

'GIT > Git 도구' 카테고리의 다른 글

[GIT] Git Reset과 Checkout 쉽게 이해하기  (1) 2024.03.18
[GIT] 히스토리 편집하기  (0) 2024.03.18
[GIT] 히스토리 검색하기  (0) 2024.03.18
[GIT] 작업에 서명하기  (0) 2024.03.18
[GIT] Stashing과 Cleaning  (0) 2024.03.11