본문 바로가기

Today Learning _

Part 16. GIT 다루기(심화)

브랜치(branch)란?

소프트웨어를 개발할 때 개발자들은 동일한 소스코드를 공유하고 다루게 됩니다.
동일한 소스코드 위에서 어떤 개발자는 버그를 수정하고 또 다른 개발자는 새로운 기능을 만들어냅니다.
이와 같이 여러 사람이 동일한 소스코드를 기반으로 서로 다른 작업을 할 때 

각각 서로 다른 버전의 코드가 만들어질 수밖에 없습니다.

이때 여러 개발자들이 동시에 다양한 작업을 할 수 있게 만들어주는 기능이 '브랜치(branch)'입니다.
각자 독립적인 작업 영역(저장소) 안에서 마음대로 소스 코드를 변경할 수 있습니다.
이렇게 분리된 작업 영역에서 변경된 내용은 후에 원래의 버전과 비교해서 하나의 새로운 버전으로 만들어 낼 수 있습니다.

 

즉, 브랜치란 독립적으로 어떤 작업을 진행하기 위한 개념입니다.
필요에 의해 만들어지는 각각의 브랜치는 다른 브랜치의 영향을 받지 않기 때문에 
여러 작업을
동시에 진행할 수 있습니다.

만들어진 브랜치는 다른 브랜치와 병합(merge) 함으로써 작업한 내용을 다시 새로운 하나의 브랜치로 모을 수 있습니다.

 

브랜치를 사용해 동시에 여러 작업을 진행할 때 작업 흐름을 한눈에 파악할 수 있습니다.
여러 명이서 동시에 작업을 할 때 다른 사람의 작업에 영향을 주거나 받지 않도록

메인 브랜치에서 자신의 작업 전용 브랜치를 만듭니다.


그리고 각각 작업을 진행한 후 작업이 끝난 사람은 메인 브랜치에 자신의 브랜치 변경 사항을 적용합니다. 

이렇게 함으로써 다른 사람의 작업에 영향을 받지 않고

독립적으로 특정 작업을 수행하고 결과를 하나로 모아 나가게 됩니다.


이런 방식으로 작업할 경우 '작업 단위', 즉 브랜치로 그 작업의 기록을 중간에 남기게 되므로
문제가 발생했을 경우 원인이 되는 작업을 찾아내거나 그에 따른 대책을 세우기 쉬워집니다.

 

 

 

 

 

 

master 브랜치

저장소를 처음 만들면 Git은 'master'라는 이름의 브랜치를 만들어 둡니다.
이 새로운 저장소에 새로운 파일을 추가한다거나 추가한 파일의 내용을 변경하여
그 내용을 저장(commit)하는 것은 모두 master라는 이름의 브랜치를 통해 처리할 수 있습니다.

master가 아닌 또 다른 브랜치를 만들어서 선언(checkout) 하지 않는 이상 
모든 작업은 master 브랜치에서 이뤄집니다. 

 

 

 

 

 

 

 

브랜치 만들기

Git 에서는 작업에 따라 자유롭게 브랜치를 만들 수 있습니다.
그러나 이것을 효과적으로 관리하려면 작업할 팀원들과 어떠한 방식으로 

브랜치를 만들고 통합할 것인지 미리 정해두는 것이 좋습니다.

 

 

 

통합 브랜치(Intergration Branch)


통합 브랜치는 언제든지 배포할 수 있는 버전을 만들 수 있어야 하는 브랜치입니다.
그렇기에 늘 안정적인 상태를 유지하는 것이 중요합니다.
: 안정적인 상태는 현재 작업중인 소스코드가 모바일에서 작동하는 애플리케이션을 개발하기 위한 것이라면
그 어플리케이션의 모든 기능이 정상적으로 동작하는 상태를 의미합니다.

만약 이 어플리케이션에 어떤 문제가 발견되어 그 문제(버그)를 수정한다던지 새로운 기능을
추가한다던지 해야할 때, '토픽 브랜치(Topic branch)'를 만들 수 있습니다.
처음에는 보통 통합 브랜치에서 토픽 브랜치를 만들어냅니다.

