본문 바로가기
프로그래밍 놀이터/Tips

[도서 정리] 5. 분산 환경에서의 Git - ProGit

by 돼지왕 왕돼지 2020. 1. 9.
반응형

5. 분산 환경에서의 Git - ProGit



5.1. 분산환경에서의 워크플로


-

중앙집중형 버전 관리 시스템(CCVS)에서는 각 개발자는 중앙 저장소를 중심으로 하는 하나의 노드일 뿐이다.

하지만 git 에서는 각 개발자의 저장소가 하나의 노드이기도 하고 중앙 저장소 같은 역할도 할 수 있다.

즉, 모든 개발자는 다른 개발자의 저장소에 일한 내용을 전송하거나, 다른 개발자들이 참여할 수 있도록 자신이 운영하는 저장소 위치를 공개할 수도 있다.




* 중앙집중식 워크플로




* Integration-Manager 워크폴로


-

다른 개발자는 읽기만 가능하고 자신은 쓰기도 가능한 공개 저장소를 만드는 워크플로.

이 워크플로에는 보통 프로젝트를 대표하는 하나의 공식 저장소가 있다.

기여자는 우선 공식 저장소를 하나 clone 하고 수정하고 나서 자신의 저장소에 push 한다.

그 다음 프로젝트 integration-manager 에게 새 저장소에서 pull 하라고 요청한다.

그러면 integration-manager 는 기여자의 저장소를 리모트 저장소로 등록하고, 로컬에서 기여물을 테스트하고, 프로젝트 메인 브랜치에 merge 하고, 그 내용을 다시 프로젝트 메인 저장소에 push 한다.



-

이 방식은 github 이나 gitlab 같은 hub 사이트를 통해 주로 사용하는 방식이다.

프로젝트를 fork 하고 수정사항을 반영하여 다시 모두에게 공개하기 좋은 구조로 돼 있다.

이 방식의 장점은 기여자와 integration-manager 가 각자의 사정에 맞춰 프로젝트를 유지할 수 있다는 점이다.

기여자는 자신의 저장소와 브랜치에서 수정 작업을 계속해 나갈 수 있고 수정사항이 프로젝트에 반영되도록 기다릴 필요가 없다.

관리자는 여유를 가지고 기여자가 push 해 놓은 커밋을 적절한 시점에 merge 한다.




* Dictator 와 Lieutenant 워크플로


-

저장소를 여러 개 운영하는 방식을 변형한 구조이다.

보통 수백 명의 개발자가 참여하는 아주 큰 프로젝트를 운영할 때 이 방식을 사용한다.

리눅스 커널 프로젝트가 대표적이다.



-

여러 명의 Integration-manager 가 저장소에서 자신이 맡은 부분만을 담당하는데 이들을 lieutenants 라고 부른다.

모든 Lieutenant 는 최종 관리자 아래에 있으며 이 최종 관리자를 Dictator 라고 부른다.

최종 관리자가 관리하는 저장소를 공식 저장소로 하며 모든 프로젝트 참여자는 이 공식 저장소를 기준으로 작업한다.



-

이 방식은 일반적이지는 않지만 깊은 계층 구조를 가지는 환경이나 규모가 큰 프로젝트에서는 매우 쓸모 있다.




* 워크플로 요약


-

위에서 소개한 워크플로 외에도 다양한 변종 워크플로가 실제로 사용된다.





5.2. 프로젝트에 기여하기


-

기여하는 방식에 영향을 끼치는 몇 가지 변수가 있는데,

활발히 기여하는 개발자의 수가 얼마인가, 선택한 워크플로가 무엇인가, 각 개발자에게 접근 권한을 어떻게 부여했는가, 외부에서도 기여할 수 있는가 등이 변수이다.



-

활발한 개발자는 얼마나 많은 개발자가 얼마나 자주 코드를 쏟아 내는가를 말한다.

아주 큰 프로젝트는 수백, 수천 명의 개발자가 하루에도 수십, 수백 개의 커밋을 만들어 낸다.

