어제 Git-Flow를 공부하면서 기초 개념과 용어에 대한 혼란이 너무 많이 왔었다. 오늘이 3일차인데, 아직 Git에서 헤매는 부분이 너무 많다.

오늘의 일정은 아래와 같은 일정이었다. 이런 일정을 snippet이라는 용어로 부른다.

[오늘의 업무]

  • Daily Scrum [매일의 상태를 체크]
  • 코드 컨벤션 구체화
  • Git 과제 완료
  • github 초기 설정
  • 기능 정의서 정리

오늘의 업무를 일찍 끝내서 남은 시간은 Git을 공부하는데 투자했다. 먼저, 간단한 용어를 정리하고 Git-Flow에 대한 내용을 정리할 예정이다.

Git vs Github

  • Git은 대표적인 분산 버전 관리 시스템으로, 사용자의 프로젝트에 포함된 파일의 변경 사항을 관리하고 추적하는 도구이다.
  • Github은 Git을 사용하는 프로젝트를 지원하는 웹 호스팅 서비스이다.

#Git Pointer

HEAD

  • 현재 Branch가 가리키는 가장 최근 Commit

Master Branch

  • 가장 기본이 되는 Branch
  • Master branch는 Git에서 기본으로 생성해준다.

origin

  • 원격 Repository를 뜻하는 Alias
  • Alias이므로 이름을 변경할 수 있다.

origin/master

  • 원격 Repository의 Master branch

origin/HEAD

  • 원격 Repository가 바라보는 버전

Gitflow Workflow

  • nvie.com의 빈센트 드리센이 제안한 것이다.
  • 대형 프로젝트에도 적용할 수 있는 좀 더 엄격한 작업 절차를 갖는다.
  • Gitflow Workflow의 핵심 컨셉은 master와 develop, 두 개의 메인 브랜치를 이용한다는 것이다.
    • master branch : 릴리즈 이력을 관리하기 위해 사용. 즉, 배포 가능한 상태만을 관리한다.
    • develop branch : 기능 개발을 위한 브랜치들을 병합하기 위해 사용. (모든 기능이 추가되고 버그가 수정되어 배포 가능한 상태라면 ‘master’ 브랜치에 merge 한다.) 평소에는 이 브랜치를 기반으로 개발을 진행한다.

1.중앙 원격 저장소, 자신의 원격 저장소, 로컬 저장소의 개념

  • 중앙 원격(remote) 저장소

    • 여러 명이 같은 프로젝트를 관리하는 데 사용하는 그룹 계정의 중립된 원격 저장소
    • Organization을 만들어 사용할 수 있다. Organization의 사용자와 저장소는 팀으로 관리되고 저장소의 권한 설정도 팀으로 관리한다.
  • 자신의 원격(remote) 저장소

    • remote repository라고 불린다.
    • 파일이 Github 전용 서버에서 관리되는 원격 저장소
  • 로컬(local) 저장소

    • local repository라고 불린다.
    • 내 PC에 파일이 저장되는 개인 전용 저장소, 지역 저장소의 개념이다.

2. 프로젝트 참여자는 git clone 명령으로 로컬 저장소를 만든다.

git clone 명령으로 중앙 원격 저장소(remote repository)를 복제하여 자신의 로컬 저장소(local repository)를 만들 수 있다. 프로젝트 참여자는 clone 한 이 로컬 저장소에서 작업을 수행하면 된다.

1
2
3
// 터미널에서 자신이 원하는 디렉토리로 이동한 후 clone 명령어 입력

git clone [중앙 remote repository URL]
  • git clone 명령은 아래의 명령들을 포함한 작업이다.
1
2
3
4
5
6
// 해당 디렉토리를 빈 Git 저장소로 만드는 작업
git init
// 현재 작업 중인 Git 저장소에 팀의 중앙 원격 저장소를 추가한다. 이름을 origin으로 짓고 긴 서버 주소(URL) 대신 사용한다. 마치 별명과 같다.
git remote add origin [중앙 remote repository URL]
// 중앙 원격 저장소(origin)의 master 브랜치 데이터를 로컬에 가져오기만 하는 작업
git fetch origin master

fetch와 pull의 차이

  • fetch : 원격 저장소의 데이터를 로컬에 가져오기만 하는 작업
  • pull : 원격 저장소의 데이터를 가져와 자동으로 병합까지 하는 작업
  • 즉, 단순히 원격 저장소의 내용을 확인만 하고 로컬 데이터와 병합은 하고 싶지 않은 경우에는 fetch 명령어를 사용한다.
  • pull = fetch + merge

3.먼저 할 일은 Develop 브랜치를 만드는 것이다.

