다시 시작하는 Git, GitHub 고급 1

Hyuno Choi·2021년 3월 21일
0
post-thumbnail

2021년 3월 21일

지난 포스팅까지 Git 기초편을 마치고 드디어 고급편을 쓰게 되었습니다.🧐 브랜치라는 개념부터 하나씩 살펴보겠습니다.

서론 - 나뭇가지 브랜치🌱

브랜치라는 단어를 나뭇가지라는 뜻으로 알고있는 분들이 많을 텐데요, Git에서도 같은 뜻으로 쓰이는 용어입니다. 마치 나뭇가지가 각자의 방향으로 뻗어나가는 것처럼 각자의 영역에서 독립적인 작업을 할 수 있도록 도와주는 개념입니다.

브랜치를 만드는 이유

우리가 프로젝트를 진행할 때는 여러 사람과 협업하게 됩니다. 이럴 때 다른 팀원의 작업이 끝나기를 기다렸다가 내가 맡은 기능을 구현하는 것은 매우 비효율적일 것입니다. 브랜치는 각각의 작업이 독립적으로 진행될 수 있도록 각자의 프로젝트 흐름을 만들어줍니다. 각자의 작업이 끝나면 통합 브랜치에 작업 내용을 반영시켜주기만 하면 됩니다!

그림에서 볼 수 있는 것과 같이, 만약 해결해야 할 문제가 issue1, issue2 두 개라면 각각의 브랜치에서 문제를 해결한 후 본류의 흐름과 합치면 되는 것입니다. 여기서 중간의 메인 브랜치를 통합 브랜치, 중간중간 뻗어나간 브랜치들을 토픽 브랜치라고 합니다. 이번 포스팅에서는 크게 밑의 세 내용을 다루겠습니다.

  1. 토픽 브랜치 만들기
  2. 통합 브랜치와 토픽 브랜치 병합하기
  3. 병합 시 발생할 수 있는 충돌 다루기

브랜치 만들기

$ git branch <브랜치명> 명령어로 브랜치를 만들 수 있습니다. issue1이라는 이름의 브랜치를 만들고자 한다면,

통합 브랜치 밖에 없는 현재 상태에서 $ git branch issue1을 입력해줍니다.

이제 issue1이라는 브랜치가 생긴 것을 확인할 수 있습니다. 또한 $ git branch라고 입력하면 브랜치 목록을 볼 수 있습니다. 현재 선택된 브랜치에는 *표시가 붙어있습니다.

브랜치 전환하기

$ git checkout <브랜치명> 명령어로 브랜치 간 이동이 가능합니다. 위에서 생성한 issue1 브랜치로 이동하기 위해서는 $ git checkout issue1이라고 입력하면 됩니다.

브랜치를 변환할 때 주의해야 할 점이 있는데요, 현재 브랜치에서 커밋하지 않은 변경 사항이 있다면 브랜치를 전환할 수 없습니다. 커밋을 진행하고 전환하던지, $ git stash 명령어로 변경 사항을 임시 저장하고 브랜치를 전환해야 합니다.

HEAD가 issue1 브랜치로 변경되었습니다. HEAD란 현재 브랜치의 가장 최신 커밋을 가리키는 포인터입니다. 여기서 새로운 커밋을 만들게 되면 master 브랜치와는 독립적으로 issue1 브랜치에만 변경 사항이 반영됩니다.

보이는 것과 같이 master 브랜치는 그대로인 채 issue1 브랜치만 앞으로 간 것을 볼 수 있습니다. 이제부터는 디렉토리를 만들어 실습도 같이 진행하겠습니다.📁

  1. test라는 폴더를 만들고 file.txt라는 파일을 생성합니다.
  2. master 브랜치에서 addcommit을 진행합니다.

그리고 새 브랜치를 만들어 작업을 진행합니다.

  1. $ git branch issue1으로 새 브랜치를 만듭니다.
  2. $ git checkout issue1을 통해 새 브랜치로 이동합니다.
  3. file.txt 첫 번째 줄에 "첫 번째 줄"이라고 입력한 후 저장합니다.
  4. addcommit을 진행합니다.