개발자가 많으면 많을수록 코드를 깔끔하게 적용하거나 머지하기 어려워진다.

어떤 커밋은 다른 개발자가 이미 기여했기에 불필요해지기도 하고 때론 서로 충돌이 일어난다.




* 커밋 가이드라인


-

Git 프로젝트의 Documentation/SubmittingPatches 문서를 참고하면 좋다.



-

무엇보다도 먼저 공백문자를 깨끗하게 정리하고 커밋해야 한다.

Git 은 공백 문자를 검사해볼 수 있는 간단한 명령을 제공한다.

커밋 하기 전에 git diff —check 명령으로 공백문자에 대한 오류를 확인할 수 있다.

$ git diff --check


커밋을 하기 전에 공백문자에 대해 검사를 하면 공백으로 불필요하게 커밋되는 것을 막고 이런 커밋으로 인해 불필요하게 다른 개발자들이 신경 쓰는 일을 방지할 수 있다.



-

각 커밋은 논리적으로 구분되는 Changeset 이다.

최대한 수정사항을 하나의 주제로 요약할 수 있어야 하고 여러 가지 이슈에 대한 수정사항을 하나의 커밋에 담지 않아야 한다.

여러 가지 이슈를 한꺼번에 수정했다고 하더라도 Staging area 를 이용하여 한 커밋에 하나의 이슈만 담기도록 한다.

작업 내용을 나누고, 커밋마다 적절한 메시지를 작성한다.



-

커밋 메시지의 첫 라인은 50자가 넘지 않는 아주 간략한 메시지를 적어 해당 커밋을 요약한다.

다음 한 라인은 비우고 그 다음 라인부터 커밋을 자세히 설명한다.

개발 동기, 구현 상황의 제약조건이나 상황 등을 자세하게 쓴다.

명령문으로 시작하는 것도 좋은 방법이다. (테스트를 추가함 -> 테스트를 추가)




* 비공개 소규모 팀




* 비공개 대규모 팀




* 공개 프로젝트 fork


-

공개 팀을 운영할 때에는 모든 개발자가 프로젝트의 공유 저장소에 직접 쓰기 권한을 가지지 않는다.

그래서 프로젝트 관리자는 몇 가지 일을 더 해줘야 한다.



-

fork 가 되면 프로젝트 저장소에서 갈라져 나온, 쓰기 권한이 있는 저장소가 하나 만들어진다.

그러면 로컬에서 수정한 커밋을 fork 한 저장소에 push 할 수 있다.

그 저장소를 로컬 저장소의 리모트 저장소로 등록한다.

작업 후 branch 를 push 하고, 프로젝트 관리자에게 이 내용을 알린다. 이것을 pull request 라고 한다.

git 호스팅 사이트에서 관리자에게 보낼 메시지를 생성하거나 git request-pull 명령으로 이메일을 수동으로 만들 수 있다.

$ git request-pull [topic_branch_base_branch_name] [topic_branch_remote_name(url)]


request-pull 명령은 아규먼트를 두 개 입력받는다.

첫번째는 작업한 토픽 브랜치의 base 브랜치이다.

두번째는 토픽 브랜치가 위치한 저장소 url 이다.



-

merge 할 때 conflict 가 나면, contributor 가 conflict 를 해결한 후 다시 pull request 를 보내야 한다.



-

—squash 옵션은 현재 브랜치에 머지할 때 해당 브랜치의 커밋을 모두 하나의 커밋으로 합쳐서 머지한다.

이 때 머지 커밋은 만들지 않는다.

다른 브랜치에서 수정한 사항을 전부 가져오는 것은 똑같다.

커밋은 부모가 하나이고 커밋을 기록하기 전에 좀 더 수정할 기회도 있다.


—no-commit 옵션을 추가하면 커밋을 합쳐 놓고 자동으로 커밋하지 않는다.




* 대규모 공개 프로젝트와 이메일을 통환 관리


-

대규모 프로젝트는 보통 수정사항이나 patch 를 수용하는 자신만의 규칙을 마련해 놓고 있다.