방법[1] : GUI 도구를 이용한 생성(이 방법을 추천)

‘master’ 브랜치를 기준으로 develop 브랜치를 만든다. 여기서는 Pull Request를 이용할 것이기 때문에 GUI 도구를 이용한 생성 방법을 사용한다.[이 방법을 추천한다고 한다!!]

  • Github 페이지에서 Branch:master를 클릭한 후 새로 생성할 브랜치의 이름을 develop으로 적는다. [이때, branch를 생성할 수 있는 사용자는 Owner 권한이 있는 사용자]
  • 새로 생성한 develop branch를 default branch로 설정해야 한다.
  • develop branch를 default branch로 설정하는 이유는?
    • Git-flow에서 평소에는 develop branch를 기반으로 개발을 진행하기 때문에 git push origin some-feature(내 로컬 저장소의 some-feature branch를 중앙 원격 저장소로 올리는 명령)를 한 후, Github 페이지에서 해당 some-feature branch에 대해 merge를 할 때 중앙 원격 저장소의 ‘master’ branch가 아닌 default로 설정되어 있는 develop에 병합하도록 설정하기 위함이다.

방법[2] : 팀 구성원 중 한 명이 자신의 로컬 저장소에 빈 develop 브랜치를 만들고 중앙 저장소로 푸쉬

  • 이 경우는 팀원 중 한 명이 자신의 로컬 저장소에 빈 develop branch를 생성한다.
    • git branch develop
  • 그 다음, 중앙 원격 저장소(origin)에 develop branch를 푸시한다.
    • git push -u origin develop
1
2
3
4
5
# develop 브랜치 생성
git branch develop

# 중앙 원격 저장소 origin으로 develop branch push
git push -u origin develop
  • 그런 다음 Github 페이지에서 자신이 push한 ‘develop’ branch를 병합해달라는 Pull Request를 날린다.
  • 프로젝트 관리자는 해당 pull request를 merge 하여 새로운 ‘develop’ branch를 중앙 원격 저장소에 생성한다.
  • 그 다음에는 방법[1]과 같은 방법으로 develop branch를 default branch로 설정한다.

4. 팀 구성원 모두가 Gitflow workflow를 적용할 준비를 한다.

이제 팀 구성원들은 중앙 저장소를 복제하고(처음에 clone 했으면 넘어가도 좋다.), 중앙 저장소와 연결된 개발 브랜치를 만들어야 한다.

checkout -b develop origin/develop```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

이제 팀 구성원 모두가 이 워크플로우를 적용하기 위한 준비가 되었다. 다음 단계로 넘어가보자.

## 5. 설명을 위해 현재 로컬에서 작업 중인 branch 위치를 표시한다.

중앙 원격 저장소에는 master, develop branch가 있고, 자신의 로컬 저장소에도 master, develop branch와 로그인 기능을 구현한 feature/login branch가 있다고 가정한다. 또한, 현재는 master branch에서 작업 중이라고 가정하고 아래와 같이 작업 중인 위치를 표시한다.
(Gitflow Workflow에서는 대부분의 작업이 **develop** branch에서 이루어진다.)


<img src="/img/github-collaboration-9.png" width="700" height="500">


## 6. 새로운 기능 개발을 위해 격리된 branch를 만든다.

로컬 저장소에서 branch를 따고, 코드를 수정하고, 변경 내용을 커밋한다. 이때, 'master' branch에서 기능 개발을 위한 브랜치를 따는 것이 아니라 우리가 앞서 설정에서 default branch로 설정한 '**develop**' branch에서 따야한다.


```git
git checkout -b [branch name] develop

# 아래의 두 명령어를 합하면 위와 같다.
git branch [branch name] develo
git checkout [branch name]

7. 로컬 저장소의 새로운 기능 브랜치를 중앙 원격 저장소(remote repository)에 푸시한다.

  • 새로 만든 브랜치(feature/login branch)에 새로운 기능에 대한 내용을 커밋한다.
1
2
3
4
5
6
$ git commit -a -m "Write commit message"

# 위의 명령어는 아래의 두 명령어를 합한 것
$ git add . # 변경된 모든 파일을 스테이징 영역에 추가
$ git add [some-file] # 스테이징 영역 some-file 추가
$ git commit -m "Write commit message" # local 작업 작업 폴더에 history 하나를 쌓는다. [commit]
  • 커밋을 완료했다면, 내가 작업한 내용을 포함한 브랜치(feature/login branch)를 중앙 원격 저장소에 올린다.
  • $git push origin feature/login branch
  • 이는 로컬 저장소의 백업 역할을 할 뿐만 아니라, 다른 팀 구성원들이 나의 작업 내용과 진도를 확인할 수도 있어 좋은 습관이라 할 수 있다.