일반적으로 저장소를 처음 만들었을 때 생기는 'master'브랜치를 통합 브랜치로 사용합니다.

 

 

토픽 브랜치(Topic Branch)


토픽 브랜치란, 기능 추가나 버그 수정과 같은 단위 작업을 위한 브랜치입니다. 
여러 개의 작업을 동시에 진행할 때에는, 그 수만큼 토픽 브랜치를 생성할 수 있습니다.

토픽 브랜치는 보통 통합 브랜치로부터 만들어내며 토픽 브랜치에서 특정 작업이
완료되면 다시 통합 브랜치에 병합하는 방식으로 진행됩니다. 

이런 토픽 브랜치는 '피처 브랜치(Feature branch)'라고 부르기도 합니다.

 

 

 

 

 

 

 

 

 

브랜치 전환하기

Git에서는 항상 작업할 브랜치를 미리 선택해야 합니다.
처음에는 master 브랜치가 선택되어 있습니다.

 

현재 선택된 브랜치가 아닌 다른 브랜치에서 작업하고 싶을 땐 

'체크아웃(checkout)' 명령어를 실행하여 원하는 브랜치로 전환할 수 있습니다.

체크 아웃을 실행하면 브랜치 안에 있는 마지막 커밋 내용이 작업 트리에 펼쳐집니다.
브랜치가 전환되었으므로 이후에 실행한 커밋은 전환한 브랜치에 추가됩니다.

 

 

HEAD

 

'HEAD'란 현재 사용 중인 브랜치의 선두 부분을 나타내는 이름입니다. 
기본적으로는 'master'의 선두 부분을 나타냅니다.
'HEAD'를 이동하면 사용하는 브랜치가 변경됩니다.

 

커밋을 지정할 때 '~(틸드, 물결 기호)'와 '^(캐럿, 삽입 기호)'을 사용해 
현재 커밋으로부터 특정 커밋의 위치를 가리킬 수 있습니다.

이때 자주 사용하는 것이 'HEAD'로서 '~'와 숫자를 'HEAD' 뒤에 붙여 몇 세대 앞의 커밋을
가리킬 수 있습니다. '^'은 브랜치 병합에서 원본이 여럿 있는 경우 몇번째 원본인지를 지정할 수 있습니다.


HEAD~3          HEAD~2 or HEAD~1^1                  HEAD~1 or HEAD^                   HEAD

 

 

stash


커밋하지 않은 변경 내용이나 새롭게 추가한 파일이 인덱스와 작업 트리에 남아 있는 채로
다른 브랜치로 전환(checkout)되면 그 변경 내용은 기존 브랜치가 아닌 

전환된 브랜치에서 커밋할 수 있습니다.

다만 커밋 가능한 변경 내용 중에 전환된 브랜치에서도 한차례 변경이 되어 있는 경우에는
체크 아웃에 실패할 수 있습니다. 이 경우 이전 브랜치에서 커밋하지 않은 내용을 커밋하거나
stash를 이용해 일시적으로 변경 내용을 다른 곳에 저장하여 충돌을 피하게 한 뒤 체크아웃을 해야합니다.

 

stash란 파일의 변경 내용을 일시적으로 기록해두는 영역입니다.
stash를 사용하여 작업 트리와 인덱스 내에서 아직 커밋하지 않은 변경을 일시적으로 저장해 둘 수 있습니다.
이곳에 저장된 변경 내용은 나중에 다시 불러와 원래의 브랜치나 다른 브랜치에 커밋할 수 있습니다.

 

 

 

 

 

 

 

브랜치 통합하기

작업이 완료된 토픽 브랜치는 최종적으로 통합 브랜치에 병합됩니다.
브랜치 통합에는 merge를 사용하는 방법과 rebase를 사용하는 방법의 2가지 종류가 있습니다.
어느 쪽을 사용하느냐에 따라 통합 후의 브랜치의 이력이 크게 달라집니다.

 

 