프로젝트마다 규칙이 다를 수 있으므로 각 프로젝트의 규칙을 미리 알아둘 필요가 있다.

오래된 대규모 프로젝트는 대부분 메일링리스트를 통해서 patch 를 받아들인다.



-

메일을 통해 하는 방법은 프로젝트를 fork 하여 push 하는 것이 아니라 커밋 내용을 메일로 만들어 개발자 메일링리스트에 제출한다.



-

git-format-patch 명령으로 메일링리스트에 보낸 mbox 형식의 파일을 생성한다.

각 커밋은 하나씩 이메일 메시지로 생성되는데 커밋 메시지의 첫 번째 라인이 제목이 되고 머지 메시지 내용과 patch 자체가 메일 메시지의 본문이 된다.

이 방식은 수신한 이메일에 들어 있는 패치를 바로 적용할 수 있어서 좋다.


ex) 

$ git format-patch -M origin/master # -M 옵션은 이름이 변경된 파일이 있는지 살펴보라는 옵션이다.



-

생성된 patch 파일의 — 라인과 patch 가 시작되는 라인 (diff —git으로 시작되는 라인) 사이에 내용을 추가하면 추가정보만 입력되고, patch 에 적용되지는 않는다.



-

특정 메일 프로그램을 사용하거나 이메일을 보내는 명령어로 메일링리스트에 보낼 수 있다.

git 에는 patch 메일을 그대로 보낼 수 있는 도구가 있다.

IMAP 프로토콜로 보낸다.








5.3. 프로젝트 관리하기


-

프로젝트를 운영하는 것은 크게 두 가지로 이루어진다.

하나는 format-patch 명령으로 생성한 patch 를 이메일로 받아서 프로젝트에 patch 를 적용하는 것이다.

다른 하나는 프로젝트의 다른 리모트 저장소로부터 변경 내용을 merge 하는 것이다.




* 토픽 브랜치에서 일하기


-

메인 브랜치에 통합하기 전에 임시로 토픽 브랜치를 하나 만들고 거기에 통합해보고 나서 다시 메인 브랜치에 통합하는 것이 좋다.

이렇게 하면 patch 를 적용할 때 이리저리 수정해 보기도 하고 좀 더 고민해 봐야 하면 patch 를 적용해둔 채로 나중으로 미룰 수도 있다.



-

브랜치 이름을 잘 짓는 것이 좋다.

sc 라는 사람이 작업한 patch 라면 sc/ruby_client 처럼 앞에 닉네임을 붙여서 브랜치를 만들 수 있다.


ex) 

$ git branch sc/ruby_client master




* 이메일로 받은 patch 를 적용하기


-

이메일로 받은 패치를 프로젝트에 적용하기 전에 우선 토픽 브랜치에 패치를 적용한다.

패치를 적용하는 방법은 git apply 와  git am 을 사용하는 두 가지 방법이 있다.



-

git diff 나 unix 의 diff 명령으로 만든 patch 를 적용할 때는 git apply 명령을 사용한다.


ex) 

$ git apply /tmp/patch-ruby-client.patch


위 명령은 patch -p1 명령과 거의 같지만, git apply 명령이 patch 명령보다 훨씬 더 꼼꼼하게 비교한다.

git diff 로 생성한 패치 파일에 파일을 추가하거나, 파일을 삭제하고, 파일의 이름을 변경하는 내용이 들어 있으면 그대로 적용된다.

이런 것은 patch 명령으로 할 수 없다.


그리고 git apply 는 모두 적용 혹은 모두 취소 모델을 사용하기 때문에 유용하다.

하지만 patch 명령은 여러 파일에 적용하다가 중간에 실패하면 거기서 중단하기 때문에 깔끔하지 못하다.


git apply 는 자동으로 커밋해 주지 않기 때문에 알아서 커밋해야 한다.


실제로 패치를 적용하기 전에 패치가 잘 적용되는지 시험해보려면 git apply --check 명령을 사용할 수 있다.

$ git apply --check [patch_file_name]



-

프로젝트 기여자가 git 의 format-patch 명령을 잘 사용하면 관리자의 작업은 훨씬 쉬워진다.