여기까지의 과정을 시각화 한 것이 바로 위의 그림과 같습니다. 여기서 중요한 점은 다시 master 브랜치로 체크아웃 했을 때 file.txt에는 아무 내용도 적혀있지 않다는 것입니다. 특정 브랜치 상에서 작업한 내용은 그 브랜치에만 적용될 뿐, 다른 브랜치에는 아무런 영향도 주지 않습니다.

브랜치 병합하기

Git 브랜치에 관한 내용 중 가장 까다로운 부분이 이 브랜치 병합일 것입니다. 의도하던 의도하지 않았던 앞으로 브랜치를 병합하면서 수많은 충돌 상황을 마주할 것입니다. 그때마다 당황하지 않고 순서대로 충돌을 처리하기 위해서는 병합 방법에 대해 잘 알고있어야 하며 많은 연습을 해야 합니다. 브랜치 병합은 mergerebase로 그 방법이 나뉩니다.

merge

fast-forward

위의 상황으로 다시 돌아가봅시다. 우리는 issue1 브랜치에서 file.txt의 첫 번째 줄에 새로운 내용을 추가했습니다. 이제 추가가 끝나서 master 브랜치에도 해당 커밋을 반영하고 싶다면 merge 명령어를 사용하면 됩니다.

  1. $ git checkout master 명령어로 master 브랜치로 이동합니다.
  2. $ git merge issue1 명령어로 issue1 브랜치를 master에 통합합니다.

이때 다른 브랜치를 통합하는 주체인 브랜치에 HEAD가 위치해야 하며 merge 뒤에는 '통합 당하는' 브랜치 이름을 적어야 합니다.

master 브랜치로 체크아웃해서 HEAD가 master로 이동한 모습입니다.

merge 병합으로 master 브랜치가 issue1과 같은 위치에 있게 되었습니다. 이제 master 브랜치에서 file.txt 파일을 열어도 issue1 브랜치에서 변경한 내용이 들어있습니다. 이렇게 issue1이 master 브랜치의 이력을 모두 포함하고 있을 경우 master 브랜치가 단순히 앞으로 이동하는 병합을 fast-forward 병합이라고 합니다.

non-fast-forward

이제 fast-forward 병합이 아닌 경우를 살펴보겠습니다. 우리는 아까 issue1 브랜치에서만 변경 사항을 만들었습니다. 그런데 issue1 브랜치를 변경하는 동안 master 브랜치에서도 변경 사항이 발생하면 어떻게 될까요?

상황을 시각화하면 다음과 같을 것입니다. issue1에서 커밋이 한 번 진행되는 동안 master 브랜치에서도 커밋이 진행되었습니다. 이럴 경우 master 브랜치가 단순히 앞으로 가는 fast-forward 병합은 불가능합니다.

이런 상황에서 병합은 non-fast-forward로 진행됩니다. 병합을 진행하면 두 브랜치가 병합되는 새로운 커밋이 생기게 됩니다. 만약 서로 다른 부분을 수정했다면 문제 없이 병합되겠지만, 같은 줄을 수정했다면

  • issue1의 변경 사항을 선택
  • master의 변경 사항을 선택
  • 둘 다 선택

과 같은 의사결정을 내릴 수 있습니다.

브랜치 삭제

병합이 끝나고 필요 없다고 판단되는 브랜치는 $ git branch -d <브랜치명> 명령어로 삭제할 수 있습니다.

rebase

이번에도 아까와 비슷한 상황입니다. issue1에서 변경이 일어나는 동시에 master 브랜치에서도 변경이 발생하였습니다.

이 상태에서 merge를 사용하면 non-fast-forward병합이 이루어질 것입니다. 그러나 rebase를 사용하면 다른 방법으로 브랜치를 병합할 수 있습니다. rebase는 간단히 말해 issue1이 분기되는 커밋부터 마지막 커밋까지를 잘라서 master 브랜치의 끝에 붙이는 것입니다. 즉, issue1 브랜치에 master의 최신 커밋을 반영한다고 말할 수 있을 것입니다.