merge

 

ex) 'master' 브랜치에서 분기하는 'bugfix' 브랜치가 있을 때

bugfix 브랜치를 master에 병합할 때 master 브랜치의 상태가 이전부터 

변경되어 있지 않으면 매우 쉽게 병합할 수 있습니다. 

 

bugfix 브랜치의 이력은 masater 브랜치의 이력을 모두 포함하기에 

master 브랜치는 단순히 이동하기만 해도 bugfix 브랜치 내용을 적용할 수 있습니다.
이를 fast-forward(빨리감기)병합이라고 부릅니다.

 

하지만 bugfix 브랜치를 분기한 이후에 master 브랜치에 여러 가지
변경 사항이 적용되는 경우도  있습니다.

이 경우에는 master 브랜치 내의 변경 내용과
bugfix 브랜치 내의 변경 내용을  하나로 통합할 필요가 있습니다.

따라서 양쪽 변경을 가져온 'merge commit(병합 커밋)'을 실행하게 됩니다.
병합 완료 후, 통합 브랜치인 master 브랜치로 통합된 이력이 생기게 됩니다.

**병합 시에 fast-forward 병합이 가능한 경우라도 
'non fast-forward 병합' 옵션을 지정하여 만들어 낼 수 있습니다. 
이를 실행하면 브랜치가 그대로 남기에 브랜치로 실행한 작업 확인 및 관리 면에서 더 유용할 수 있습니다.

 

 

rebase


위와 마찬가지로 master 브랜치에서 분기하는 bugfix 브랜치가 있습니다.
rebase를 이용해 'non fast-forward 병합' 방식으로 진행하도록 해보겠습니다.

bugfix 브랜치를 master 브랜치에 rebase하면 bugfix 브랜치 이력이 master 브랜치 뒤로 이동합니다.
이때 이동하는 bugfix의 내용이 master의 커밋된 버전들과 충돌하는 부분이 생길 수 있습니다.
그때는 각각의 커밋에서 발생한 충돌 내용을 수정할 필요가 있습니다.

rebase만 하면 master의 위치는 그대로 유지되는데 

이 위치를 변경하기 위해서는 master 브랜치에서
bugfix 브랜치를 fast-forward(빨리 감기) 병합하면 됩니다.

 

 

* merge 와 rebase는 통합 브랜치에 토픽 브랜치를 통합하고자 하는 목적은 같으나 특징은 다릅니다.

merge : 변경 내용의 이력이 모두 그대로 남아 있기에 이력이 복잡하다.
rebase : 이력은 단순해지지만 원래 커밋의 이력이 변경된다. 정확한 이력을 남겨야 할 때는 사용하지 않는다.

- 토픽 브랜치에 통합 브랜치의 최신 코드를 적용할 경우 rebase 사용
- 통합 브랜치에서 토픽 브랜치를 불러올 경우에는 우선 rebase를 한 후 merge

 

 

 

 

 

 

브랜치 만들기

$ git branch <branchname>


issue 1의 브랜치를 예시로 만들어 봅니다.


$ git branch issue 1

$ git branch
  issue1
* master

옵션을 지정하지 않고 branch 명령어를 실행하면 브랜치 목록 전체를 확인할 수 있습니다.
앞부분에 *이 붙어 있는 것이 현재 선택된 브랜치입니다.

 

 

 

 

 

 

브랜치 전환하기

issue 1 브랜치로 작업을 수행하려면 이 브랜치를 사용하겠다고 명시적으로 지정해야 합니다.

 

$ git checkout branch

다음과 같이 checkout 명령어 뒤에 사용할 브랜치 이름을 입력하면 됩니다. 

$ git checkout issue 1
  Switched to branch 'issue 1'

** checkout 명령에 -b 옵션을 넣으면 브랜치 작성과 체크아웃을 한 번에 실행할 수 있습니다.

$ git checkout -b branch