format-patch 명령으로 만든 패치 파일은 기여자의 정보와 커밋 정보가 포함되어 있기 때문이다.

그래서 기여자가 diff 보다 format-patch 를 사용하도록 권해야 한다.

git apply 는 기존의 패치 파일에만 사용한다.


format-patch 명령으로 생성한 패치 파일은 git am 명령으로 적용한다.

git am 은 이메일 여러 통이 들어 있는 mbox 파일을 읽어서 패치한다.

mbox 파일은 간단한 텍스트 파일이다.


받은 메일이 git send-email 로 만든 메일이라면 mbox 형식으로 저장하고 이 mbox 파일을 git am 명령으로 적용하면 된다.

사용하는 메일 클라가 여러 이메일을 하나의 mbox 파일로 저장할 수 있다면 메일 여러 개를 한번에 패치할 수 있다.



-

이메일로 받은 것이 아니라 format-patch 명령으로 만든 이슈 트래킹 시스템 같은데 올라온 파일이라면 먼저 내려받고선 git am 명령으로 패치한다.


ex) 

$ git am aa.patch


패치가 성공하면 자동으로 새로운 커밋이 하나 만들어진다.

이메일의 from 과 date 에서 저자 정보가, 이메일의 제목과 메시지에서 커밋 메시지가 추출돼 사용된다.



-

patch 에 실패할 수도 있다.

이러면 git am 명령은 패치를 중단하고 사용자에게 어떻게 처리할지 물어온다.

git 은 머지나 리베이스의 경우처럼 문제를 일으킨 파일에 충돌 표시를 해 놓는다.

패치도 마찬가지로 충돌을 해결하면 된다.

충돌을 해결하고 난 후 git am —resolved 명령을 입력한다.

$ git am --resolved


git am 에 -3 옵션을 줄 수 있다. 이 옵션은 git 에 3-way 패치를 적용해 보라고 하는 것이다.

패치가 어느 시점에서 갈라져 나온 것인지 알 수 없기 때문에 이 옵션은 기본적으로 비활성화 되어 있다.


ex) 

$ git am -3 aaa.patch



-

mbox 파일에 들어 있는 여러 패치를 적용할 때 am 명령의 대화형 방식을 사용할 수 있다.

-i 옵션을 주면 되고, 이 방식을 사용하면 패치를 적용할 때마다 묻는다.




* 리모트 브랜치로부터 통합하기


-

프로젝트 기여자가 자신의 저장소를 만들고 커밋을 몇 번 하고 저장소의 url 과 변경 내용을 메일로 보내왔다면 url 을 리모트 저장소로 등록하고 merge 할 수 있다.

예를 들어 jesicca 가 ruby-client 브랜치에 특정 기능을 만들어 놨다고 메일을 보내왔다면...

$ git remote add jessica git://github.com/jessica/myproject.git

$ git fetch jessica

$ git checkout -b rubyclient jessica/ruby-client



-

리모트 저장소로 등록하면 커밋의 히스토리도 알 수 있다.

머지할 때 어디서부터 갈라졌는지 알 수 있기 때문에 -3 옵션을 주지 않아도 자동으로 3-way merge 가 적용된다.



-

리모트 저장소로 등록하지 않고도 merge 할 수 있다.

계속 함께 일할 개발자가 아닐 때 사용하면 좋다.


ex) 

$ git pull onetime_contributors_url




* 무슨 내용인지 확인하기


-

아래 명령을 통해 master 에 포함되지 않은 branch_name 에만 있는 commit 들을 확인해 볼 수 있다.


$ git log [branch_name] —not master # -p 옵션을 주어 자세히 볼 수도 있다.



-

아래 명령을 통해 master 와의 차이점을 볼 수 있다.

$ git diff master


이 명령은 diff 내용을 보여주긴 하지만 잘못된 것을 보여줄 수도 있다.

토픽 브런치에서 작업하는 동안 master 브랜치에 새로운 커밋이 추가될 수도 있기 때문이다.

