2017. 12. 1. 20:31ㆍGit
Feature Branch Workflow
2. 팀 구성원간에 소통을 활성화하여 협업 성과를 이끌어 내기 위한 Feature Branch Workflow
앞선 글에서 봤듯이 몇 개의 Git 명령어만으로도 Subversion의 작업 흐름을 그대로 재현할 수 있다.
Centralized Workflow는 Git의 특장점인 분산 버전 관리의 이점은 누리지 못한다.
Centralized Workflow를 이용하면서 협업을 좀 더 유연하게 하려면 Feature Branch Workflow 작업 흐름을 따라가면 된다.
개발할 기능을 개별 브랜치로 분리함으로써, master 브랜치에 새로 개발한 기능을 병합하기 전에 충분한 토론을 할 수 있다는 장점이 있다.
Feature Branch Workflow의 핵심 컨셉은 기능별 브랜치를 만들어서 작업한다는 사실이다.
기능 개발 브랜치는 격리된 작업 환경을 제공하기 때문에 다수의 팀 구성원이 master를 중심으로 해서 안전하게 새로운 기능을 개발할 수 있다.
따라서 master 브랜치는 항상 버그 프리 상태로 유지할 수 있어, 지속적 통합(Continuout Integration)을 적용하기도 수월하다.
또, 풀 리퀘스트를 적용하기도 쉽다.
2.1. 작동원리
이 워크플로우도 프로젝트의 공식적인 변경 이력을 기록하기 위해서 중앙 저장소와 master 브랜치를 사용한다.
그런데, master 브랜치에 직접 커밋하지는 않고, 새로운 기능을 개발할 때마다 브랜치를 만들어서 작업한다.
보통 animated-menu-items
또는 issue-#1061
처럼 의미를 담고 있는 브랜치 이름을 사용한다.
새로 만든 기능 개발용 브랜치도 중앙 저장소에 올려서 팀 구성원들과 개발 내용에 대한 의견(코드 리뷰 등)을 나눌 수 있다.
master 브랜치를 손대지 않기 때문에 다른 기능 개발 브랜치를 얼마든지 올려도 된다.
이는 일종의 로컬 저장소 백업 역할을 하기도 한다.
2.2. 풀 리퀘스트
브랜치를 이용하면 격리된 영역에서 안전하게 새 기능을 개발할 수 있을 뿐만아니라, 풀 리퀘스트를 이용해서 브랜치에 대한 팀 구성원들의 토론 참여를 이끌어 낼 수도 있다. 기능 개발을 끝내고 master에 바로 병합하는 것이 아니라, 브랜치를 중앙 저장소에 올리고 master에 병합해달라고 요청하는 것이 풀 리퀘스트다.
풀 리퀘스트는 특정 브랜치에 대한 코드 리뷰의 시작점이라 볼 수 있다. 따라서 기능 개발 초기 단계에 미리 풀 리퀘스트를 보낸다고 문제될 것은 없다. 예를 들어, 기능 개발 중에 막히는 부분은 미리 풀 리퀘스트를 던져서 다른 팀 구성원들의 도움을 받을 수도 있을 것이다. 풀 리퀘스트에 포함된 각 커밋에 팀 구성원들이 의견을 제시할 수 있고, 새로운 의견이 등록되면 토론 참여자들에게 알림을 보낼 수도 있다.
팀 구성원이 풀 리퀘스트를 수용(또는 합의)하면, 그 이후의 작업 절차는 Centralized Workflow와 거의 같다.
먼저 로컬 master가 최신 상태인지 확인한 후, 기능 개발 브랜치를 master브랜치에 병합하고, 원격저장소로 푸시하면 된다.
2.3. 연습
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | jaehyukshin$ git status HEAD detached from ed54b6b nothing to commit, working tree clean jaehyukshin$ git checkout master Warning: you are leaving 1 commit behind, not connected to any of your branches: e750d7e Cristiano Ronaldo's work added If you want to keep it by creating a new branch, this may be a good time to do so with: git branch <new-branch-name> e750d7e Switched to branch 'master' Your branch is up-to-date with 'origin/master'. jaehyukshin$ git branch RonaldoBranch e750d7e jaehyukshin$ git checkout RonaldoBranch Switched to branch 'RonaldoBranch' | cs |
rebase 이후에 origin master에 올라간 사항 이외에 로컬 변경사항들을 관리하기 위해서 다른 브랜치를 생성해서 master와 별개로 관리를 하라고 추천한다.
git branch RonaldoBranch e750d7e
명령으로 브랜치를 생성하고 RonaldoBranch로 체크아웃해서 로컬 변경사항들을 RonaldoBranch 원격저장소에 푸시해준다.
git push -u origin RonaldoBranch
---
위와 같은 방법으로 브랜치를 만들어줄 수 있고, 또는 이렇게 새로 브랜치를 만들면 된다,
git checkout -b miae-feature master
checkout은 브랜치로 체크아웃하는 명령인데,
-b 옵션을 주게 되면 체크아웃하려는 브랜치가 없으면 master 브랜치를 기준으로 해서 만들 수 있다.
이 브랜치에서는 평상시 하던대로 새로운 기능을 개발하고 변경 내용을 커밋하면 된다.
git push -u origin RonaldoBranch
기능 브랜치를 중앙 저장소(origin)에 푸시하는 명령이다.
팀마다 다르지만 Feature Branch Workflow를 따르는 팀은 개발자가 직접 master에 병합하지 않고, 풀 리퀘스트를 이용하는 규칙을 가지고 있다.)
master 브랜치에 병합하기 전에 풀 리퀘스트를 던져서 팀 구성원들에게 작업 완료 사실을 알려야 한다.
물론 그 전에 중앙 저장소에 작업 내용을 올려야 한다.
---
# master branch
Messi 버튼을 누르면 MessiShoot()이라는 메소드가 실행이 된다.
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 | import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { Button messiButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); messiButton = (Button)findViewById(R.id.messiButton); messiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MessiShoot(); } }); } public void MessiShoot(){ Toast.makeText(getApplicationContext(), "Messi shoots in the corner of the left post", Toast.LENGTH_SHORT).show(); } } | cs |
# RonaldoBranch
Messi 버튼과 Ronaldo 버튼이 있는데, Messi 버튼에는 기능이 없다.
Ronaldo 버튼은 누르면 RonaldoPass()라는 메소드가 실행이 된다.
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 30 31 32 33 34 35 36 37 38 | import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { Button messiButton; Button ronaldoButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); messiButton = (Button) findViewById(R.id.messiButton); messiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getApplicationContext(), "FC Barcelona", Toast.LENGTH_SHORT).show(); } }); ronaldoButton = (Button) findViewById(R.id.ronaldoButton); ronaldoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { RonaldoPass(); } }); } public void RonaldoPass(){ Toast.makeText(getApplicationContext(), "Ronaldo passes the ball", Toast.LENGTH_SHORT).show(); } } | cs |
RonaldoBranch의 작업 내용과 master branch의 작업 내용을 병합을 해서
Messi 버튼과 Ronaldo 버튼을 누르면 각각 MessiShoot()메소드와 RonaldoPass() 메소드가 실행이 되게 병합을 하려고 한다.
우선 RonaldoBranch에서 작업하고 있는 사람이 master 프로젝트를 관리하는 사람에게 풀 리퀘스트를 보낸다.
그러면 관리하는 팀장은 풀 리퀘스트를 받고, 작성된 코드를 검토해서 수정이 더 필요한지, 아니면 병합을 할 것인지 판단해서 RonaldoBranch에 알려준다.
병합은 팀장이든 RonaldoBranch 개발 브랜치 작업하는 사람이든 어느 누가 해도 상관이 없다.
git checkout master
git pull
git pull origin RonaldoBranch
git push -u origin master
먼저 master브랜치로 체크아웃하고 최신 상태인지를 확인해야 한다. - git pull
그 다음 RonaldoBranch 브랜치를 로컬로 가져와야 하는데 로컬에 이미 해당 브랜치가 있다면 최신 상태로 병합한다.
그냥 git merge RonaldoBranch 명령을 이용할 수 있지만,
항상 중앙 저장소의 최신 변경 내용을 로컬에 반영하기 위해서 git pull origin RonaldoBranch 명령을 이용하는 것이 좋다.
마지막으로 병합된 master 브랜치를 중앙 저장소로 다시 올린다.
이 과정을 거치면 병합 커밋이 생기는데, 어떤 팀은 코드 베이스에 기능 추가된 이력을 시각적으로 인지할 수 있어서 좋아하는 병합 커밋을 남기는 것을 좋아한다.
만약 기능 추가 이력을 일직선으로 유지하는 것을 선호한다면, 병합 전에 리베이스를 할 수도 있다 (fast-forward 병합이 적용됨).
GUI 도구를 이용하면 이 복잡한 풀 리퀘스트 처리 과정을 ‘수락’ 버튼 하나로 할 수도 있다.
# master에 병합된 코드
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 30 31 32 33 34 35 36 37 38 39 40 41 42 | import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { Button messiButton; Button ronaldoButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); messiButton = (Button) findViewById(R.id.messiButton); messiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MessiShoot(); } }); ronaldoButton = (Button) findViewById(R.id.ronaldoButton); ronaldoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { RonaldoPass(); } }); } public void RonaldoPass(){ Toast.makeText(getApplicationContext(), "Ronaldo passes the ball", Toast.LENGTH_SHORT).show(); } public void MessiShoot(){ Toast.makeText(getApplicationContext(), "Messi shoots in the corner of the left post", Toast.LENGTH_SHORT).show(); } } | cs |
병합 완료된 후 흐름도 그래프
RonaldoBranch 브랜치 이외에도 다른 기능 브랜치들이 있을 수도 있다.
master와 분리된 브랜치에서 작업을 하고 있기 때문에 RonaldoBranch 이외의 기능들은 병합 도중에도 계속 작업을 할 수 있다.
따라서, 이 워크플로우는 격리된 브랜치로 안전하게 작업하다가도 또 필요할 때는 팀 구성원들과 중간 작업을 공유하기도 쉽다.
이 워크플로우의 유연성은 큰 장점이지만, 현장에서 적용할 때 유연성은 독이 될 수도 있다.
특히 팀이 크고, 프로젝트 규모가 크면 브랜치마다 좀 더 특별한 의미를 부여하는 것이 더 낫다.
그래서 기능 개발과, 릴리스, 유지보수를 위해 좀 더 엄격한 워크플로우 사용한다.
# 참고 사이트)
https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow
http://blog.appkr.kr/learn-n-think/comparing-workflows/
'Git' 카테고리의 다른 글
Learn git branch (0) | 2019.05.06 |
---|---|
[Git Collaborating Workflows / Git 협업 흐름] Gitflow Workflow (0) | 2017.12.01 |
How to use Markdown / 마크다운 작성법 (0) | 2017.12.01 |
[Git Collaborating Workflows / Git 협업 흐름] Centralized Workflow / git rebase (0) | 2017.11.30 |
git add commit push with Terminal or gitbash (1) | 2017.10.15 |