issue 1 브랜치를 체크아웃 한 상태에서 commit을 수행합니다.
파일을 수정 후  아래 작업을 실행합니다.

$ git add file.txt
$ git commit -m "add 설명을 추가"

 

 

 

 

 

 

브랜치 병합하기

issue1 브랜치 변경사항을 master 브랜치에 병합하겠습니다.

브랜치 병합은 merge 명령어로 실행합니다.


이 명령어에 병합할 커밋 이름을 넣어 실행하면 지정한 커밋 내용이 'HEAD'가 가리키는
브랜치에 넣어집니다. 'HEAD'는 현재 사용 중인 브랜치에 위치합니다.

 

$ git merge commit

master 브랜치에 issue 1을 넣기 위해서는 master 브랜치에 HEAD가 위치하게 만들어야 합니다.
checkout 명령어를 이용해 master 브랜치로 전환합니다.

$ git checkout master
  Switched to branch 'master'

병합하기 전에 파일의 내용을 확인합니다.
파일의 커밋을 issue 1에서 실행했기에 master에서는 내용이 변경되어 있지 않아야 합니다.

$ git merge issue1
Updating 1257027.. b2 b23 c4
Fast-forward
 file.txt|    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

 

 

이제 master 브랜치가 가리키는 커밋이 issue 1과 같은 위치로 이동했습니다.
이런 방식의 병합을 fast-forward(빨리 감기) 병합이라고 합니다.

이후 파일을 열어보면 파일의 내용이 추가되어 있는 것을 확인할 수 있습니다.

 

 

 

 

 

 

브랜치 삭제하기

issue 1 브랜치 내용이 master에 통합 되었기에 issue1브랜치가 필요없게 되었습니다.
브랜치를 삭제하려면 branch 명령에 -d 옵션을 지정하여 실행하면 됩니다.

 

$ git branch -d branchname

$ git branch -d issue1
  Deleted branch issue 1 (was b2b23c4).

이후 issue1 브랜치는 삭제됩니다.
branch 명령어를 통해 확인합니다.

$ git branch
 * master

 

 

 

 

 

동시에 여러 작업하기

$ git branch issue 2
$ git branch issue 3

$ git checkout issue 2
  Switched to branch 'issue 2'

$ git branch
* issue2
   issue 3
   master


이후 파일 수정 후 commit을 해줍니다.(현재 issue 2 브랜치)

$ git add file.txt
$ git commit -m "설명 추가"
[issue 2 8 f7 aa27] commit의 설명 추가
 1 files changed, 2 insertions(+), 0 deletions(-)


다음에 issue 3 브랜치로 전환합니다.

$ git checkout issue3
  Switched to branch 'issue 3'

파일을 열어 변경 사항이 없는지 확인합니다.
issue 3 브랜치의 file.txt를 수정후 커밋합니다.

$ git add file.txt
$ git commit -m "issue 3 수정"
[issue 3 e5 f91 ac] pull 설명을 추가
 1 files changed, 2 insertions(+), 0 deletions(-)

이처럼 각 브랜치에서는 독립적으로 서로 다른 작업을 처리할 수 있습니다.

 

 

 

 

 

 

병합할 때 발생하는 충돌 해결하기

 

이전에 만들었던 issue 2 브랜치와 issue 3 브랜치의 변경 부분을 모두 master 브랜치에 통합하겠습니다.

 

먼저 master 브랜치를 체크아웃 한 다음 issue 2를 병합합니다.

$ git checkout master
 Switched to branch 'master'

$ git merge issue2
Updating b2 b23 c4.. 8 f7 aa27
Fast-forward
 file.txt |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

 

이러면 fast-forward(빨리 감기) 병합이 실행됩니다.

 

fast-forward 병합을 나타낸 그림입니다.

 

 

이번에는 issue 3 브랜치를 병합합니다.

 

$ git merge issue3
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

 