방법[1] : 팀이 pull request를 이용하는 경우

  • 로컬 저장소의 새로운 기능 브랜치를 중앙 원격 저장소(remote repository)에 push한 후, 프로젝트 관리자에게 자신의 기여분을 반영해 달라는 pull request를 보낸다.
    = 새로 만든 기능 개발용 브랜치도 중앙 저장소에 올려서 팀 구성원들과 개발 내용에 대한 의견(코드 리뷰)등을 나눌 수 있다.
  • 이후에는 모든 팀원이 변경한 코드 내용을 확인하고 마지막으로 확인한 팀원 또는 프로젝트 관리자가 변경 내용을 중앙 원격 코드 베이스에 병합(merge)하는 작업을 한다.
  • 이는 일종의 로컬 저장소 백업 역할을 하기도 한다.

Pull Request란?

  • 기능 개발을 끝내고 master에 바로 병합(merge)하는 것이 아니라, 브랜치를 중앙 원격 저장소에 올리고 병합(merge)해달라고 요청하는 것이다.
  • GUI 도구를 이용한 pull request
    1. Github 페이지에서 Pull Request 버튼을 이용하면, 어떤 branch를 제출할 지 정할 수 있다.
    2. 기능을 구현한 branch(여기서는 feature/login branch)를 프로젝트의 중앙 원격 저장소의 develop branch에 병합해 달라고 요청한다.
  • GUI 도구를 이용한 merge
    1. Github 페이지에서 Pull Request 버튼을 누른 후, File changed 탭에서 변경 내용을 확인한다.
    2. Conversation 탭으로 이동하여 Confirm merge를 하면 중앙 원격 코드 베이스(‘develop’ branch)에 병합된다.
    3. 위에서 ‘develop’ branch를 default branch로 설정했기 때문에 자동으로 ‘develop’ branch로 merge 도니다.
    4. 충돌이 일어난 경우는 팀원들과 합의 하에 충돌 내용을 수정한 후 병합을 진행한다.

방법[2] : 팀이 pull request를 이용하지 않는 경우

  • 기능 브랜치를 병합하기 전에 반드시 자신의 로컬 저장소 develop branch에 중앙 원격 저장소의 변경 내용을 반영해서 최신 상태로 만들어야 한다.
  • 또한 자신이 직접 새로운 기능에 대한 병합을 할 때, ‘master’ branch에 병합하지 않도록 주의해야 한다.
1
2
3
4
5
6
7
# -u 옵션 : 새로운 기능 브랜치와 동일한 이름으로 중앙 원격 저장소의 브랜치로 추가한다.

// 로컬의 기능 브랜치를 중앙 원격 저장소(origin)에 올린다.
$ git push -u origin feature/login branch

// -u 옵션으로 한 번 연결한 후에는 옵션 없이 아래의 명령만으로 기능 브랜치를 올릴 수 있다.
$ git push origin feature/login branch

8. 중앙 원격 저장소와 자신의 로컬 저장소를 동기화하기 위해 로컬 저장소의 branch를 develop branch로 이동한다.

// 로컬 저장소의 branch를 develop branch로 이동
$git checkout develop

9. 중앙 원격 저장소의 코드 베이스에 새로운 커밋이 있다면 다음과 같이 가져온다.

중앙 원격 저장소(origin)의 메인 코드 베이스(‘develop’ branch)가 변경되었으므로, 프로젝트에 참여하는 모든 개발자가 자신의 로컬 저장소를 동기화해서 최신 상태로 만들어야 한다.

  • $git pull origin develop
    • 중앙 원격 저장소(origin)의 변경 내용을 develop 브랜치에 반영해서 동기화한다.

10. 새로운 기능을 추가하기 위해서 그 작업에 대한 branch를 생성하여 작업한다.

  • 중앙 원격 저장소와 동기화된 로컬 저장소의 ‘develop’ branch에서 새로운 작업에 대한 branch를 생성하여 다른 작업을 한다. 앞에서 했던 것과 동일하게 하면 된다.
  • local에서 완성한 이전 작업 브랜치는 삭제한다.
    • $git branch -d feature/login

11. 배포하기

만약 develop 브랜치에서 버전 1.2에 대한 기능이 모두 구현이 완료 되었으면 배포를 위한 전용 브랜치를 사용하여 배포 과정을 캡슐화 한다. 이렇게 함으로써 한 팀이 해당 배포를 준비하는 동안 다른 팀은 다음 배포를 위한 기능 개발을 계속할 수 있다. 이게 Gitflow의 장점이라고 생각한다.

버전 번호를 부여한 새로운 ‘release’ branch는 develop branch로부터 생성한다.