위 명령은 토픽 프랜치의 마지막 커밋과 master 브랜치의 마지막 커밋을 비교한다.


아래 명령은 공통 조상인 커밋을 찾고 이 조상 커밋에서 변경된 내용을 보여준다.


$ git merge-base branch_name master

(output sha)

$ git diff (output sha)


이 방법으로 원하는 결과를 얻을 수 있지만 사용법이 불편하다.

git 은 triple-dot 으로 간단하게 위와 같은 비교방법을 지원한다.

diff 명령을 사용할 때 두 브랜치 사이에 ... 를 쓰면 된다.


$ git diff master…branch_name







* 기여물 통합하기


-

토픽 브랜치를 바로 master 브랜치에 머지하는 것은 가장 간단한 방법이다.

이 워크플로(머지하는 워크플로)에서는 master 브랜치가 안전한 코드라고 가정한다.

토픽 브랜치를 검증하고 master 브랜치로 머지할 때마다 토픽 브랜치를 삭제한다.



-

개발자가 많고 규모가 큰 프로젝트에서는 최소한 두 단계로 머지하는 것이 좋다.

master 브랜치는 아주 안정적인 버전을 릴리스하기 위해 사용한다.

develop 브랜치는 새로 수정된 코드를 통합할 때 사용한다.

그리고 두 브랜치를 모두 공개 저장소에 push 한다.

토픽 브랜치들은 develop 브랜치에 머지한다.

그 후 릴리스해도 될 만한 수준이 되면 master 브랜치를 develop 브랜치까지 fast-forward 한다.


이 워크플로를 사용하면 안정 버전이 필요하면 master 브랜치를 빌드하고, 안정적이지 않더라도 좀 더 최신 머전이 필요하면 develop 브랜치를 사용하여 빌드한다.



-

git 을 개발하는 프로젝트는 long-running 브랜치를 4개 운영한다.

각 브랜치 이름은 master, next, pu(Proposed Updates), maint 이다.

maint 는 마지막으로 릴리스한 버전을 지원하는 브랜치다.


토픽 브랜치가 부족한 점이 없고 안정적이라면 next 에 머지된다.

만약 더 개선되어야 할 필요가 있다면 pu 에 머지한다. 그 후 충분히 검증을 마치면 pu 에서 next 로 옮긴다.


그리고 나중에 master 브랜치로 머지된다.

그리고 토픽 브랜치들은 삭제된다.


마지막으로 릴리스 버전에 패치가 필요하면 maint. 브랜치를 이용해 대응한다.



-

히스토리를 한 줄로 관리하려고 merge 보다 rebase 나 cherry-pick 을 더 선호하는 관리자들도 있다.

토픽 브랜치에서 작업을 마친 후 master 에 통합할 때 master 브랜치를 기반으로 rebase 한다.

그러면 커밋이 다시 만들어진다.

master 대신 develop 등의 브랜치에 하고, master 는 fast-forward 하는 방법도 있다.


cherry-pick 은 커밋 하나만 rebase 하는 것이다.

커밋 하나로 patch 내용을 만들어 현재 브랜치에 적용을 하는 것이다.

토픽 브랜치에 있는 커밋 중에서 하나만 고르거나 토픽 브랜치에 커밋이 하나밖에 없을 때 rebase 보다 유용하다.


ex) git cherry-pick sha1_hash



-

수시로 merge 나 rebase 를 한다거나 오랫동안 유지되는 토픽 브랜치를 쓰는 사람에게 유용한 rerere 기능이 있다.

rerere 의 뜻은 reuse recorded resolution(충돌 해결방법 재사용) 이고 수작업으로 하던 작업을 쉽게 하는 방법이다.

rerere 기능이 활성화돼 있으면 머지가 성공할 때마다 그 이전과 이후 상태를 저장해둔다.

나중에 충돌이 발생하면 비슷한 상황에서 머지가 성공한 적이 있었는지 찾아보고 해결할 수 있다면 자동으로 해결한다.


rerere 기능의 동작은 두 부분으로 나누어 볼 수 있다.