confilct(충돌)이 나오는 것을 보니 자동 병합이 되지 않는 것을 알 수 있습니다.
파일을 확인해 보면 충돌이 있는 부분에 git 이 자동으로 충돌 정보를 포함하여 파일 내용을 변경합니다.

 

ex)
<<<<<<< HEAD
commit: 인덱스의 상태를 기록하기
=======
pull: 원격 저장소의 내용을 가져오기
>>>>>>> issue3

 

파일을 보면 이런 식으로 변경되어 있을 것입니다.

이 내용을 통해 어떤 브랜치에서 어떤 부분이 충돌되었는지 확인할 수 있어야 합니다.

충돌이 일어난 부분은 일일이 확인해서 수정해 주어야 합니다. 
부분을 모두 수정한 후 다시 커밋합니다.

 

 

$ git add file.txt

$ git commit -m "issue 브랜치 병합"
# On branch master
nothing to commit (working directory clean)

 

issue3 브랜치를 병합할 땐 충돌 부분을 수정했기에 변화를 기록하는 병합 커밋이 새로 생성됩니다.
그리고 master 브랜치의 시작(HEAD)이 그 위치로 이동해 있는 것을 확인할 수 있습니다.
이와 같은 방식을 non fast forward 병합이라고 합니다.

 

non fast forward 병합을 나타낸 그림입니다 :

 

 

 

 

 

 

rebase로 병합하기

앞선 merge로 두 개의 브랜치를 master 브랜치로 모두 병합시켰습니다.
그로 인해 두개의 줄기로 브랜치가 분기되었다가 다시 하나로 합쳐지게 됩니다.

 



issue 3 브랜치를 병합할 때 rebase를 먼저 실행한 후 병합을 시도한다면
그 이력을 하나의 줄기로 만들 수 있습니다.

 

 

이를 위해 이전에 실행했던 병합 명령을 취소합니다.

 

git reset 을 적용한 그림입니다 :

 

$ git reset --hard HEAD~

이제 issue3 브랜치로 전환해 master 브랜치에 rebase를 실행합니다.

$ git checkout issue3
 Switched to branch 'issue 3'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: pull 설명을 추가
Using index info to reconstruct a base tree...
:13: new blank line at EOF.
+
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging  file.txt
CONFLICT (content): Merge conflict in file.txt
Failed to merge in the changes.
Patch failed at 0001 pull 설명을 추가

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".

 

merge와 마찬가지로 file.txt 파일 내용에 충돌이 있기에 그 부분을 수정해줍니다.
수정 후에는 commit이 아니라 rebase 명령에 --continue 옵션을 지정하여 실행해야 합니다.

 

 

$ git add file.txt

$ git rebase--continue
Applying: 수정된 내용


만약 rebase 자체를 취소하려면 --abort 옵션을 지정합니다.

 

rebase 를 한 결과 그림입니다 :

 

이처럼 rebase만 실행한 경우는 issue3 브랜치가 두 브랜치 앞쪽으로 위치기 옮겨질 뿐
master 브랜치는 아직 issue 3의 변경 사항이 적용되지 못한 채 뒤에 남겨져 있습니다.
이제 master 브랜치로 전환하여 issue3 브랜치 변경 사항을 병합합니다.

 

 

$ git checkout master
 Switched to branch 'master'

$ git merge issue3
Updating 8 f7 aa27.. 96 a 0 ff0
Fast-forward
 file.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

 

file.txt 최종적인 내용은 merge 했을 경우와 동일하지만 이력은 달라집니다.

 

 

rebase 를 이용한 병합의 결과물입니다 :

 

 

 

 

 

 

 

 

참고자료

 

브랜치란? 【브랜치 (Branch)】 | 누구나 쉽게 이해할 수 있는 Git 입문~버전 관리를 완벽하게 이용해보자~ | Backlog

브랜치란? 【브랜치 (Branch)】 | 누구나 쉽게 알 수 있는 Git에 입문하신 것을 환영합니다. Git을 사용해 버전 관리를 할 수 있도록 함께 공부해봅시다!

backlog.com