1
2
// develp 브랜치로부터 release 브랜치(release-1.2)를 생성
$git checkout -b release-1.2 develop
  • 이렇게 release 브랜치를 만드는 순간부터 배포 사이클이 시작된다.
    • release 브랜치에서는 배포를 위한 최종적인 버그, 수정, 문서 추가 등 릴리즈와 직접적으로 관련된 작업을 수행한다.
    • 직접적으로 관련된 작업들을 제외하고는 release 브랜치에 새로운 기능을 추가로 병합하지 않는다.
  • release 브랜치에서 배포 가능한 상태가 되면(배포 준비가 완료되면)
    • 배포 가능한 상태 : 새로운 기능을 포함한 상태로 모든 기능이 정상적으로 동작하는 상태를 말한다.
  1. ‘master’ 브랜치에 병합한다.(이때, 병합한 커밋에 release 버전 태그를 부여!)
  2. 배포를 준비하는 동안 release 브랜치가 변경되었을 수 있으므로 배포 완료 후 ‘develop’ 브랜치에도 병합한다.
  3. 작업했던 release 브랜치는 삭제한다. 이때, 다음 번 배포(release)를 위한 개발 작업은 ‘develop’ 브랜치에서 계속 진행해 나간다.

방법[2] : 팀이 pull request를 이용하는 경우

팀이 풀 리퀘스트를 통한 코드 리뷰를 하는 방식을 사용한다면 release 브랜치를 그대로 중앙 원격 저장소에 push 한 후 다른 팀원들의 리뷰나 확인 과정을 거쳐 'master’와 ‘develop’ branch에 병합한다.

12. 버그 수정하기

배포한 버전에 긴급하게 수정을 해야 할 필요가 있을 경우(버그 발견…!), ‘master’ 브랜치에서 직접 브랜치[hotfix 브랜치]를 만들어 필요한 부분만을 수정한 후 다시 ‘master’ 브랜치에 병합하여 이를 배포해야 한다.

‘develop’ 브랜치에서 문제가 되는 부분을 수정하여 배포 가능한 버전을 만들기에는 시간도 많이 소요되고 안정성을 보장하기도 어렵기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// hotfix 브랜치(hotfix-1.2.1)를 'master' 브랜치에서 분기(유일함)
$ git chekcout -b hotfix-1.2.1 master

/*문제가 되는 부분만을 빠르게 수정한다.*/

/* 필요한 부분을 수정한 후 'master' 브랜치로 이동한다. */
$git checkout master
// 'master' 브랜치에 hotfix-1.2.1 브랜치 내용을 병합한다.
$git merge --no-ff hotfix-1.2.1

// 병합한 커밋에 새로운 버전 이름으로 태그를 부여한다.
$git tag -a 1.2.1

// 'master' 브랜치를 중앙 원격 저장소(origin)에 올린다.
$git push origin master


/* 'hotfix' 브랜치의 변경 사항을 'develop' 브랜치에도 적용 */
// develop 브랜치로 이동한다.
$git checkout develop

// 'develop' 브랜치에 hotfix-1.2.1 브랜치 내용을 병합한다.
$git merge --no-ff hotfix-1.2.1

// 'develop' 브랜치를 중앙 원격 저장소(origin)에 올린다.
$git push origin develop

// -d 옵션 : hotfix-1.2.1에 해당하는 브랜치를 삭제한다.
$git branch -d hotfix-1.2.1
  1. 배포한 버전에 긴급하게 수정을 해야 할 필요가 있을 경우, ‘master’ 브랜치에서 hotfix 브랜치를 분기한다.(‘hotfix’ 브랜치만 master에서 바로 딸 수 있다.)
  2. 문제가 되는 부분만을 빠르게 수정한다.
  3. 다시 ‘master’ 브랜치에 병합(merge)하여 이를 안정적으로 다시 배포한다. 그리고 중앙 원격 저장소(origin)에 올린다.(push)
  4. 새로운 버전 이름으로 태그를 매긴다.
  5. hotfix 브랜치에서의 변경 사항은 ‘develop’ 브랜치에도 병합(merge)한다. 이 develop 브랜치도 중앙 원격 저장소(origin)으로 push한다.
  6. 작업했던 hotfix 브랜치는 삭제한다.

추가 기능

  • fast forward : merge할 때 default로 지정된 방식인데, default로 merge를 할 경우 커밋 이력들이 표시된다.

git checkout -b feature/login develop
git commit
git commit

git checkout develop
git merge feature/login

을 수행하면 feature/login에서 수행했던 2번의 커밋 이력이 develop에 merge할 때 develop의 이력에 같이 표시된다.

  • non fast forward :

참고