반응형

브랜치와 Merge는 보통 이런 식으로 진행합니다.

  1. 프로젝트가 있고 뭔가 작업을 진행하고 있음
  2. 새로운 이슈를 처리할 때 branch를 하나 생성
  3. 새로 만든 branch에서 작업 진행

그러던 중 중요한 문제가 생겨서 그것을 해결하는 Hotfix를 먼저 만들어야 했습니다.

  1. 새로운 이슈를 처리하기 이전의 운영 브랜치(ex - develop)로 이동
  2. Hotfix 브랜치를 새로 하나 생성
  3. 수정한 Hotfix 테스트를 마치고 운영 브랜치로 Merge
  4. 다시 작업하던 브랜치로 옮겨가서 하던 일 계속 진행

1. Branch의 기초

먼저 지금 작업하는 프로젝트에서 이전에 master 브랜치에 커밋을 볓 번 했다고 가정하겠습니다.

그림 1. 현재 커밋 히스토리

 

이때 Github 이슈 관리 시스템에 등록된 53번 이슈를 처리한다고 하면 이 이슈에 집중할 수 있는 브랜치를 새로 하나 만듭니다. 브랜치를 만들면서 checkout 까지 한 번에 하려면 git checkout -b 옵션을 사용합니다.

$ git checkout -b iss53
Switched to a new branch "iss53"

그림 2. master 와 별개로 진행 하는  iss53  브랜치

 

이번에는 다른 상황을 가정해보겠습니다.

 

작업하고 있던 프로젝트에 문제가 생겨서 즉시 고쳐야 합니다. 버그를 해결한 Hotfix에 iss53 브랜치가 섞이는 것을 방지하기 위해 iss53 과 관련된 코드를 어딘가에 저장해두고 원래 웅영 환경의 소스로 복구해야 합니다. Git을 사용하면 이런 노력을 들일 필요 없이 그냥 master 브랜치로 돌아가면 됩니다.

 

그렇지만, 브랜치를 이동하려면 해야할 일이 있습니다. 아직 커밋하지 않은 파일이 checkout 할 브랜치와 충돌 나면 브랜치를 변경할 수 없습니다. 브랜치를 변경할 때는 워킹 디렉토리를 정리하는 것이 좋습니다. 이런 문제를 다루는 방법은 추후 별도 포스팅에서 다루겠습니다. 지금은 작업하던 것을 모두 커밋하고 master 브랜치로 옮깁니다.

$ git checkout master
Switched to branch 'master'

이때 워킹 디렉토리는 53번 이슈를 시작하기 이전 모습으로 되돌려지기 때문에 새로운 문제에 집중할 수 있는 환경이 만들어집니다. Git은 자동으로 워킹 디렉토리에 파일들을 추가하고, 지우고, 수정해서 checkout 한 브랜치의 마지막 스냅샷으로 되돌려 놓는다는 것을 기억해야 합니다.

 

이젠 해결해야 할 핫픽스가 생겼을 때를 살펴보겠습니다.

hotfix 라는 브랜치를 만들고 새로운 이슈를 해결할 때까지 사용하겠습니다.

$ git checkout -b hotfix
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(+)

그림 3.  master  브랜치에서 갈라져 나온  hotfix  브랜치

 

운영 환경에 적용하려면 문제를 제대로 고쳤는지 테스트하고 최종적으로 운영환경에 배포하기 위해 hotfix 브랜치를 master 브랜치에 합쳐야합니다. git merge 명령으로 그 역할을 수행할 수 있습니다.

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Merge 메시지에서 눈여겨 볼 것이 있습니다. 바로 "Fast-forward" 입니다.

hotfix 브랜치가 가리키는 C4 커밋이 C2 커밋에 기반한 브랜치이기 때문에 브랜치 포인터는 Merge 과정 없이 그저 최신 커밋으로 이동합니다. 이런 Merge 방식을 "Fast-forward" 라고 부릅니다.

다시 말해 A 브랜치에서 다른 B 브랜치를 Merge 할 때 B 브랜치가 A 브랜치 이후의 커밋을 가리키고 있으면 그저 A 브랜치가 B 브랜치와 동일한 커밋을 가리키도록 이동시킬 뿐입니다.

 

이제 hotfixmaster 브랜치에 포함됐고 운영환경에 적용할 수 있는 상태가 되었다고 가정해보겠습니다.

그림 4. Merge 후  hotfix  와 같은 것을 가리키는  master  브랜치

 

급한 문제를 해결하고 master 브랜치에 적용하고 나면 다시 일하던 브랜치로 돌아가야 합니다. 이제 더 이상 필요 없는 hotfix 브랜치는 삭제합니다. git branch -d 명령을 통해 브랜치를 삭제할 수 있습니다.

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

자, 이제 이슈 53번을 처리하던 환경으로 되돌아가서 하던 일을 계속 하겠습니다.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

그림 5.  master  와 별개로 진행하는  iss53  브랜치

 

위에서 작업한 hotfixiss53 브랜치에 영향을 끼치지 않았음을 기억해야합니다.

git merge master 명령으로 master 브랜치를 iss53 브랜치에 Merge 하면 iss53 브랜치에 hotfix 가 적용됩니다. 아니면 iss53 브랜치가 master 브랜치에 Merge 할 수 있는 수준이 될 때까지 기다렸다가 Merge 하면 hotfixiss53 브랜치가 합쳐집니다.

2. Merge의 기초

계속해서 예제를 이어나가겠습니다.

53번 이슈를 다 구현하고 master 브랜치에 Merge 하는 과정을 살펴보겠습니다. iss53 브랜치를 master 브랜치에 Merge 하는 것은 앞서 살펴본 hotfix 브랜치를 Merge 하는 것과 비슷합니다.