rebase를 할 때는 merge와는 반대로 issue1 브랜치에 HEAD를 둔 상태에서 $ git rebase master 명령어를 입력합니다.

rebase 과정을 시각화한 자료입니다. 위의 issue1 브랜치 전체가 master 브랜치의 끝에 붙음으로써 master 브랜치의 최신 커밋이 반영됩니다. 이로써 isue1은 master의 모든 변경사항을 포함하게 되었습니다. 여기서 master 브랜치로 이동한 후 merge하면 fast-forward 병합이 일어나게 됩니다.

지금까지 브랜치를 만들고 병합하는 과정을 간단히 알아보았는데요, 이런 과정이 현업에서는 주로 어떻게 사용되고 있는지 살펴보겠습니다.

'성공적인 Git 브랜칭 모델'

개발 과정에서는 다양한 브랜치가 만들어지고 병합됩니다. 이렇게 브랜치를 만드는 과정에 대한 일종의 표준적인 모델이 있는데요, 이 모델에서는 크게 네 종류의 브랜치를 구분하여 개발을 진행합니다.

  • 메인 브랜치
  • 피처 브랜치/토픽 브랜치
  • 릴리스 브랜치
  • 핫픽스 브랜치- 핫픽스 브랜치

출처 : 누구나 쉽게 이해할 수 있는 Git 입문 https://backlog.com/git-tutorial/kr/stepup/stepup1_5.html

메인 브랜치

메인 브랜치에 해당하는 것은 master 브랜치와 develop 브랜치가 있습니다.

master

master 브랜치는 배포 가능한 상태만을 관리하는 브랜치입니다. 커밋할 때는 태그를 사용하여 버전을 기록합니다.

develop

develop 브랜치는 개발의 기반이 되는 브랜치로, 통합 브랜치의 역할을 합니다.

피처 브랜치

develop 브랜치에서 갈라져 나오는 피처 브랜치는 새로운 기능을 추가하거나 버그 수정이 필요할 때 분기하는 브랜치입니다. 피처 브랜치에서 개발이 완료되면 develop 브랜치로 병합됩니다.

릴리스 브랜치

릴리스 브랜치는 버그 수정 및 기능 개발이 끝난 상태로 모든 기능이 정상적으로 동작하는지 확인합니다. develop 브랜치에서 master 브랜치로 가기 전에 들리는 최종 점검 장소라고 할 수 있습니다. 만약 릴리스 브랜치에서 수정 사항이 발생했다면 develop 브랜치에도 병합을 통해 변경 사항을 적용해주어야 합니다.

핫픽스 브랜치

핫픽스 브랜치는 말 그대로 master 브랜치에서 긴급한 오류가 발생했을 때 빠른 수정을 담당하는 브랜치입니다. develop 브랜치에서 개발 작업을 진행하는 것과는 독립적으로 master 브랜치의 오류를 수정할 수 있습니다. 역시 핫픽스 브랜치에서 생긴 수정 사항은 develop 브랜치에도 반영해주어야 합니다.

충돌 해결하기

조금 헷갈릴 수 있는 개념들인데요, 이번에는 지금까지 살펴본 내용을 바탕으로 충돌 상황을 일부러 만들어 해결하는 연습을 해보겠습니다.

merge 사용하기

먼저, merge를 사용해 브랜치를 합치는 방법입니다.

  1. test 폴더를 생성하고 폴더 안에 file.txt 파일을 만듭니다.
  2. $ git branch issue1, $ git branch issue2 명령어로 브랜치 두 개를 만듭니다.
  3. $ git checkout issue2 명령어로 issue2 브랜치로 이동합니다.
  4. file.txt를 열어 첫 줄에 "첫 번째 줄"이라고 입력합니다.
  5. 커밋을 진행합니다.

현재까지의 진행 상황을 시각화한 모습입니다. master와 issue1 브랜치에는 변화가 없고 issue2 브랜치에서 커밋이 생겼습니다.

  1. $ git checkout issue1 명령어로 issue1 브랜치로 이동합니다.
  2. file.txt를 열어 첫 줄에 "첫 번째 줄!"이라고 입력합니다.
  3. 커밋을 진행합니다.

