1. Git 브랜치
1-1. 브랜치(Branch)
- 본래의 소스코드로부터 파생한 독립적인 작업 공간
- 최신 커밋을 가리키는 일종의 포인터이다.
- 매우 가볍다.
- 생성, 이동, 병합(merge)이 매우 쉽다.
1-2. master 브랜치
- git은 기본적으로 master 브랜치를 생성한다.
- 현재 작업 중인 브랜치 확인하는 명령어
$git branch - master 브랜치는 첫 번째 커밋을 만들어야 생성된 커밋을 가리킬 수 있다.
1-3. 브랜치 실습을 위한 디렉토리 생성
2. 브랜치 실습
2-1. 커밋 생성
- $vi MainService.java
- write code 1 저장 - $git add MainService.java
- $git status
- $git commit -m "Commit 1 on master branch"
- $git log
- $git branch
- $git log
2-2. HEAD
- 현재 브랜치를 가리키는 일종의 포인터
- 현재 브랜치의 마지막 커밋에 대한 스냅샷
2-3. 두 번째 커밋 생성
- $vi MainService.java
- write code 2 저장 - $git add MainService.java
- $git commit -m "Commit 2 on master branch"
- $git log
첫 번째 커밋에 있던 HEAD -> master가 두 번째 커밋에 있다. master 브랜치는 현재 두 번째 커밋을 가리키고 있다.
3. 브랜치 생성과 이동
3-1. 브랜치 생성
- $git branch [생성할 브랜치명]
- $git log
HEAD는 master를 가리키고 있고, feature-login 브랜치는 생성만 되어 있는 상태임을 알 수 있다. feature-login브랜치는 마지막 commit2를 가리키고 있다.
3-2. 브랜치 이동(Checkout)
- $git checkout [이동할 브랜치명]
- HEAD는 checkout 대상 브랜치로 이동한다.
- 로컬 저장소의 상태는 HEAD가 가리키는 마지막 커밋이 최신이 된다.
- 작업 디렉토리의 파일 상태도 변경된다. - $git branch
- $git log
*가 가리키고 있는 것이 현재 브랜치이다. 현재 feature-login브랜치인 것을 알 수 있다.
3-3. 세 번째 커밋 생성
- $vi LoginService.java
- write code 1 저장. - $git add LogineService.java
- $git commit -m "Commit 3 on feature-login branch"
- $git log
두 번째 커밋은 master 브랜치를 가리키고 있고, 생성한 세 번째 커밋은 HEAD와 feature-login브랜치가 가리키고 있다.
3-4. master 브랜치로 이동
- $git checkout master
- $git log
feature-login 브랜치에서 LoginService.java파일을 생성하였다. master브랜치에 LoginService.java파일은 없다. 각각의 브랜치는 독립된 공간이기 때문에 병합을 하기 전까지 독립적으로 운영된다. 또한 세 번째 커밋은 feature-login브랜치에서 했기 때문에 master브랜치에서 두 번째 커밋까지 보인다.
3-5. 커밋 생성
- $vi MainService.java
- write code 3 저장 - $git add MainService.java
- $git commit -m "Commit 4 on master branch"
- $git log
커밋 히스토리를 확인하면 두 번째 커밋 뒤에 네 번째 커밋이 생성된 것을 확인할 수 있다.
- 브랜치와 상관없이 모든 브랜치의 커밋 확인
$git log --all - 그래프 형식으로 보고 싶다면
$git log --all --graph
4. 브랜치 병합(merge)
개발 중 이슈가 발생하면 브랜치는 어떻게 운용될까?
master브랜치에서 issue라는 브랜치를 생성한 뒤 해당 이슈는 이 브랜치에서 관리하고 해결하는 방법이 있다. 그런데 브랜치 전략은 조직마다 개인마다 모두 다르다.
4-1. 브랜치 생성
- master 브랜치에서 issue라는 브랜치 생성하며 issue 브랜치로 이동
$git checkout -b issue
4-2. 커밋 생성
- $vi MainService.java
fix issue-number 1 저장 - $git add MainService.java
- $git commit -m "Commit 5 on issue branch"
- $git log
issue 브랜치에서 문제가 해결되었다면 최종적으로 master 브랜치에서 합병이 되어야 한다. 그래야 변경된 사항이 코드에 적용이 될 수 있다. 이러한 작업을 브랜치 합병 또는 브랜치 merge라고 한다.
4-3. 브랜치 병합(merge)
- 기준이 되는 브랜치로 이동해서 병합해야 한다.
issue 브랜치 -> master 브랜치
$git checkout master - 합쳐질 브랜치를 병합한다.
$git merge issue - $git log
Fast-Forward Merge
브랜치의 위치만 최신 커밋으로 이동시키는 방식
master 브랜치에서 issue라는 브랜치를 만들어 이슈를 해결하였다. 최종적으로 issue 브랜치의 변경사항들을 master브랜치에 merge를 했다. 현재 master가 가리키는 대상과 issue가 가리키는 대상이 동일한 상황이다. issue브랜치가 더 이상 필요하지 않은 경우 효과적인 운영을 위하여 삭제하는 것이 좋다.
각각의 브랜치들이 어떤 커밋을 가리키고 있는지 확인.
- $git branch -v
브랜치 삭제
- $git branch -d [삭제할 브랜치 이름]
브랜치를 삭제하면 소스코드, 커밋들이 삭제되는 것이 아니라 issue라는 브랜치, 일종의 포인터가 삭제되는 구조이다.
따라서 git의 브랜치는 생성, 이동, 삭제가 매우 빠른 특징이 있다.
기능 개발이 완료되면 해당 기능에서의 변경점들을 master 브랜치로 merge 하는 작업이 필요하다. feature-login브랜치를 master로 merge 하는 과정을 실습한다.
- 기준이 되는 브랜치로 이동해서 병합해야 한다.
feature-login 브랜치 -> master 브랜치
$git branch - 목표 브랜치를 병합한다.
$git merge feature-login
3-way Merge
아래 3개 커밋을 모두 고려하여 병합하는 방식으로 3-way merge의 결과는 새로운 커밋으로 생성된다.
- master와 feature-login 브랜치의 공통 부모 커밋
- master 브랜치의 최신 커밋
- feature-login 브랜치의 최신 커밋
merge 하고 난 뒤의 메시지에서 위에서 진행한 merge와 달리 Fast Forward방식이라는 언급이 없다.
master브랜치에서 feature-login브랜치를 생성한 뒤에 master에는 Commit4, Commit5 두 개의 커밋이 생성되었다. 이때는 master 브랜치로 이동한다고 해도 merge 할 수 없는 상황이다. 때문에 Fast Forward방식으로는 merge가 불가능하다.
이때 git은 master와 feature-login의 공통 커밋인 Commit2를 찾게 되고 master의 최신 커밋인 Commit 5, feature-login의 최신 커밋인 Commit 3 총 3개의 커밋으로 merge를 진행한다. 이러한 방식을 3-way Merge라 한다.
5. 충돌(conflict)
브랜치 병합은 항상 성공하는가?
Commit3은 LoginService.java, Commit5는 MainService.java 즉, 서로 다른 파일에 대해서 수정하였다. 따라서 merge 했을 때 동일한 코드를 건드리는 일은 발생하지 않는다. 하지만 현업에서 브랜치 merge는 굉장히 빈번하게 실패한다.
실패하는 케이스를 확인하기 위해 feature-login 브랜치에서 MainService.java파일을 수정한 Commit6을 생성해본다.
- $git checkout feature-login
- $vi MainService.java
write code 3 on feature login branch 저장 - $git add MainService.java
- $git commit -m "Commit 6 on feature-login branch"
- $git log -u
- feature-login 브랜치 내용 -> master 브랜치로 병합하기
충돌(conflict)
이 두 개의 변경점이 서로 merge 될 때 세 번째 줄에서 conflict가 발생한다. 이전까지의 merge방식은 git이 알아서 merge 하여 최종 merge 커밋까지 자동으로 생성했었다.
하지만 현재 상황과 같이 git이 위의 두 가지 변경점 중 어떤 변경점을 MainService.java에 반영할지 판단할 수 없다. 그렇기 때문에 conflict를 발생시켜 사용자로 하여금 해당 conflict를 해결하도록 한다.
conflict를 해결한다는 것은
- 개발하는 기능의 목적에 맞게
- 어떤 변경사항을 어떻게 반영할지를 결정하고
- 수정하여 반영하는 것을
- conflict을 해결하는 과정
conflict는 두 가지 방식으로 해결할 수 있다.
- 직접 merge 하기
- Tool을 이용해서 merge 하기
5-1. 직접 merge 하기
git은 충돌을 하게 되면 해당 파일 안에 HEAD~feature-login 같은 부분을 추가한다.
HEAD부터 ======까지 의미하는 것은 HEAD가 가리키는 즉, master 브랜치의 변경점을 의미한다.
======부터 feature-login은 feature-login 브랜치의 변경점을 의미한다.
이 두 가지 변경점을 비교하여 개발자가 직접 수정하도록 한다.
5-2. mergetool 사용하기
- $git mergetool
- 왼쪽 - master브랜치
- 중간 - 충돌한 브랜치의 공통부분
- 오른쪽 - feature-login 브랜치
- 아래 - 현재 파일의 상태
- 아래 창에서 원하는 부분을 수정하여 저장
- git add MainService.java
- git commit
- 자동으로 merge 되었다는 commit 생성됨.
6. Git의 Tag
Git에서 Tag는 특정 브랜치 위에서 생성된다. 특정 시점에 tag이름으로 꼬리표가 달리는데, 실제 어떤 git에서 tag안에서 어떤 정보가 저장되고, 어떤 지점에 생성되는지 알아보도록 한다.
- 태그는 특정 시점의 소스코드 정보를 기록함
- 프로젝트 진행 중 의미 있는 시점의 커밋을 태깅한 것
- 의미 있는 시점이란?
- 1차 목표 기능 개발 완료되었을 때
- 매우 중요한 이슈가 해결되었을 때
- 기능 개발 완료 및 테스트까지 모두 완료하여 통과하였을 때
- 고객에게 소프트웨어를 배포할 때
위에서 Git 브랜치 이해를 위한 실습을 자세히 진행하였다. master라는 기본 브랜치 위에 feature-login 브랜치를 생성하여 기능을 개발하고, issue브랜치를 생성하여 issue를 해결하는 새로운 커밋들을 생성했었다. 모든 작업이 완료되고 각각의 브랜치 상의 변경점들을 master 브랜치로 병합하는 실습을 진행했다. 모든 작업이 완료되고 의미있는 시점이라 판단하여 v1.0이라는 태그를 태깅해본다.
6-1. Git 태그의 종류
- Lightweight 태그
- 버전명과 같은 태그명만 남기는 태그 - Annotated 태그
- Git 데이터베이스에 태그를 만든 사람의 이름, 이메일, 태그 생성 날짜, 태그 메시지 등을 저장한 태그
- Annotated 태그가 더 많은 정보를 저장하기 때문에 많이 사용된다.
6-2. Git 태그 생성하기
- Lightweight 태그 생성
$git tag [태그명] - Annotated 태그 생성
$git tag -a [태그명] -m [태그 메시지]
6-3. Annotated 태그 생성
- $git tag -a v1.0 -m "Implemented login feature"
- $git log
6-4. 태그 정보 확인하기
- $git show v1.0
6-5. 특정 시점의 커밋 태그 하기
- 태깅하고자 하는 커밋의 ID값 확인
$git log --oneline -> 간결하게 커밋 내역 확인
- 노란 부분이 커밋의 ID
- 커밋 ID값을 인자로 태깅하기
$git tag -a v0.1 [커밋 ID] -m "fix issue number-1"
$git log --oneline
- $git show v0.1
6-6. 태그 활용 전략
- Git을 이용한 태그 생성 시점은 조직마다 다를 수 있다.
- 태그 생성 시점?
- 태그명 규칙?
- 태그 메시지 규칙?
중요한 것은 소스코드의 효율적인 관리를 위해 태그 생성 시점과 방법에 대해서 일관성 있는 규칙을 정해 프로젝트 팀원 모두가 준수할 수 있도록 정책화해야 한다.