git merge 명령으로 합칠 브랜치에서 합쳐질 브랜치를 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(+)

hotfix 를 Merge 했을 때와 메시지가 다릅니다.

현재 브랜치가 가리키는 커밋이 Merge 할 브랜치의 조상이 아니므로 Git은 "Fast-forward" 로 Merge 하지 않습니다. 이 경우에는 Git은 각 브랜치가 가리키는 커밋 두 개의 공통 조상 하나를 사용하여 3-way Merge를 합니다.

그림 6. 커밋 3개를 Merge

 

단순히 브랜치 포인터를 최신 커밋으로 옮기는 게 아니라 3-way Merge 의 결과를 별도의 커밋으로 만들고 나서 해당 브랜치가 그 커밋을 가리키도록 이동시킵니다. 그래서 이런 커밋은 부모가 여러 개고 Merge 커밋이라고 부릅니다.

그림 7. Merge 커밋

 

iss53 브랜치를 master 에 Merge 하고 나면 더는 iss53 브랜치가 필요없습니다.

이제 다음 명령으로 브랜치를 삭제하고 이슈의 상태를 처리 완료로 표시하면 됩니다.

$ git branch -d iss53

3. Conflict의 기초

가끔씩 3-way Merge가 실패할 때도 있습니다. Merge 하는 두 브랜치에서 같은 파일의 한 부분을 동시에 수정하고 Merge 하면 Git은 해당 부분을 Merge 하지 못합니다.

예를 들어, 53번 이슈와 hotfix 가 같은 부분을 수정했다면 Git은 Merge 하지 못하고 아래와 같은 충돌(Conflict) 메시지를 출력합니다.

$ 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 하지 못해서 새 커밋이 생기지 않습니다. 변경사항의 충돌을 개발자가 해결하지 않는 한 Merge 과정을 진행할 수 없습니다. Merge 충돌이 일어났을 때 Git이 어떤 파일을 Merge 할 수 없었는지 살펴보려면 git status 명령을 이용합니다.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

충돌이 일어난 파일은 unmerged 상태로 표시됩니다. Git은 충돌이 난 부분을 표준 형식에 따라 표시해주고, 개발자는 해당 부분을 수동으로 해결합니다.

충돌 난 부분은 아래와 같이 표시됩니다.

<<<<<<< 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 버전(merge 명령을 실행할 때 작업하던 master 브랜치)의 내용이고 아래쪽은 iss53 브랜치의 내용입니다. 충돌을 해결하려면 위쪽이나 아래쪽 내용 중에서 고르거나 새로 작성하여 Merge 합니다.

아래는 아예 새로 작성하여 충돌을 해결하는 예제입니다.

<div id="footer">
please contact us at email.support@github.com
</div>

충돌한 양쪽에서 조금씩 가져와서 새로 수정했습니다. 그리고 <<<<<<<<, ==========, >>>>>>>>> 가 포함된 행을 삭제했습니다. 이렇게 충돌한 부분을 해결하고 git add 명령으로 다시 Git에 저장합니다.

 

충돌을 해결하고 나서 해당 파일이 Staging Area에 저장됐는지 확인했으면 git comit 명령으로 Merge 한 것을 커밋합니다. 충돌을 해결하고 Merge 할 때는 커밋 메시지가 아래와 같습니다.

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#    .git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#    modified:   index.html
#

어떻게 충돌을 해결했고 좀 더 확인해야 하는 부분은 무엇이고 왜 그렇게 해결했는지에 대해서 자세하게 기록합니다. 자세한 기록은 나중에 이 Merge 커밋을 이해하는데 도움을 줍니다.

4. Branch 관리

지금까지 브랜치를 만들고, Merge 하고, 삭제하는 방법에 대해 살펴봤습니다. 브랜치를 관리하는 데 필요한 다른 명령도 살펴보겠습니다.

git branch 는 단순히 브랜치를 만들고 삭제하는 명령이 아닙니다. 아무런 옵션 없이 실행하면 브랜치의 목록을 보여줍니다.

$ git branch
  iss53
* master
  testing

* 기호가 붙어 있는 master 브랜치는 현재 checkout 해서 작업하는 브랜치를 나타냅니다. 즉, 지금 수정한 내용을 커밋하면 master 브랜치에 커밋되고 포인터가 앞으로 한 단계 나아갑니다.

 

git branch -v 명령을 실행하면 브랜치마다 마지막 커밋 메시지도 함께 보여줍니다.

$ git branch -v
  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes

각 브랜치가 지금 어떤 상태인지 확인하기에 좋은 옵션도 있습니다.

현재 checkout 한 브랜치를 기준으로 --merged--no-merged 옵션을 사용하여 Merge 된 브랜치인지 그렇지 않은지 필터링 해 볼 수 있습니다.

 

git branch --merged 명령으로 이미 Merge 한 브랜치 목록을 확인합니다.

$ git branch --merged
  iss53
* master

iss53 브랜치는 앞에서 이미 Merge 했기 때문에 목록에 나타납니다.

* 기호가 붙어 있지 않은 브랜치는 삭제해도 되는 브랜치입니다. 이미 다른 브랜치와 Merge 했기 때문에 삭제해도 정보를 잃지 않습니다.

반대로 현재 checkout 한 브랜치에 Merge 하지 않은 브랜치를 살펴보려면 git branch --no-merged 명령을 사용합니다.

$ git branch --no-merged
  testing

위에는 없었던 다른 브랜치가 보입니다. 아직 Merge 하지 않은 커밋을 담고 있기 때문에 삭제할 수 없습니다.

만약 Merge 하지 않은 브랜치를 강제로 삭제하려면 -D 옵션으로 삭제해야 합니다.

 


출처 - 저서 ProGit

반응형
반응형