rerere 기능을 설정하는 부분과 rerere 기능을 명령으로 사용하는 부분이다.

설정은 rerere.enabled 값을 설정하면 되는데 글로벌 설정에 저장해 두고 사용하면 편하다.


$ git config —global rerere.enabled true


이후부터 머지가 성공할 때마다 전후 상황을 기록해두고 나중에 충돌이 나면 사용한다.



-

필요하다면 git rerere 명령을 사용하여 저장된 캐시를 바탕으로 대화형 인터페이스를 통해 충돌을 다룰 수도 있다.

git rerere 명령을 직접 실행하면 현재 머지 과정에서 발생한 충돌을 해결하는 데 참고할 만한 이전 머지 기록을 찾아준다.(사실 rerere.enabled 옵션이 켜져 있다면 자동).

rerere 를 사용할 때 기록할 내용을 세세하게 설정하거나 기록된 내용 중 특정 기록을 지운다거나 하는 보조 명령도 제공한다.




* 릴리스 버전에 태그 달기


-

ex) 

$ git tag -s v1.5 -m ‘1.5 tag’


-s 는 서명을 포함하는데, PGP 공개키도 배포해야 한다.

git 개발 프로젝트는 관리자의 PGP 공개키를 blob 형식으로 git 저장소와 함께 배포한다.

gpg —list-keys 명령으로 어떤 pgp 공개키를 포함할지 확인한다.


$ gpg --list-keys



-

git hash-object 명령으로 공개키를 바로 git 저장소에 넣을 수 있다.

이 명령은 git 저장소 안에 blob 형식으로 공개키를 저장해주고 그 blob 의 sha-1 값을 알려준다.


$ gpg -a —export F721C45A | git hash-object -w —stdin

(output hash)

$ git tag -a maintainer-pgp-pub (output hash)


git push —tags 로 maintainer-php-pub 태그를 공유한다.

다른 사람이 태그의 서명을 확인하려면 우선 git 저장소에 저장된 pgp 공개키를 꺼내서 gpg 키 db 에 저장해야 한다.


$ git show maintainer-pgp-pub | gpg —import


사람들은 이렇게 공개키를 얻어서 서명된 태그를 확인한다.

또한 관리자가 태그 메시지에 서명을 확인하는 방법을 적어 놓으면 좋다.




* 빌드 넘버 만들기


-

git describe 명령으로 좀 더 사람이 기억하기 쉬운 이름을 얻을 수 있다.

git 은 가장 가까운 태그의 이름과 태그에서 얼마나 더 커밋이 쌓였는지, 그리고 해당 커밋의 sha-1 값을 조금 가져다가 이름을 만든다.


$ git describe master



-

git describe 명령은 -a 나 -s 옵션을 주고 만든 annotated 태그가 필요하다.

릴리스 태그는 git describe 명령으로 만드니 꼭 이름이 적당한지 사전에 확인해야 한다.

그리고 이 값은 checkout 이나 show 명령에도 사용할 수 있지만 전적으로 이름 뒤에 붙은 sha-1 값을 사용한다.



-

최근 리눅스에서는 충돌 때문에 축약된 sha-1 가 8자에서 10자로 늘어났다.




* 릴리스 준비하기


-

git 을 사용하지 않는 사람을 위해 소스 코드 스냅샷을 압축한다.

쉽게 압축할 수 있도록 git 은 git archive 명령을 지원한다.


ex) 

$ git archive master —prefix=‘project/‘ | gzip > ‘git describe master’.tar.gz


—format=zip 옵션을 주어 zip 형식으로 압축 파일을 만들 수도 있다.




* Shortlog 보기


-

이메일로 프로젝트의 변경사항을 사람들에게 알려야 할 때, git shortlog 명령을 사용하면 지난 릴리스 이후의 변경사항 목록을 쉽게 얻어올 수 있다.

 

ex) 

$ git shortlog —no-merges master —not v1.0.1


이는 v.1.0.1 이후 변경 내용을 Author 기준으로 정리한 커밋을 이메일로 전송한다.





5.4. 요약




반응형

댓글