issue1에서도 커밋이 진행되어 다음과 같아졌습니다. issue1과 issue2에서 독립적인 작업이 진행된 것을 볼 수 있습니다. 이제 master 브랜치에 issue1의 변경 사항을 병합해보겠습니다.

  1. $ git checkout master로 이동합니다.
  2. $ git merge issue1 명령어로 issue1 브랜치를 병합합니다.

병합 결과입니다. issue1 브랜치가 생긴 이후로 master 브랜치에 아무런 변화가 없었기 때문에 fast-forward 병합이 일어난 것을 볼 수 있습니다. 이제 master 브랜치에 issue2 브랜치를 병합합니다.

  1. $ git merge issue2 명령어를 실행합니다.
$ git merge issue3
CONFLICT (add/add): Merge conflict in file.txt
Auto-merging file.txt
Automatic merge failed; fix conflicts and then commit the result.

명령어를 실행하면 다음과 같은 충돌이 발생합니다. master 브랜치는 issue1과 병합되어 file.txt에 첫 번째 줄이 들어가있는데 issue2 브랜치의 file.txt에도 첫 번째 줄이 입력되어 있기 때문입니다. 이런 상황에서는 충돌이 발생한 파일을 열어 충돌을 직접 해결해주어야 합니다.

<<<<<<< HEAD
첫 번째 줄!
=======
첫 번째 줄
>>>>>>> issue3

Git에서는 충돌이 발생하면 어떤 부분에 충돌이 생겼는지를 파일에 직접 표시해줍니다. file.txt를 열면 현재 HEAD의 내용과 병합하려는 브랜치의 내용이 함께 표시되어 있습니다. 이번 실습에서는 두 변경 사항을 모두 받아들이기로 합니다. Git에서 삽입한 부분을 모두 지우고 두 문장만 남겨줍니다.

첫 번째 줄!
첫 번째 줄
  1. 이렇게 바뀐 file.txt를 커밋해줍니다.

이제 모든 병합이 끝났습니다. 마지막으로 만든 커밋이 merge 커밋입니다. non-fast-forward 병합이 일어난 것을 볼 수 있습니다.

rebase

이번에는 마지막 충돌 해결 부분만 rebase를 써서 다시 진행하겠습니다.

  1. $ git reset --hard HEAD~ 명령어로 방금 전 merge 커밋을 취소합니다.

이제 다시 예전과 같은 상태가 되었습니다. 여기서 issue2 브랜치를 master 브랜치에 병합하지 말고 rebase를 사용해서 issue2 브랜치에 master 브랜치의 최신 커밋을 반영합니다.

  1. $ git checkout issue2 명령어로 브랜치를 이동합니다.
  2. $ git rebase master로 master 브랜치에 리베이스 합니다.

이번에도 아까와 같은 이유로 rebase 도중에 충돌이 발생하게 됩니다. file.txt를 열어 특수문자를 지우고 두 브랜치의 내용을 모두 받아들입니다.

첫 번째 줄!
첫 번째 줄

이제 rebase를 계속 진행할 수 있습니다. rebase의 경우 새로운 커밋을 만드는 것이 아니라 add--continue 옵션을 줘서 리베이스를 계속 진행합니다.

  1. $ git add file.txt
  2. $ git rebase --continue 명령어로 리베이스를 마칩니다.

issue2가 리베이스 되는 모습입니다. 이제 issue2는 master 브랜치의 모든 변경 사항을 포함하고 있습니다. 이 상태에서 master 브랜치에 issue2 브랜치를 병합합니다.

  1. $ git checkout master
  2. $ git merge issue2

이제 master 브랜치가 issue2의 모든 변경 사항을 포함하게 됐습니다. 아까와는 다르게 fast-forward 병합이 이루어졌습니다.

이번에는 브랜치와 병합, 그리고 까다로운 오류 처리에 대해 작성하였는데요, 다음 포스팅에서는 심화된 오류 처리와 원격 저장소에 대해 작성하겠습니다.


<참고 문서>
profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글