posted by REDFORCE 2017. 4. 13. 01:10

출처 : git-scm.com


6.4 GitHub - Organization 관리하기

Organization 관리하기

GitHub에는 Organization이라는 계정도 있다. 개인 계정처럼 Organizaiton 계정도 프로젝트 네임스페이스지만 다른 점이 많다. 이 계정은 여러 명이 같은 프로젝트를 관리하는 데 사용하는 그룹 계정이고 사람들을 서브 그룹을 나누어 관리하는 도구도 있다. 이 계정은 “perl'이나 ‘`rails” 같은 오픈소스 그룹이나 ``goolge'이나 ``twitter’'같은 회사가 사용한다.

Organization 기초

Organization을 만드는 것은 매우 쉽다. GitHub 페이지 오른쪽 위에 있는 “+” 아이콘을 클릭하고 메뉴에서 ‘`New organization’'을 선택하면 된다.

``New organization'' 메뉴 아이템.
Figure 126. “New organization” 메뉴 아이템.

먼저 이름과 소유자 이메일 주소를 입력해서 Organization 계정을 만든다. 그리고 나서 다른 사람들을 초대한다. 필요하면 공동 소유자로 만들 수 있다.

다 만들면 Organization의 소유자가 된다. 개인 계정과 마찬가지로 Organization도 오픈 소스에는 무료다.

GitHub은 Organization 소유자가 저장소를 Fork 할 때는 어느 계정으로 Fork 하는 것인지 묻는다. 새 저장소를 만들 때도 개인 계정 밑에 만들지 Organization 밑에 만들지 선택할 수 있다. 그리고 소유자는 해당 Organization에 저장소가 생길 때마다 자동으로 “Watching” 상태가 된다.

아바타에서 개인 아바타를 올렸던 것처럼 Organization 계정에도 똑같이 아바타를 올릴 수 있다. 계정 랜딩 페이지도 개인 계정과 같다. 가지고 있는 저장소의 목록 페이지가 랜딩 페이지이고 다른 사람들이 볼 수 있다.

Organization 계정이 개인 계정과 다른 점이 있는데 그 점들을 살펴보자.

Organization과 개인은 팀을 통해 연결된다. Organizatoin의 사용자와 저장소는 팀으로 관리되고 저장소의 권한 설정도 팀으로 관리한다.

만약 회사에 frontendbackenddeployscripts 이렇게 저장소가 세 개 있다고 하자. HTML/CSS/JavaScript 개발자는 frontend 저장소에 접근 권한이 있어야 한다. 반대로 운영하는 사람들은 backend`나 `deployscripts 같은 저장소에 접근 권한이 있어야 한다. Organization에서 팀은 저장소에서 함께 일하는 사람을 관리하는 효과적인 도구다.

Organization 페이지는 저장소, 사용자, 팀을 한눈에 보여주는 대시보드다.

orgs 01 page
Figure 127. Organization 페이지

Organization 페이지 오른쪽에 있는 Teams 사이드바를 클릭하면 팀을 관리하는 페이지로 넘어간다. 다음 페이지에서 팀에 팀원이나 저장소를 추가하고, 설정을 관리하고, 팀의 권한 설정을 할 수 있다. 팀은 저장소에 대해 읽기 전용, 읽고 쓰기, 관리 권한을 가질 수 있다. 팀 페이지.에 있는 “Settings” 버튼을 클릭하면 권한 수준을 변경할 수 있다.

orgs 02 teams
Figure 128. 팀 페이지.

누군가를 팀에 초대하면 그 사람에게 초대 메일이 간다.

개인 사용자에 멘션하는 것처럼 팀 `@mentions`도 사용할 수 있다. `@acmecorp/frontend`처럼 하면 팀의 모든 멤버가 참여하게 된다. 정확히 누구한테 물어야 할지 모를 때는 그냥 팀 전체에 문의하는 것도 방법이다.

사용자가 속하는 팀의 수는 제한이 없다. 단순히 팀을 권한 관리 용도로 사용하지 마라. uxcssrefactoring`과 같이 팀은 어떤 질문 등을 관리하기에 좋고 `legalcolorblind 같은 팀은 또 다른 이슈를 처리하는 데 좋다.

감사 로그

소유자는 Organization에서 일어나는 모든 정보를 알 수 있다. Audit Log 탭에 보면 저장소에서 일어난 일들의 로그가 있다. 누가 세계 어디에서 무슨 일을 했는지 보여준다.

orgs 03 audit
Figure 129. 감사 로그.

소유자는 이 화면에서 누가, 어디서, 무엇을 했는지 걸러 볼 수 있다.


posted by REDFORCE 2017. 4. 13. 01:09

출처 : git-scm.com


6.3 GitHub - GitHub 프로젝트 관리하기

GitHub 프로젝트 관리하기

지금까지 남의 프로젝트에 기여하는 법을 살펴보았고 이번에는 직접 프로젝트를 운영하는 법을 살펴보자. 프로젝트를 생성해서 관리하는 방식 말이다.

새 저장소 만들기

저장소를 새로 만들고 프로젝트 코드를 공유해 보자. 대시보드 오른쪽에 있는 “New repository” 버튼을 클릭하면 저장소를 만드는 폼으로 이동한다. 맨 위 툴바의 사용자 이름 옆에 있는 + 버튼을 클릭해도 된다.

``Your repositories'' 박스.
Figure 110. “Your repositories” 박스.
사용자 이름 옆 ``new repository'' 메뉴.
Figure 111. 사용자이름 옆 “New repository” 메뉴.

위 버튼을 누르면 ‘`새 저장소’'를 만드는 화면으로 이동한다.

``새 저장소'' 만들기.
Figure 112. “새 저장소” 만들기.

프로젝트 이름을 넣는 것만 필수다. 다른 것은 생략해도 된다. “Create Repository” 버튼을 클릭하면 '뿅’하고 <user>/<project_name> 위치에 GitHub 저장소가 생긴다.

아직 저장소에 코드가 하나도 없어서, GitHub는 Git 저장소를 만드는 방법이나 기존 Git 프로젝트를 넣는 방법을 보여준다. 이 내용을 다시 살펴보고 싶다면 Git의 기초를 보라. 여기서 또 설명하지 않는다.

GitHub에 프로젝트를 올렸으면 다른 사람들에게 프로젝트 URL을 알려주고 공유할 수 있다. 모든 프로젝트의 HTTP URL은 `https://github.com/<user>/<project_name>`처럼 생겼고 SSH는 `git@github.com:<user>/<project_name>`처럼 생겼다. Git은 이 두 URL을 통해서 Fetch 하고 Push 할 수 있지만, 인증 방식은 사용하는 프로토콜에 따라 다르다.

Note

GitHub 계정 없이 Clone 할 수 있기 때문에 공개 프로젝트를 공유할 때는 SSH보다 HTTP URL를 더 많이 공유한다. SSH URL을 사용하려면 계정도 있어야 하고 SSH 키도 GitHub에 등록해야 한다. 브라우저에서 프로젝트 페이지에 접속할 때도 저장소 URL로 사용하는 HTTP URL을 그대로 사용한다.

동료 추가하기

저장소에 커밋 권한을 주고 싶은 동료가 있으면 ‘`Collaborator’로 추가해야 한다. Ben과 Jeff, Louise라는 동료가 있는데 그들이 내 저장소에 Push 할 수 있도록 하고 싶으면 내 프로젝트에 GitHub 계정들을 추가해야 한다. 계정이 추가된 사람은 해당 프로젝트와 Git 저장소에 ``Push'할 수 있을 뿐만 아니라 읽고 쓰기도 가능하다.

오른쪽 밑에 있는 ``Settings`` 링크를 클릭한다.

저장소 설정 링크.
Figure 113. 저장소 설정 링크.

왼쪽 메뉴에서 ‘`Collaborators’를 선택한다. 텍스트 박스에 사용자 이름을 입력하고 ‘`Add collaborator'를 클릭한다. 필요한 사람을 모두 추가할 때까지 반복한다. 그리고 오른쪽에 있는 ``X’'를 클릭하면 권한이 회수된다.

저장소의 동료.
Figure 114. 저장소의 동료.

Pull Request 관리하기

프로젝트를 만들고 코드도 넣고 동료가 Push 할 수 있게 했다. 이제 Pull Reqeust가 왔을 때 어떻게 해야 하는지 보자.

Pull Request는 같은 저장소나 Fork 한 저장소에서 브랜치를 보내오는 것이다. 그 둘의 차이는 권한에 있다. Fork 한 저장소는 다른 사람의 저장소이기 때문에 그 보내온 브랜치에 Push 할 권한이 없다. 하지만, 같은 저장소의 브랜치에는 Push 할 수 있다.

‘`tonychacon’이라는 사람이 ``fade'라는 Arduino 프로젝트를 만든 상황을 살펴보자.

이메일 알림

어떤 사람이 코드를 수정해서 Pull Request를 보내왔다. 그러면 새로운 Pull Request가 왔다는 메일이 담당자에게 간다. 새 Pull Request에 대한 이메일 알림. 같은 메일이 보내진다.

Pull Request 이메일 알림
Figure 115. 새 Pull Request에 대한 이메일 알림.

이 이메일은 무엇이 달라진 것인지 간략히 보여준다. 해당 Pull Request에서 어떤 파일이 얼마나 변경됐는지 보여준다. 그리고 Pull Request 페이지 링크도 있고 CLI로 Merge 하는 방법과 URL도 간략히 보여준다.

`git pull <url> patch-1`라는 명령이 궁금할 텐데 이렇게 하면 리모트 브랜치를 간단히 Merge 할 수 있다. 저장소를 리모트로 추가하지 않아도 된다. 필요하면 토픽 브랜치를 만들고 리모트 브랜치로부터 통합하기에서 배운 명령어로 Pull Request로 직접 Merge 해도 된다.

그리고 눈치챘을 테지만 .diff`와 `.patch URL은 Pull Request의 'Unified Diff’와 Patch 버전의 URL이다. 이 URL로 아래와 같이 Pull Request를 Merge 할 수 있다.

$ curl http://github.com/tonychacon/fade/pull/1.patch | git am

Pull Request로 함께 일하기

GitHub 플로우에서 설명했듯이 Pull Request를 만든 사람과 토론할 수 있다. GFM을 사용하여 특정 커밋을 선택하거나, 특정 라인을 지정하거나, 혹은 전체 Pull Request 자체에도 코멘트를 남길 수 있다.

일단 대화에 참여하고 나면 누군가 코멘트할 때마다 이메일 알림이 계속 온다. 그 이메일에는 Pull Request 페이지의 링크가 포함돼 있기 때문에 어떤 일이 일어나고 있는지 쉽게 알 수 있다. 그리고 답 메일을 보내면 Pull Request의 코멘트로 달린다.

답 메일
Figure 116. 답변 메일이 Pull Request의 스레드가 됨.

보내온 코드가 마음에 들어서 Merge 하고 싶다면 로컬에 가져와서 Merge 할 수 있다. git pull <url> <branch> 명령으로 Merge 하면 되는데 먼저 Fork 한 저장소를 리모트로 추가하고 Fetch 해서 Merge 한다.

GitHub 사이트에서 “Merge” 버튼을 누르는 것으로 간편하게 Merge 할 수 있다(Trivial Merge). “fast-forward'가 가능할 때도 ``non-fast-forward” Merge를 하기 때문에 Merge 커밋이 생긴다. 그래서 “Merge” 버튼을 클릭해서 Merge 하면 항상 Merge 커밋이 생긴다. 여기서 어떻게 해야 하는지 'command line 힌트 링크를 클릭하면 Merge 버튼과 Pull Request를 수동으로 Merge 하기.과 같이 알려준다.

Merge 버튼
Figure 117. Merge 버튼과 Pull Request를 수동으로 Merge 하기.

만약 Pull Request를 Merge 하지 않기로 했다면 그냥 닫으면 된다. 그러면 그 Pull Request를 보낸 사람에게 알림이 간다.

Pull Request의 Ref

일일이 리모트를 등록하고 Pull 하는 것은 Pull Request를 많이 처리하는 사람에게는 고통스럽다. GitHub는 이럴 때 사용하는 방법을 제공한다. 이 내용은 Refspec에서 자세히 설명할 거고 조금 어려울 수 있다.

GitHub는 Pull Request의 브랜치를 서버에 있는 가상 브랜치로 노출해준다. GitHub가 자동으로 해주기 때문에 바로 이용하면 된다.

이걸 해보려면 저수준(“plumbing”) 명령어인 `ls-remote`가 필요하다. 이 명령어는 아무래도 매일 쓰는 명령어는 아니지만, 서버에 어떤 Ref가 있는지 보여 준다. “plumbing” 명령어는 Plumbing 명령과 Porcelain 명령에서 자세히 설명한다.

이 명령어로 좀 전의 “blink” 저장소를 살펴보자. 저장소 브랜치뿐만 아니라 태그 등 온갖 Ref를 보여준다.

$ git ls-remote https://github.com/schacon/blink
10d539600d86723087810ec636870a504f4fee4d	HEAD
10d539600d86723087810ec636870a504f4fee4d	refs/heads/master
6a83107c62950be9453aac297bb0193fd743cd6e	refs/pull/1/head
afe83c2d1a70674c9505cc1d8b7d380d5e076ed3	refs/pull/1/merge
3c8d735ee16296c242be7a9742ebfbc2665adec1	refs/pull/2/head
15c9f4f80973a2758462ab2066b6ad9fe8dcf03d	refs/pull/2/merge
a5a7751a33b7e86c5e9bb07b26001bb17d775d1a	refs/pull/4/head
31a45fc257e8433c8d8804e3e848cf61c9d3166c	refs/pull/4/merge

저장소 안이라면 `git ls-remote origin`이라고 실행시켜도 된다. 저장된 리모트 이름을 사용할 수 있다.

GitHub 저장소에 어떤 Pull Reqeust라도 열려있다면 `refs/pull/`로 시작하는 이름으로 Ref가 생성된다. 이것도 브랜치지만 `refs/heads/`로 시작하는 브랜치와는 달리 Clone과 Fetch 할 때 받아지지 않는다. 기본적으로 무시된다.

Pull Request에는 두 종류의 Ref가 있다. /head`로 끝나는 것은 Pull Request 브랜치가 가리키는 마지막 커밋이다. 누군가 우리 저장소에 `bug-fix`라는 브랜치를 Pull Request로 보내는 상황을 살펴보자. 이 브랜치는 `a5a775 커밋을 가리킨다. bug-fix 브랜치는 Fork 한 저장소에 있는 브랜치라서 우리 저장소에 없다. 그럼에도 a5a775`를 가리키는 `pull/<pr#>/head 형식의 브랜치가 자동으로 생긴다. 그래서 매번 다른 저장소를 리모트로 등록하지 않고서도 Pull Request 브랜치를 쉽게 Pull 할 수 있다.

그 브랜치를 한번 가져와 보자.

$ git fetch origin refs/pull/958/head
From https://github.com/libgit2/libgit2
 * branch            refs/pull/958/head -> FETCH_HEAD

‘`리모트의 브랜치 `origin`을 `refs/pull/958/head`로 Fetch 한다’'는 뜻이다. Git은 충실하게 전부 내려받고 마지막 커밋을 `.git/FETCH_HEAD`에 저장한다. `git merge FETCH_HEAD`으로 Merge 해서 테스트할 수 있다. 이렇게 Merge 하면 Merge 커밋 메시지가 약간 이상해진다. 또한 많은 Pull Request를 처리해야 하는 경우, 쓸데없는 Merge 커밋도 많아진다.

항상 Pull Request를 전부 가져오게 할 수 있다. .git/config 파일을 열어서 origin 리모트를 찾는다.origin 리모트는 사실 아래와 같은 것을 의미한다.

[remote "origin"]
    url = https://github.com/libgit2/libgit2
    fetch = +refs/heads/*:refs/remotes/origin/*

fetch =`로 시작하는 라인이 ``refspec.''이라는 거다. 리모트 이름과 로컬 `.git 디렉토리를 어떻게 매핑하는지 나타낸다. 여기서는 해당 리모트에서 refs/heads`에 해당하는 이름이 `refs/remotes/origin 디렉토리에 매핑된다는 의미다. Refspec을 새로 추가해보자.

[remote "origin"]
    url = https://github.com/libgit2/libgit2.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

추가한 마지막 라인의 의미는 ‘` refs/pull/123/head 같은 Ref를 `refs/remotes/origin/pr/123`에 저장’' 한다는 의미다. `git fetch`라고 실행하면 새 Refspec의 브랜치도 가져온다.

$ git fetch
# …
 * [new ref]         refs/pull/1/head -> origin/pr/1
 * [new ref]         refs/pull/2/head -> origin/pr/2
 * [new ref]         refs/pull/4/head -> origin/pr/4
# …

서버에 있는 모든 Pull Request을 추적하는 트래킹 브린치가 생겼다. 쓰기는 불가능하지만 계속 Fetch 해 올 수 있다. 이렇게 하면 Pull Request를 로컬에 가져와서 작업하는 게 편해진다.

$ git checkout pr/2
Checking out files: 100% (3769/3769), done.
Branch pr/2 set up to track remote branch pr/2 from origin.
Switched to a new branch 'pr/2'

head`로 끝나는 Refspec에 대해서 살펴봤고 이제 `refs/pull/#/merge 처럼 생긴 Refspec을 알아보자. 이 브랜치는 GitHub에서 Merge 버튼으로 Merge 했을 때 적용되는 결과다. GitHub에서 실제로 Merge 하기 전에 로컬로 가져와서 먼저 테스트할 수 있다.

Pull Request 이어가기

Pull Request를 Merge 할 브랜치는 `master`가 아니어도 된다. 주 브랜치를 고를 수도 있고 Pull Request를 열 때 다른 브랜치를 골라도 된다. 심지어 다른 Pull Request를 고를 수도 있다.

착착 잘 진행하는 어떤 Pull Request가 있는데 거기에 뭔가 아이디어를 더하고 싶다는 생각이 들었다. 좋은 아이디어라는 확신도 부족하고 무엇보다 Merge 될 브랜치에 Push 권한이 없다. 이럴 땐 Pull Request에 Pull Request를 보낼 수 있다.

Pull Request를 만들러 가면 페이지 위쪽에 어떤 저장소의 브랜치를 어떤 저장소의 브랜치로 요청하는 것인지를 보여주는 박스가 있다. “Edit” 버튼을 누르면 Fork 한 저장소 중 하나로 저장소를 변경하고 해당 저장소의 브랜치로 변경할 수 있다.

PR의 대상 브랜치
Figure 118. Pull Request을 어디로 보낼지 대상을 선택.

쉽게 다른 Fork 저장소나 Pull Request의 브랜치를 골라 Pull Request를 열 수 있다.

멘션과 알림

GitHub는 어떤 팀이나 사람에게 질문하거나 피드백을 받을 수 있도록 쉽고 편한 알림 시스템을 제공한다.

GitHub 어디에서나 `@`만 입력해도 동료나 기여자의 사용자 이름이 자동완성 된다.

멘션
Figure 119. `@`만 입력.

자동완성 메뉴에 없는 사람도 입력할 수 있지만, 자동완성이 편하고 빠르다.

GitHub에서 글을 쓸 때 `@멘션`을 하면 해당 사용자에게 알림이 간다. 일일이 의견을 물으러 다니는 것보다 이렇게 토론에 참여시키는 게 훨씬 유용하다. GitHub에서는 멘션으로 팀의 동료나 다른 사람을 이슈나 Pull Request에 참여시킨다.

한번 ‘@멘션`으로 언급되면 그 사람은 “구독 상태(Subscribed)’'가 된다. 그래서 해당 이슈나 Pull Request에서 계속 알림이 온다. 이슈나 Pull Request를 직접 만들었거나, 해당 저장소를 'Watching’하는 상태이거나, 코멘트를 단 경우에도 구독 상태가 된다. 더는 알림을 받고 싶지 않으면 화면의 ``Unsubscribe” 버튼으로 멈출 수 있다.

Unsubscribe
Figure 120. 특정 이슈와 Pull Request의 알림 끊기(Unsubscribe).

알림 페이지

GitHub의 “알림''은 프로젝트에서 어떤 일이 일어나면 바로 알 수 있도록 안내해 주는 것이다. 이 알림은 원하는 방법으로 설정해 쓸 수 있다. 설정의 ``Notification center” 탭에 가면 설정할 수 있는 옵션이 있다.

Notifiation center
Figure 121. Notification center 옵션.

알림을 이메일로 받을지 웹으로 받을지 선택할 수 있다. 물론 두 가지 방법을 동시에 사용해도 된다. 그리고 그냥 대화에 참여하는 경우와 프로젝트를 Watching 하는 경우를 나누어 선택할 수 있다.

웹 알림

웹 알림은 GitHub에서 제공하는 것으로 GitHub 사이트에서만 확인할 수 있다. 이 옵션을 선택하면 알림이 오면 알림 아이콘에 파란 점을 볼 수 있다. Notification center.을 확인해보자.

Notifiation center
Figure 122. Notification center.

알림 아이콘을 클릭하면 알림 메시지를 확인할 수 있다. 알림은 프로젝트별로 분류된다. 왼쪽 메뉴에 있는 프로젝트를 선택하면 관련 알림만 걸러서 볼 수 있다. 각 알림에 있는 체크박스를 클릭해서 읽었다고 표시를 할 수 있고 제일 위에 있는 체크박스를 클릭하면 해당 알림에 대해서 전부 읽음 표시를 할 수 있다. 그리고 Mute 버튼을 클릭하면 해당 사항에 대해서는 더는 알림이 오지 않는다.

이 기능을 사용하면 쏟아지는 알림들도 매우 효율적으로 처리할 수 있다. GitHub의 파워 유저는 이메일 알림을 꺼놓고 GitHub 사이트에서만 알림을 관리하기도 한다.

이메일 알림

이메일 알림을 켜 놓으면 이메일로도 GitHub 알림을 확인할 수 있다. 이메일 알림으로 온 코멘트과 새 Pull Request에 대한 이메일 알림.의 예를 보면 관련 알림들이 이메일 스레드로 잘 분류되는 것을 볼 수 있다. 그래서 이메일 스레드를 잘 지원하는 메일 클라이언트를 사용하는 것이 좋다.

GitHub가 보낸 이메일 헤더를 보면 여러 가지 메타데이터가 들어 있다. 그래서 사용자는 이메일 필터나 룰 같은 자동 관리 기능으로 쉽게 관리할 수 있다.

새 Pull Request에 대한 이메일 알림.에서 보여준 이메일의 헤더는 아래와 같다.

To: tonychacon/fade <fade@noreply.github.com>
Message-ID: <tonychacon/fade/pull/1@github.com>
Subject: [fade] Wait longer to see the dimming effect better (#1)
X-GitHub-Recipient: tonychacon
List-ID: tonychacon/fade <fade.tonychacon.github.com>
List-Archive: https://github.com/tonychacon/fade
List-Post: <mailto:reply+i-4XXX@reply.github.com>
List-Unsubscribe: <mailto:unsub+i-XXX@reply.github.com>,...
X-GitHub-Recipient-Address: tchacon@example.com

프로젝트에 따라 혹은 Pull Request인지에 따라 분류하거나 다른 주소로 재전송하고 싶다면 Message-ID`를 이용하는 게 좋다. 이 데이터는 `<user>/<project>/<type>/<id> 형식으로 돼 있다. 만약 이슈에 대한 데이터면 ‘<type>`부분이 ``pull’이 아니라 ``issues'라고 돼 있을 것이다.

List-Post`와 `List-Unsubscribe 필드를 인식하는 메일 클라이언트를 사용하고 있으면 좀 더 편리하게 사용할 수 있다. `List-Post`는 이메일로 리스트에 글을 올리는 데 사용하고 `List-Unsubscribe`는 이메일 클라이언트에서 알림을 그만 받도록 할 수 있다. 이슈와 Pull Request페이지의 “Unsubscribe” 버튼을 클릭하거나 웹 알림 페이지에서 “Mute” 버튼을 클릭하는 것과 같다.

이메일과 웹 알림이 둘 다 켜져 있으면 알림이 이메일로도 오고 웹으로도 온다. 이메일 클라이언트에서 이미지가 허용되어 있으면, 메일을 읽었을 때 웹에서도 읽었다고 표시된다.

특별한 파일

저장소에 있는 파일 중에서 GitHub가 사용하는 몇 가지 특이한 파일들이 있다.

README

GitHub는 저장소 랜딩 페이지를 보여줄 때 README 파일을 이용해서 보여준다. README 파일 형식에 상관없이 잘 보여준다. README 파일이든 README.md 파일이든 README.asciidoc 파일이든 GitHub가 자동으로 렌더링해서 보여준다.

많은 사람이 이 파일에 저장소나 프로젝트에 처음 방문한 사람들에게 필요한 정보를 정리해 둔다. 보통 아래와 같은 내용을 쓴다.

  • 무슨 프로젝트인지

  • 설정하고 설치하는 방법

  • 사용법과 실행 결과에 대한 예제

  • 프로젝트의 라이센스

  • 기여하는 방법

GitHub는 README 파일을 렌더링하는 것이기 때문에 이미지나 외부 링크를 적어도 된다.

CONTRIBUTING

GitHub는 CONTRIBUTING 파일도 인식한다. README와 마찬가지로 원하는 파일 형식을 사용하면 된다. Pull Request를 열 때 이 파일이 있으면 CONTRIBUTING 파일이 있음을 보여준다.과 같이 링크를 보여준다.

Contributing notice
Figure 123. CONTRIBUTING 파일이 있음을 보여준다.

이 파일에는 프로젝트에 기여하는 방법과 Pull Request 규칙 같은 것을 적는다. 그러면 사람들이 Pull Request를 열 때 이 가이드라인을 참고할 수 있다.

프로젝트 관리

특별히 관리할 만한 게 별로 없지만 알고 있으면 유용한 것들을 소개한다.

기본 브랜치 변경하기

기본 브랜치를 “master''말고 다른 브랜치로 설정할 수 있다. Pull Request를 열 때 설정한 기본 브랜치가 기본으로 선택된다. 기본 브랜치는 저장소 설정 페이지의 ``Options” 탭에서 변경한다.

기본 브랜치
Figure 124. 기본 브랜치 변경하기.

기본 브랜치 변경은 쉽고 정말로 기본으로 쓰인다. 저장소를 Clone 하면 여기서 설정한 브랜치가 기본으로 Checkout된다.

프로젝트 넘기기

프로젝트 소유자를 다른 사용자나 Organization으로 변경할 수 있다. 저장소 설정 페이지의 “Options''탭을 보면 페이지 아래쪽에 ``Transfer ownership” 항목이 있다. 여기 있는 Transfer 버튼으로 프로젝트를 넘길 수 있다.

프로젝트 넘기기
Figure 125. 다른 GitHub 사용자나 Organization에 프로젝트 넘기기.

맡던 프로젝트를 다른 사람에게 넘겨주거나 프로젝트가 커져서 Organizaiton 계정으로 옮기고 싶을 때 유용하다.

저장소만 옮겨지는 것이 아니라 'Watching’하는 사람이나 'Star’한 사람까지도 함께 옮겨진다. 그리고 URL은 Redirect되는데 웹 접속뿐만 아니라 Clone 이나 Fetch 요청까지도 Redirect된다.


posted by REDFORCE 2017. 4. 13. 00:48

출처 : git-scm.com


6.2 GitHub - GitHub 프로젝트에 기여하기

GitHub 프로젝트에 기여하기

계정은 이제 만들었으니 프로젝트에 참여하는 방법을 살펴볼 차례가 됐다.

프로젝트 Fork 하기

참여하고 싶은 프로젝트가 생기면 아마 그 프로젝트에 Push 할 권한은 없을 테니까 ‘`Fork’해야 한다. ``Fork'하면 GitHub이 프로젝트를 통째로 복사해준다. 그 복사본은 사용자 네임스페이스에 있고 Push 할 수도 있다.

Note

과거에는 ‘`Fork’가 좋은 의미로 쓰이지 않았다. 오픈 소스 프로젝트를 ‘`Fork'한다는 것은 복사해서 조금은 다른 프로젝트를 만드는 것을 의미했고 때때로 원래 프로젝트와 경쟁하거나 기여자를 나누는 결과를 가져오기도 했다. GitHub에서 ``Fork’'는 단순히 자신의 네임스페이스로 복사하는 것을 뜻한다. 그래서 공개한 상태로 수정하고 좀 더 열린 방식으로 참여할 수 있다.

이 방식에서는 사람들을 프로젝트에 추가하고 Push 권한을 줘야 할 필요가 없다. 사람들은 프로젝트를 ‘`Fork’'해서 Push 한다. 그리고 Push 한 변경 내용을 원래 저장소로 보내 기여한다. 이것을 Pull Request라고 부르는데 나중에 다시 설명한다. 토론 스레드를 만들고 거기서 코드 리뷰를 하면서 토론하는 스레드를 만들어 토론을 시작한다. 프로젝트 소유자 마음에 들 때까지 소유자와 기여자는 함께 토론한다. 마음에 들게 되면 Merge 한다.

프로젝트는 쉽게 Fork 할 수 있다. 프로젝트 페이지를 방문해서 오른쪽 꼭대기에 있는 “Fork” 버튼을 클릭한다.

``Fork'' 버튼.
Figure 89. “Fork” 버튼.

몇 초안에 복사된 프로젝트 페이지로 이동한다. 이 새 프로젝트의 소유자는 Fork 한 사람 자신이기 때문에 쓰기 권한이 있다.

GitHub 플로우

GitHub은 Pull Request가 중심인 협업 워크플로를 위주로 설계됐다. 이 워크플로는 Fork 해서 프로젝트에 기여하는 것인데 단일 저장소만 사용하는 작은 팀이나 전 세계에서 흩어져서 일하는 회사, 혹은 한 번도 본 적 없는 사람들 사이에서도 유용하다. Git 브랜치 에서 설명했던 토픽 브랜치 중심으로 일하는 방식이다.

보통은 아래와 같이 일한다.

  1. `master`에서 토픽 브랜치를 만든다.

  2. 뭔가 수정해서 커밋한다.

  3. 자신의 GitHub 프로젝트에 브랜치를 Push 한다.

  4. GitHub에 Pull Request를 연다.

  5. 토론하면서 그에 따라 계속 커밋한다.

  6. 프로젝트 소유자는 Pull Request를 Merge 하고 닫는다.

이 방식은 기본적으로 Integration-Manager 워크플로에서 설명하는 Integration-Manager 워크플로와 같다. 토론이나 리뷰를 이메일이 아니라 GitHub에서 제공하는 웹 기반 도구를 사용하는 것뿐이다.

GitHub에 있는 오픈소스 프로젝트에 이 워크플로를 이용해서 뭔가 기여하는 예제를 살펴보자.

Pull Request 만들기

Tony는 자신의 Arduino 장치에서 실행해볼 만한 코드를 찾고 있었고 GitHub에 있는 https://github.com/schacon/blink에서 매우 흡족한 프로그램을 찾았다.

기여하고자 하는 프로젝트.
Figure 90. 기여하고자 하는 프로젝트.

다 좋은데 너무 빠르게 깜빡이는 게 마음에 안 들었다. 매초 깜빡이는 것보다 3초에 한 번 깜빡이는 게 더 좋을 것 같았다. 그래서 프로그램을 수정하고 원 프로젝트에 다시 보내기로 했다.

앞서 설명했던 것처럼 Fork 버튼을 클릭해서 프로젝트를 복사한다. 사용자 이름이 ‘`tonychacon’'이라면 `https://github.com/tonychacon/blink`에 프로젝트가 복사된다. 이 프로젝트는 본인 프로젝트이고 수정할 수 있다. 이 프로젝트를 로컬에 Clone 해서 토픽 브랜치를 만들고 코드를 수정하고 나서 GitHub에 다시 Push 한다.

$ git clone https://github.com/tonychacon/blink (1)
Cloning into 'blink'...

$ cd blink
$ git checkout -b slow-blink (2)
Switched to a new branch 'slow-blink'

$ sed -i '' 's/1000/3000/' blink.ino (3)

$ git diff --word-diff (4)
diff --git a/blink.ino b/blink.ino
index 15b9911..a6cc5a5 100644
--- a/blink.ino
+++ b/blink.ino
@@ -18,7 +18,7 @@ void setup() {
// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  [-delay(1000);-]{+delay(3000);+}               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  [-delay(1000);-]{+delay(3000);+}               // wait for a second
}

$ git commit -a -m 'three seconds is better' (5)
[master 5ca509d] three seconds is better
 1 file changed, 2 insertions(+), 2 deletions(-)

$ git push origin slow-blink (6)
Username for 'https://github.com': tonychacon
Password for 'https://tonychacon@github.com':
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 340 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To https://github.com/tonychacon/blink
 * [new branch]      slow-blink -> slow-blink
  1. Fork 한 개인 저장소를 로컬에 Clone 한다.

  2. 무슨 일인지 설명이 되는 이름의 토픽 브랜치를 만든다.

  3. 코드를 수정한다.

  4. 잘 고쳤는지 확인한다.

  5. 토픽 브랜치에 커밋한다.

  6. GitHub의 개인 저장소에 토픽 브랜치를 Push 한다.

Fork 한 내 저장소에 가면 GitHub은 토픽 브랜치가 하나 Push 됐다는 것을 알려주고 원 저장소에 Pull Request를 보낼 수 있는 큰 녹색 버튼을 보여준다.

아니면 저장소의 브랜치 페이지로(https://github.com/<user>/<project>/branches) 가서 해당 브랜치의 “New pull request” 버튼을 이용한다.

Pull Request 버튼
Figure 91. Pull Request 버튼

녹색 버튼을 클릭하면 Pull Request의 제목과 설명을 입력하는 화면이 보인다. 항샹 프로젝트 소유자가 판단을 내릴 수 있을 정도로 공을 들여 작성해야 한다. 왜 수정했는지 얼마나 가치 있는지 설명해서 관리자를 설득해야 한다.

그리고 “ahead” 토픽 브랜치가 master 브랜치에서 달라진 커밋도 보여주고 수정된 내용을 “unified diff” 형식으로 보여준다. 이 수정 내용이 프로젝트 관리자가 Merge 할 내용이다.

Pull Request 생성
Figure 92. Pull Request를 생성하는 페이지

화면에 있는 Create pull request 버튼을 클릭하면 프로젝트 원소유자는 누군가 코드를 보냈다는 알림을 받는다. 그 알림에는 해당 Pull Request에 대한 모든 것을 보여주는 페이지의 링크가 들어 있다.

Note

Pull Request는 보통 공개 프로젝트에서 사용한다. 기여자는 수정하고 나서 원 저장소에 Pull Request를 연다. 개발 초창기에는 프로젝트 내부에서도 많이 사용한다. 이미 Pull Request를 열어 놓은 토픽 브랜치라고 할지라도 계속 Push 할 수 있다. 마지막이 아니라 처음부터 Pull Request를 열면 어떤 주제를 가지고 팀 동료와 함께 토론할 수 있어서 좋다.

Pull Request 놓고 감 놓고 배 놓기

Pull Request가 오면 프로젝트 소유자는 변경 점이 무엇인지 확인한 후, Merge 혹은 거절하거나 코멘트를 달 수 있다. 소유자가 아이디어 자체를 마음에 들어 한다면 빛을 보기까지 좀 더 공을 들여야 한다.

이런 소통을 이메일로 하는 워크플로는 분산 환경에서의 Git에 설명했었다. GitHub에서는 온라인에서 한다. 프로젝트 소유자는 unified diff 형식의 변경사항을 검토하고 즉각 해당 라인에 코멘트를 달 수 있다.

PR 라인 코멘트
Figure 93. Pull Request의 코드에 코멘트 달기

관리자가 코멘트를 달면 Pull Request를 만든 사람에게 알림이 간다. 실제로는 저장소를 'Watch’하는 사람 모두에게 알림이 간다. 알림 정책은 설정할 수 있지만, 다음에 검토한다. 알림을 받는 Tony가 이메일 알림을 켜놨다면 이메일 알림도 받는다.

이메일 알림으로 온 코멘트
Figure 94. 이메일 알림으로 온 코멘트

누구나 Pull Request에 코멘트를 달 수 있다. Pull Request 토론 페이지를 보면 프로젝트 소유자가 코드에 코멘트를 달거나 Pull Request 자체에 코멘트를 달면서 토론하는 것을 보여 준다. 코드 코멘트도 맥락을 이루어 커뮤니케이션 할 수 있다.

PR 토론 페이지
Figure 95. Pull Request 토론 페이지

이 토론을 보고 기여자는 자신이 무엇을 해야 자신의 코드가 받아들여질지 알 수 있다. 다행히 매우 직관적이다. 만약 이 일을 이메일로 하고자 한다면 관련 커밋을 다시 말아서 메일링 리스트에 다시 보내야 한다. 하지만, GitHub에서는 해당 토픽 브랜치에 이어서 커밋하고 Push 하면 된다. 최종 Pull Request에서 Push로 업데이트한 PR의 코드를 보면 예전 코드에 달렸던 코멘트는 나오지 않는다. 추가된 커밋으로 인해 코드가 수정되었기 때문이다.

기존 PR에 이어서 Push를 하면 알림이 가지 않는다. 그래서 Tony는 자신이 작업한 내용을 코멘트로 남겼다. 그러면 프로젝트 소유자는 무슨 일이 있었는지 쉽게 알 수 있다.

최종 PR
Figure 96. 최종 Pull Request

꼭 짚고 넘어가야 할 것이 있다. 이 Pull Request의 “Files Changed” 탭을 클릭하면 “unified” diff를 볼 수 있다. 이 Pull Request가 주 브랜치에 Merge 되면 어떻게 달라지는지 보여준다. git diff 명령을 빌어 표현하자면 `git diff master…​<branch>`와 같은 명령이 실행되는 거고 `<branch>`는 Pull Request의 브랜치를 의미한다. 무슨 내용인지 확인하기에서 자세히 설명한다.

그 외 알아두면 좋은 것은 GitHub은 Pull Request가 Merge 될 수 있는지 검사해서 서버에서 Merge 할 수 있도록 Merge 버튼을 제공한다. 이 버튼은 저장소에 쓰기 권한이 있는 사람만 볼 수 있고 이 버튼으로 Merge 하면 Merge 커밋이 생긴다(Trivial Merge). “fast-forward” Merge가 가능할 때도 ‘`non-fast-forwrd’'로 Merge 한다.

로컬에 Pull Request 브랜치를 당겨와서 Merge 해도 된다. master 브랜치에 Merge 해서 GitHub에 Push 하면 자동으로 해당 Pull Request가 닫힌다.

이런 방식이 대부분의 GitHub 프로젝트가 사용하는 기본 워크플로다. 토픽 브랜치를 만들고 Pull Request를 연다. 거기서 토론을 계속 하고 그 브랜치에 커밋을 하기도 한다. 마지막에는 Merge하고 Request를 닫는다.

Note
Fork는 옵션

한 저장소의 두 브랜치를 두고도 Pull Request를 열 수 있다. 한 저장소에 쓰기 권한이 있는 동료 둘이서 어떤 기능을 추가하려고 하고 있다면 토픽 브랜치를 만들고 Push 한다. 그리고 나서 같은 저장소의 master 브랜치에 대해 Pull Request를 만들어 코드 리뷰와 토론을 시작한다. Fork는 필수가 아니다.

Pull Request 팁

GitHub에서 프로젝트에 기여하는 방법 중 가장 기본적인 방법을 살펴봤다. Pull Request를 사용할 때 도움이 되는 유용한 팁을 몇 가지 살펴보자.

Patch를 Pull Request로 보내기

보통 프로젝트에서는 Pull Request의 Patch가 완벽하고 큐처럼 꼭 순서대로 적용돼야 한다고 생각하지 않는다. 메일링 리스트를 사용하던 프로젝트에서는 Patch 순서가 의미가 있다고 생각한다. GitHub의 Pull Request는 어떤 주제를 두고 논의하는 자리다. 논의가 다 무르익으면 Merge 한다.

이 차이는 매우 중요하다. 일반적으로 처음부터 완벽한 코드를 보낼 수 없어서 메일링 리스트로 Patch를 보낼 일은 별로 없다. Pull Request는 초기부터 프로젝트 관리자와 소통할 수 있도록 해주기 때문에 혼자 답을 찾는 게 아니라 커뮤니티에서 함께 찾을 수 있다. 누군가 Pull Request를 열면 관리자와 커뮤니티는 어떻게 수정하는 게 좋을지 의견을 낸다. Patch를 처음부터 다시 전체를 작성하지 않아도 된다. 수정한 만큼만 해당 브랜치에 커밋하고 하던 일과 대화를 계속 해 나가면 된다.

최종 Pull Request로 돌아가서 다시 보면 기여자가 커밋을 Rebase 하거나 Pull Request를 다시 열지 않았다는 것을 확인할 수 있다. 그냥 기존 브랜치에 좀 더 커밋하고 Push 했을 뿐이다. 나중에 시간이 지나서 이 Pull Request를 다시 읽으면 왜 이런 방향으로 결정했는지에 대한 맥락을 쉽게 알 수 있다. 웹사이트에서 “Merge” 버튼을 누르면 Merge 커밋을 일부러 남기겠다는 뜻이 된다. 이 Merge 커밋에는 Pull Request 정보가 들어가기 때문에 필요하면 언제든지 맥락을 확인할 수 있다.

Pull Request를 최신으로 업데이트하기

Pull Request가 만든 지 오래됐거나 깨끗하게 Merge 되지 않으면 메인테이너가 쉽게 Merge 할 수 있게 수정한다. GitHub은 자동으로 Merge 할 수 있는 Pull Request인지 아닌지 Pull Request 페이지 하단에서 알려준다.

PR Merge 실패
Figure 97. 깨끗하게 Merge 할 수 없는 Pull Request

깨끗하게 Merge 할 수 없는 Pull Request 같은 메시지를 보면 해당 브랜치를 고쳐서 녹색으로 만든다. 메인테이너가 고치지 않아도 되도록 한다.

이 문제를 해결하는 방법은 두 가지가 있다. 대상 브랜치(보통은 master 브랜치)를 기준으로 Rebase 하는 방법이 있고 대상 브랜치를 Pull Request 브랜치에 Merge 하는 방법이 있다.

GitHub을 사용하는 개발자는 대부분 후자를 고른다. 앞서 살펴봤던 것과 같은 이유다. Rebase 하면 히스토리는 깨끗해지지만 훨씬 더 어렵고 에러 나기 쉽다.

Pull Request가 Merge 될 수 있도록 대상 브랜치를 Merge 하려면 먼저 원 저장소를 리모트로 추가한다. 그리고 나서 Fetch 하고 그 저장소의 대상 브랜치를 해당 토픽 브랜치에 Merge 한다. 문제를 해결하고 그 브랜치에 도로 Push 한다.

“tonychacon” 예제에 이 워크플로를 적용해보자. 원저자가 뭔가 수정을 했는데 Pull Request와 충돌이 난다. 여기부터 살펴보자.

$ git remote add upstream https://github.com/schacon/blink (1)

$ git fetch upstream (2)
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From https://github.com/schacon/blink
 * [new branch]      master     -> upstream/master

$ git merge upstream/master (3)
Auto-merging blink.ino
CONFLICT (content): Merge conflict in blink.ino
Automatic merge failed; fix conflicts and then commit the result.

$ vim blink.ino (4)
$ git add blink.ino
$ git commit
[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \
    into slower-blink

$ git push origin slow-blink (5)
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 682 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To https://github.com/tonychacon/blink
   ef4725c..3c8d735  slower-blink -> slow-blink
  1. 원 저장소를 ‘`upstream’'이라는 이름의 리모트로 추가한다

  2. 리모트에서 최신 데이터를 Fetch 한다

  3. 대상 브랜치를 토픽 브랜치에 Merge 한다

  4. 충돌을 해결한다

  5. 동일한 토픽 브랜치에 도로 Push 한다

이렇게 하면 Pull Request는 자동으로 업데이트되고 깨끗하게 Merge 할 수 있는지 재확인된다.

PR 고침
Figure 98. 깨끗하게 Merge 할 수 있는 Pull Request.

연속성은 Git의 장기 중 하나다. 오랫동안 무엇인가 만들고 있다면 최신으로 유지하기 위해 대상 브랜치를 쉽게 Merge 해 올 수 있다. 다 마칠 때까지 하고 또 하고 할 수 있다. Merge 할 때 발생하는 충돌만 해결하면 되고 지속적으로 개발 프로세스를 관리할 수 있다.

브랜치를 꼭 깨끗하게 유지하고 싶어서 Rebase 해야 한다고 생각한다면 이미 열어 놓은 Pull Request에 대고 Push 하지 말아야 한다. 그럼 이 브랜치를 가져다 Merge 해 놓은 사람들은 Rebase 의 위험성에 설명했듯이 충격에 빠질 것이다. 대신 브랜치를 새로 만들어 Push 한다. 그리고 Pull Request도 새로 여는데 원 Pull Request가 뭔지 알 수 있도록 참조를 달고 원래 것은 닫는다.

참조

그럼 바로 "어떻게 Pull Request를 참조시키지?"라는 의문이 들겠지만, 방법은 매우 많다. GitHub에 쓰기 가능한 곳 어디에서나 참조를 달 수 있다.

먼저 Issue와 Pull Request를 서로 참조시키는 방법부터 살펴보자. 모든 Pull Request와 Issue에는 프로젝트 내에서 유일한 번호를 하나 할당한다. 예를 들어, 3인 Pull Request와 #3인 Issue는 동시에 있을 수 없다. `<num>`과 같은 형태로 코멘트가나 설명에 Pull Request와 Issue를 참조시킬 수 있다. 이 방법은 단일 프로젝트 범위에서만 유효하다. Fork 저장소의 Issue나 Pull Request를 참조시키려고 한다면 `username#<num>`라고 쓰고 아예 다른 저장소면 `username/repo#<num>`라고 써야 한다.

설명을 위해 이미 브랜치를 Rebase 했고 Pull Request를 새로 만들었다고 하자. 그럼 예전 Pull Request가 뭔지 알 수 있도록 새것에서 예전 것을 참조하게 해보고 Pull Request의 상호 참조 편집.같이 Fork 한 저장소의 이슈나 아예 다른 저장소의 이슈도 참조하게 해보자.

PR 참조 편집
Figure 99. Pull Request의 상호 참조 편집.

이 Pull Request를 보내면 Pull Request의 상호 참조.처럼 보인다.

PR 참조
Figure 100. Pull Request의 상호 참조.

GitHub URL을 전부 입력해도 딱 필요한 만큼으로 줄어든다.

그리고 원래 있던 Pull Request를 닫으면 새 Pull Request에는 기존 Pull Request가 닫혔다고 언급된다. GitHub은 Pull Request 타임라인에 트랙백 이벤트를 자동으로 만든다. 그래서 이 Pull Request에 방문하는 사람은 예전 Pull Request가 닫혔는지 알 수 있고 그 링크가 있어서 바로 클릭해서 예전 것을 볼 수 있다. 이 링크는 닫은 Pull Request의 트랙백처럼 생겼다.

닫은 PR의 트랙백
Figure 101. 닫은 Pull Request의 트랙백

이슈뿐만 아니라 커밋의 SHA도 참조할 수 있다. 40자 SHA를 적으면 GitHub은 자동으로 해당 커밋에 링크를 걸어 준다. Fork 저장소나 아예 다른 저장소의 커밋도 이슈와 동일한 방식으로 링크시킬 수 있다.

Markdown

다른 이슈를 링크하는 것은 GitHub 글쓰기의 첫걸음에 불과하다. ‘`GitHub Flavored Markdown’'이라는 형식으로 이슈나 Pull Request의 설명, 코멘트, 코드 주석 등에서 글을 쓸 수 있다. Markdown 형식으로 글을 쓰면 그냥 텍스트로 쓴 글이지만 형식을 갖춰 미끈하고 아름답게 렌더링된다.

Markdown 예제.는 Markdown으로 쓴 글이 어떻게 렌더링되는지 보여준다.

Markdown 예제
Figure 102. Markdown 예제.

GitHub Flavored Markdown

GitHub Flavored Markdown(이하 GFM)은 기본 Markdown을 확장했다. GFM은 Pull Request나 이슈 등의 글을 쓸 때 매우 유용하다.

타스크 리스트

GFM이 확장한 것 기능 중 타스크 리스트가 있는데 Pull Request에서 사용하면 좋다. 간단히 말해서 타스크 리스트는 완료했다고 표시할 수 있는 체크박스의 목록이다. 이슈나 Pull Request에서 다 했다고 표기하고 싶을 때 사용한다.

타스크 리스트는 아래와 같이 사용한다.:

- [X] Write the code
- [ ] Write all the tests
- [ ] Document the code

이 타스크 리스트를 이슈나 Pull Request에 사용하면 타스크 리스트.처럼 렌더링된다.

타스크 리스트
Figure 103. 타스크 리스트.

Pull Request를 Merge 하기 전에 꼭 처리해야 하는 일의 목록을 표현할 때 타스크 리스트를 사용한다. Markdown을 직접 고치지 않고 체크박스만 클릭해도 해당 타스크가 완료됐다고 업데이트되기 때문에 상당히 좋은 기능이다.

GitHub은 이슈나 Pull Requests에 있는 타스크 리스트를 집계해서 목록 화면에서 보여준다. 예를 들어, 타스크들이 정리된 Pull Request가 있으면 Pull Request 요약 페이지에서 얼마나 진행됐는지 볼 수 있다. 그래서 Pull Request를 타스크 여러 개로 쪼개 두면 그 브랜치가 얼마나 진행됐는지 알기 쉽다. Pull Request 목록 화면에서 보여주는 타스크 현황.를 보자.

타스크 리스트의 예
Figure 104. Pull Request 목록 화면에서 보여주는 타스크 현황.

Pull Request부터 열어 두고 일을 하면 해당 기능이 얼마나 진행됐는지 쉽게 알 수 있다.

코드 조각

코멘트에 코드 조각도 넣을 수 있다. 실제로 구현해서 브랜치에 커밋하기 전에 뭔가 아이디어를 코드로 표현해 볼 때 좋다. 그 외에도 단순히 코드 예제를 보여주기 위해서 사용하거나 해당 Pull Request에서 구현한 것이 무엇인지 보여줄 때도 사용한다.

백틱으로 된 “Fence” 안에 코드 조각을 넣는다.

```java
for(int i=0 ; i < 5 ; i++)
{
   System.out.println("i is : " + i);
}
```

코드 조각에 언어 이름을 쓰면 GitHub은 구문강조(Syntax Highlight)도 해준다. 구문강조로 미끈해진 코드.는 언어 이름을 넣어서 구문 강조된 결과다.

미끈한 코드
Figure 105. 구문강조로 미끈해진 코드.
인용

아주 긴 글에서 딱 한 부분만 집어서 논의하고 싶을 때 > 문자로 해당 부분을 인용하고 그 밑에 코멘트를 단다. 이 방법은 매우 흔히 사용하는 방법이라, 상당히 유용하고, 단축키도 지원한다. 인용하고 싶은 텍스트를 선택하고 r 키를 누르면 바로 코멘트 상자에 해당 텍스트가 인용된다.

아래와 같이 인용한다.

> Whether 'tis Nobler in the mind to suffer
> The Slings and Arrows of outrageous Fortune,

How big are these slings and in particular, these arrows?

이 텍스트는 인용 예제.처럼 렌더링된다.

인용 예제
Figure 106. 인용 예제.
EMOJI

마지막으로 소개하는 것은 글에 Emoji를 넣을 수 있다는 것이다. Emoji는 GitHub 이슈나 Pull Request에서 정말 많이 사용된다. GitHub은 Emoji를 쉽게 사용할 수 있도록 돕는다. 코멘트를 쓸 때 : 문자로 Emoji 입력을 시작하면 선택해서 자동완성할 수 있도록 Emoji 목록을 보여준다.

Emoji 자동완성
Figure 107. Emoji 자동완성.

Emoji는 :<name>: 형식으로 생겼다. 아래 예제를 보자.

I :eyes: that :bug: and I :cold_sweat:.

:trophy: for :microscope: it.

:+1: and :sparkles: on this :ship:, it's :fire::poop:!

:clap::tada::panda_face:

렌더링되면 Emoji를 많이 쓴 글.처럼 보인다.

Emoji
Figure 108. Emoji를 많이 쓴 글.

Emoji는 정보 전달하는 데도 좋지만 얼마나 재밌고 기쁜지 같은 표현도 가능하다.

Note

Emoji 문자를 사용하는 웹 서비스가 정말 많다. 어떤 Emoji 문자가 있는지 쉽게 찾아볼 수 있는 치트시트가 있어서 두고두고 참고할 수 있다.

이미지

GitHub이 제공하는 글에 이미지를 포함시키는 기능은 기술적으로 GFM이 아니지만 엄청 유용하다. Markdown 형식으로 이미지를 첨부하고 싶을 때 일반적인 방법으로는 이미지를 올리고 그 URL을 찾아서 일일이 입력해야 하는데 번거롭다. GitHub에서는 그냥 이미지를 바로 Drag-and-Drop으로 붙여 넣을 수 있다.

Drag and drop images
Figure 109. 끌어다 놓기로 이미지 자동 붙이기.

Pull Request의 상호 참조 편집.로 돌아가서 보면 Text Area 위에 ‘`Parsed As Markdown’'이라는 표시를 볼 수 있다. 그 링크를 클릭하면 GitHub에서 Markdown을 어떻게 사용하는지 알려주는 치트시트를 보여준다.


posted by REDFORCE 2017. 4. 13. 00:47

출처 : git-scm.com


6.1 GitHub - 계정 만들고 설정하기

GitHub은 가장 큰 Git 저장소 호스트이다. 수백만 개발자가 모여서 수백만 프로젝트를 수행하는 중추다. Git 저장소를 GitHub에 만들어 운영하는 비율이 높다. 많은 오픈 소스 프로젝트는 GitHub을 이용해서 Git 호스팅, 이슈 트래킹, 코드 리뷰, 등등의 일을 한다. Git을 많이 사용하다 보면 Git 프로젝트 자체에는 참여하지 않더라도 GitHub을 꼭 써야 하는 상황이 오거나 스스로 쓰고 싶어질 것이다.

이 장은 GitHub을 잘 쓰는 방법을 설명한다. 계정을 생성해서 관리하는 방법, Git 저장소를 만들고 사용하는 방법, 프로젝트에 기여하거나 다른 사람의 기여를 받아들이는 방법, 프로그래밍 가능한 GitHub 인터페이스, 각종 팁으로 삶을 편하게 만드는 방법을 살펴본다.

프로젝트를 GitHub에 만들 생각이 없거나 GitHub에 있는 프로젝트에 참여할 생각이 없으면 그냥 Git 도구로 넘어가도 된다.

계정 만들고 설정하기

가장 먼저 할 일은 무료 사용자 계정을 만드는 일이다. https://github.com에 방문해서 사용자 이름과 이메일 주소, 암호를 입력하고 ‘`Sign up for GitHub’'이라는 큰 녹색 버튼을 누른다.

GitHub 가입 폼.
Figure 82. GitHub 가입 폼.

다음 보이는 화면은 유료 옵션에 대한 안내 페이지인데, 지금은 무시한다. GitHub는 입력한 이메일 주소로 확인 메일을 보냈을 것이다. 메일의 지시를 따르자. 나중에 살펴볼 테지만 이 과정은 매우 중요하다.

Note

무료 계정도 GitHub 기능을 전부 사용할 수 있다. 딱 한 가지 제약이 있는데 모든 사람이 읽을 수 있는 공개 프로젝트만 만들 수 있다. GitHub에 돈을 내면 비공개 프로젝트도 만들 수 있지만, 이 책에서 설명하지 않는다.

화면 왼쪽 꼭대기에 있는 Octocat 로고를 클릭하면 대시보드 페이지로 이동한다. 이제 GitHub을 사용할 준비가 된 것이다.

SSH 사용하기

이제는 https:// 프로토콜로도 Git 저장소를 사용하는 데 부족함이 없다. 간단히 사용자 이름과 암호로 인증만 하면 된다. 공개 프로젝트를 Clone 하는 데는 인증도 필요 없다. 우리가 만든 계정은 프로젝트를 Fork 하고 그 프로젝트에 Push 할 때가 돼야 비로소 필요하다.

SSH 리모트를 쓰려면 공개키를 설정해야 한다. (아직 공개키가 없으면 SSH 공개키 만들기를 참고) 아래 Windows의 오른쪽 꼭대기에 있는 계정 설정 링크를 클릭하자.

``계정 설정'' 링크.
Figure 83. “계정 설정” 링크.

그 왼쪽 메뉴에서 ‘`SSH keys’'를 선택한다.

``SSH keys'' 링크.
Figure 84. “SSH keys” 링크.

여기서 “Add an SSH key” 버튼을 클릭한다. 키 이름을 적당히 입력하고 ~/.ssh/id_rsa.pub 파일의 내용을 입력 칸에 복사해 넣는다. 그리고 “Add key” 버튼을 클릭한다.

Note

SSH key 이름은 기억하기 쉬운 걸로 짓는다. "내 노트북"이나 "회사 계정"같이 구분하기 쉬운 이름으로 짓는다. 나중에 키를 삭제할 때 헷갈리지 않고 바로 알 수 있도록 짓는 것이 중요하다.

아바타

자동으로 생성해준 아바타를 다른 아바타로 바꿀 수도 있다. “SSh Keys” 탭 위에 있는 “Profile” 탭으로 가서 ‘`Upload new picture’'를 클릭한다.

``Profile'' 링크.
Figure 85. “Profile” 링크.

여기서는 여러분의 하드디스크에 있을 Git 로고를 선택하고 필요한 만큼 자른다.

아바타 자르기.
Figure 86. 아바타 자르기.

이제부터 GitHub 사이트에서 어디에서든 사용자 이름 옆에 아바타가 보인다.

Gravatar 서비스에 아바타를 업로드 한 적이 있으면 자동으로 그 아바타가 사용되고 지금 이 단계를 밟을 필요가 없다.

사용자 이메일 주소

GitHub는 Git 커밋에 있는 이메일 주소를 보고 어떤 사용자인지 식별한다. 사용자가 이메일 주소를 여러 개 사용해서 커밋했어도 GitHub에 그 이메일을 모두 등록하기만 했으면 GitHub는 잘 처리한다. “Emails” 화면에서 모두 등록한다.

이메일 주소를 전부 추가하기.
Figure 87. 이메일 주소 추가하기.

이메일 주소 추가하기.의 이메일 주소는 각각 다른 상태다. 첫 번째 주소는 이미 확인을 한 주(Primary) 주소이다. 알림이나 영수증 메일은 주 주소로 간다. 두 번째 주소도 확인한 주소로 주 주소로 변경 할 수 있는 상태다. 마지막 주소는 아직 확인이 안되어 주 주소로 변경할 수 없다. 저장소의 커밋 메시지에 이 주소 세 개 중 하나라도 있으면 GitHub가 해당 사용자 계정 페이지로 링크를 걸어준다.

투팩터 인증

더 안전한 보안을 위해서 “2FA”(투팩터 인증)을 설정한다. 2FA는 최근 들어 인기가 높아지는 인증 메커니즘이다. 암호를 도둑맞았을 때 위험을 완화하기 위해 사용한다. 2FA를 활성화 시키면 GitHub에 로그인 할 때 인증수단이 두 가지 필요하다(역주 - 기존 로그인 방식에 OTP나 SMS를 추가). 둘 중 한 가지 방법만 뚫려서는 공격자가 계정에 접근할 수 없다.

2FA 설정 화면은 계정 설정 페이지의 Security 탭에 있다.

Security 탭에 있는 2FA
Figure 88. Security 탭에 있는 2FA

“Set up two-factor authentication” 버튼을 클릭하면 2FA 설정 페이지로 이동한다. ‘`TOTP(Time based One-Time 비밀번호’'를 생성하는 스마트폰 앱을 사용하는 방식을 고르거나 GitHub가 인증 코드를 SMS로 전송해주는 방식을 고를 수 있다. 설정하면 로그인할 때 TOTP나 인증코드가 필요하다.

마음에 드는 인증 방법을 고르고 지시에 따라 2FA를 설정한다. GitHub에 로그인할 때마다 한 가지 코드를 더 입력해야 한다. 이제 계정은 좀 더 안전해졌다.


posted by REDFORCE 2017. 4. 13. 00:46

출처 : git-scm.com


5.3 분산 환경에서의 Git - 프로젝트 관리하기

프로젝트 관리하기

효율적으로 기여하는 방법뿐만 아니라 효율적으로 운영하는 방법도 알아야 한다. 언젠가는 단순히 프로젝트에 기여하는 것이 아니라 프로젝트를 직접 운영해야 할 수도 있다. 프로젝트를 운영하는 것은 크게 두 가지로 이루어진다. 하나는 format-patch 명령으로 생성한 Patch를 이메일로 받아서 프로젝트에 Patch를 적용하는 것이다. 다른 하나는 프로젝트의 다른 리모트 저장소로부터 변경 내용을 Merge 하는 것이다. 저장소를 아주 깔끔하고 정돈된 상태로 운영하고 Patch를 적용하거나 수정사항을 확인하기 쉬운 상태로 유지하려면 좋은 운영 방식을 터득해야 한다. 좋은 운영 방식은 다른 사람들이 이해하기 쉽고 프로젝트가 오랫동안 운영돼도 흐트러짐이 없어야 한다.

토픽 브랜치에서 일하기

메인 브랜치에 통합하기 전에 임시로 토픽 브랜치를 하나 만들고 거기에 통합해 보고 나서 다시 메인 브랜치에 통합하는 것이 좋다. 이렇게 하면 Patch를 적용할 때 이리저리 수정해 보기도 하고 좀 더 고민해 봐야 하면 Patch를 적용해둔 채로 나중으로 미룰 수도 있다. 무슨 Patch인지 브랜치 이름에 간단히 적어주면 다른 작업을 하다가 나중에 이 브랜치로 돌아왔을 때 기억해내기 훨씬 수월하다. 프로젝트 관리자라면 이런 토픽 브랜치의 이름을 잘 지어야 한다. 예를 들어 sc 라는 사람이 작업한 Patch라면 sc/ruby_client 처럼 앞에 닉네임을 붙여서 브랜치를 만들 수 있다. master 브랜치에서 새 토픽 브랜치를 아래와 같이 만든다.

$ git branch sc/ruby_client master

checkout -b 명령으로 브랜치를 만들고 Checkout까지 한 번에 할 수 있다.

$ git checkout -b sc/ruby_client master

이렇게 토픽 브랜치를 만들고 Patch를 적용해보고 적용한 내용을 다시 Long-Running 브랜치로 Merge 한다.

이메일로 받은 Patch를 적용하기

이메일로 받은 Patch를 프로젝트에 적용하기 전에 우선 토픽 브랜치에 Patch를 적용한다. Patch를 적용하는 방법은 git apply 명령을 사용하는 것과 git am 명령을 사용하는 것 두 가지가 있다.

apply 명령을 사용하는 방법

git diff`나 Unix의 `diff 명령(다음 절에서 다루겠지만 추천하지 않는 방법)으로 만든 Patch 파일을 적용할 때는 git apply 명령을 사용한다. Patch 파일이 `/tmp/patch-ruby-client.patch`라고 하면 아래와 같은 명령으로 Patch를 적용할 수 있다.

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

위 명령을 실행하면 Patch 파일 내용에 따라 현재 디렉토리의 파일들을 변경한다. 위 명령은 patch -p1명령과 거의 같다. 하지만, 이 명령이 patch 명령보다 훨씬 더 꼼꼼하게 비교한다. git diff`로 생성한 Patch 파일에 파일을 추가하거나, 파일을 삭제하고, 파일의 이름을 변경하는 내용이 들어 있으면 그대로 적용된다. 이런 것은 `patch 명령으로 할 수 없다. 그리고 git apply`는 ``모두 적용, 아니면 모두 취소'' 모델을 사용하기 때문에 Patch를 적용하는 데 실패하면 Patch를 적용하기 이전 상태로 전부 되돌려 놓는다. `patch 명령은 여러 파일에 적용하다가 중간에 실패하면 거기서 그대로 중단하기 때문에 깔끔하지 못하다. git apply`는 `patch 명령보다 훨씬 보수적이다. 이 명령은 자동으로 커밋해 주지 않기 때문에 변경된 파일을 직접 Staging Area에 추가하고 커밋해야 한다.

실제로 Patch를 적용하기 전에 Patch가 잘 적용되는지 한 번 시험해보려면 git apply --check 명령을 사용한다.

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

화면에 아무 내용도 뜨지 않으면 Patch가 깔끔하게 적용됐다는 것이다. 이 명령은 Patch를 적용해 보고 에러가 발생하면 0이 아닌 값을 반환하기 때문에 쉘 스크립트에서도 사용할 수 있다.

am 명령을 사용하는 방법

프로젝트 기여자가 Git의 format-patch 명령을 잘 사용하면 관리자의 작업은 훨씬 쉬워진다. format-patch 명령으로 만든 Patch 파일은 기여자의 정보와 커밋 정보가 포함되어 있기 때문이다. 그래서 기여자가 diff보다 format-patch를 사용하도록 권해야 한다. `git apply`는 기존 Patch 파일에만 사용한다.

format-patch 명령으로 생성한 Patch 파일은 git am 명령으로 적용한다. `git am`은 mbox 파일을 읽어 그 안에 이메일 메시지가 한 개가 있든 여러 개가 있든 처리할 수 있다. mbox 파일은 간단한 텍스트 파일이고 그 내용은 아래와 같다.

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

이 내용은 format-patch 명령으로 생성한 파일의 앞부분이다. 이 파일은 mbox 형식이다. 받은 메일이 git send-email`로 만든 메일이라면 mbox 형식으로 저장하고 이 mbox 파일을 `git am 명령으로 적용한다. 사용하는 메일 클라이언트가 여러 이메일을 mbox 파일 하나로 저장할 수 있다면 메일 여러 개를 한 번에 Patch 할 수 있다.

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

$ git am 0001-limit-log-function.patch
Applying: add limit to log function

Patch가 성공하면 자동으로 새로운 커밋이 하나 만들어진다. 이메일의 `From`과 `Date`에서 저자 정보가, 이메일의 제목과 메시지에서 커밋 메시지가 추출돼 사용된다. 예를 들어 위의 mbox 예제 파일을 적용해서 생성되는 커밋은 아래와 같다.

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   add limit to log function

   Limit log functionality to the first 20

Commit 부분의 커밋 정보는 누가 언제 Patch 했는지 알려 준다. Author 정보는 실제로 누가 언제 Patch 파일을 만들었는지 알려 준다.

Patch에 실패할 수도 있다. 보통 Patch가 생성된 시점보다 해당 브랜치가 너무 업데이트 됐을 때나 아직 적용되지 않은 다른 Patch가 필요한 경우에 일어난다. 이러면 git am 명령은 Patch를 중단하고 사용자에게 어떻게 처리할지 물어온다.

$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

성공적으로 Patch 하지 못하면 git은 Merge 나 Rebase 의 경우처럼 문제를 일으킨 파일에 충돌 표시를 해 놓는다. Merge 나 Rebase 할 때 충돌을 해결하는 것처럼 Patch의 충돌도 해결할 수 있다. 충돌한 파일을 열어서 충돌 부분을 수정하고 나서 Staging Area에 추가하고 git am --resolved 명령을 입력한다.

$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem

충돌이 났을 때 Git에게 좀 더 머리를 써서 Patch를 적용하도록 하려면 -3 옵션을 사용한다. 이 옵션은 Git에게 3-way Patch를 적용해 보라고 하는 것이다. Patch가 어느 시점에서 갈라져 나온 것인지 알 수 없기 때문에 이 옵션은 기본적으로 비활성화돼 있다. 하지만 같은 프로젝트의 커밋이라면 기본 옵션보다 훨씬 똑똑하게 충돌 상황을 해결한다.

$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

위의 경우는 이미 Patch 한 것을 다시 Patch 하는 상황이다. -3 옵션이 없었으면 충돌을 알아서 해결하지 못했을 것이다.

하나의 mbox 파일에 들어 있는 여러 Patch를 적용할 때 am 명령의 대화형 방식을 사용할 수 있다. 이 방식을 사용하면 Patch를 적용할 때마다 묻는다.

$ git am -3 -i mbox
Commit Body is:
---------------------
seeing if this helps the gem
---------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

이 옵션은 Patch를 여러 개 적용할 때 유용하다. 적용하려는 Patch의 내용을 미리 꼭 기억해두지 않아도 되고 적용하기 전에 이미 적용된 Patch인지 알 수 있다.

모든 Patch를 토픽 브랜치에 적용하고 커밋까지 마치면 Long-Running 브랜치에 어떻게 통합할지를 결정해야 한다.

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

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

예를 들어 Jessica가 ruby-client 브랜치에 엄청난 기능을 만들어 놨다고 이메일을 보내왔다. 이 리모트 브랜치를 등록하고 Checkout 해서 테스트한다.

$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

나중에 Jessiaca가 이메일로 또 다른 엄청난 기능을 개발한 브랜치를 보내오면 이미 저장소를 등록했기 때문에 간단히 Fetch 하고 Checkout 할 수 있다.

다른 개발자들과 함께 지속적으로 개발할 때는 이 방식이 가장 사용하기 좋다. 물론 기여하는 사람이 간단한 Patch를 이따금씩만 만들어 내면 이메일로 Patch 파일을 받는 것이 낫다. 기여자가 저장소 서버를 만들어 커밋하고 관리자가 리모트 저장소로 등록해서 Patch를 합치는 작업보다 시간과 노력이 덜 든다. 물론 Patch 한 두 개를 보내는 사람들까지도 모두 리모트 저장소로 등록해서 사용해도 된다. 스크립트나 호스팅 서비스를 사용하면 좀 더 쉽게 관리할 수 있다. 어쨌든 어떤 방식이 좋을지는 우리가 어떻게 개발하고 어떻게 기여할지에 달렸다.

리모트 저장소로 등록하면 커밋의 히스토리도 알 수 있다. Merge 할 때 어디서부터 커밋이 갈라졌는지 알 수 있기 때문에 -3 옵션을 주지 않아도 자동으로 3-way Merge가 적용된다.

리모트 저장소로 등록하지 않고도 Merge 할 수 있다. 계속 함께 일할 개발자가 아닐 때 사용하면 좋다. 아래는 리모트 저장소로 등록하지 않고 URL을 직접 사용하여 Merge를 하는 예이다.

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by recursive.

무슨 내용인지 확인하기

기여물이 포함된 토픽 브랜치가 있으니 이제 그 기여물을 Merge 할지 말지 결정해야 한다. 이번 절에서는 메인 브랜치에 Merge 할 때 필요한 명령어를 살펴본다. 주로 토픽 브랜치를 검토하는데 필요한 명령이다.

먼저 지금 작업하는 브랜치에서 master 브랜치에 속하지 않는 커밋만 살펴보는 것이 좋다. --not 옵션으로 히스토리에서 master 브랜치에 속한 커밋은 제외하고 살펴본다. 앞서 살펴본 master..contrib 형식을 사용하여 확인할 수도 있다. contrib 브랜치에 Patch를 두 개 Merge 했으면 아래와 같은 명령어로 그 결과를 살펴볼 수 있다.

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700

    seeing if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700

    updated the gemspec to hopefully work better

git log 명령에 -p 옵션을 주면 각 커밋에서 실제로 무슨 내용이 변경됐는지 살펴볼 수 있다. 이 옵션은 각 commit의 뒤에 diff의 내용을 출력해 준다.

토픽 브랜치를 다른 브랜치에 Merge 하기 전에 어떤 부분이 변경될지 미리 살펴볼 수 있다. 이때는 색다른 명령을 사용해야 한다. 물론 아래와 같은 명령을 사용할 수도 있다.

$ git diff master

이 명령은 diff 내용을 보여주긴 하지만 잘못된 것을 보여줄 수도 있다. 토픽 브랜치에서 작업하는 동안 master 브랜치에 새로운 커밋이 추가될 수도 있다. 그렇기 때문에 기대하는 diff 결과가 아닐 수 있다. 위 명령은 토픽 브랜치의 마지막 커밋과 master 브랜치의 마지막 커밋을 비교한다. master 브랜치에 한 줄을 추가되면 토픽 브랜치에서 한 줄 삭제한 것으로 보여 준다.

master 브랜치가 가리키는 커밋이 토픽 브랜치의 조상이라면 아무 문제 없다. 하지만, 그렇지 않은 경우라면 이 diff 도구는 토픽 브랜치에만 있는 내용은 추가하는 것이고 master 브랜치에만 있는 내용은 삭제하는 것으로 간주한다.

정말 보고 싶은 것은 토픽 브랜치에 추가한 것이고 결국에는 이것을 master 브랜치에 추가하려는 것이다. 그러니까 master 브랜치와 토픽 브랜치의 공통 조상인 커밋을 찾아서 토픽 브랜치가 현재 가리키는 커밋과 비교해야 한다.

아래와 같은 명령으로 공통 조상인 커밋을 찾고 이 조상 커밋에서 변경된 내용을 살펴본다.

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

이 방법으로 원하는 결과를 얻을 수 있지만, 사용법이 불편하다. Git은 Triple-Dot 구문으로 간단하게 위와 같이 비교하는 방법을 지원한다. diff 명령을 사용할 때 두 브랜치 사이에 `…​`를 쓰면, 두 브랜치의 공통 조상과 브랜치의 마지막 커밋을 비교한다.

$ git diff master...contrib

이 명령은 master 브랜치와 현재 토픽 브랜치에서 달라진 것들만 보여주기 때문에 기억해두면 매우 유용하게 사용할 수 있다.

기여물 통합하기

기여물을 토픽 브랜치에 다 적용하고 Long-Running 브랜치나 master 브랜치로 통합할 준비가 되었다면 이제 어떻게 해야 할까. 프로젝트를 운영하는 데 쓰는 작업 방식은 어떤 것이 있을까. 앞으로 그 예제 몇 가지를 살펴본다.

Merge 하는 워크플로

바로 master 브랜치에 Merge 하는 것이 가장 간단하다. 이 워크플로에서는 master 브랜치가 안전한 코드라고 가정한다. 토픽 브랜치를 검증하고 master 브랜치로 Merge 할 때마다 토픽 브랜치를 삭제한다.여러 토픽 브랜치 히스토리. 처럼 ruby_client 브랜치와 php_client 브랜치가 있을 때 ruby_client 브랜치를 master 브랜치로 Merge 한 후 php_client 브랜치를 Merge 하면 Merge 한 후의 저장소. 같아진다.

여러 토픽 브랜치 히스토리.
Figure 73. 여러 토픽 브랜치 히스토리.
Merge 한 후의 저장소.
Figure 74. Merge 한 후의 저장소.

이 워크플로에서 가장 간단한 시나리오다. 프로젝트의 규모가 커지거나 코드를 더 안정적으로 관리할 때는 이렇게 쉽게 Merge 되지 않을 것이다.

개발자가 많고 규모가 큰 프로젝트에서는 최소한 두 단계로 Merge 하는 것이 좋다. 살펴볼 예에서는 Long-Running 브랜치를 두 개를 유지한다. master 브랜치는 아주 안정적인 버전을 릴리즈하기 위해서 사용한다. develop 브랜치는 새로 수정된 코드를 통합할 때 사용한다. 그리고 두 브랜치를 모두 공개 저장소에 Push 한다. 우선 develop 브랜치에 토픽 브랜치(토픽 브랜치를 Merge 하기 전.)를 토픽 브랜치를 Merge 한 후. 같이 Merge 한다. 그 후에 릴리즈해도 될만한 수준이 되면 master 브랜치를 develop 브랜치까지 Fast-forward시킨다(토픽 브랜치를 릴리즈한 후.).

토픽 브랜치를 Merge 하기 전.
Figure 75. 토픽 브랜치를 Merge 하기 전.
토픽 브랜치를 Merge 한 후.
Figure 76. 토픽 브랜치를 Merge 한 후.
토픽 브랜치를 릴리즈한 후.
Figure 77. 토픽 브랜치를 릴리즈한 후.

이 워크플로를 사용하면 프로젝트 저장소를 Clone 하고 나서 개발자가 안정 버전이 필요하면 master 브랜치를 빌드하고 안정적이지 않더라도 좀 더 최신 버전이 필요하면 develop 브랜치를 Checkout 하여 빌드한다. 이 개념을 좀 더 확장해서 사용할 수 있다. 토픽 브랜치를 검증하기 위한 integrate 브랜치를 만들어 Merge 하고 토픽 브랜치가 검증되면 develop 브랜치에 Merge 한다. 그리고 develop 브랜치에서 충분히 안정하다는 것이 증명되면 그때 master 브랜치에 Merge 한다.

대규모 Merge 워크플로

Git을 개발하는 프로젝트는 Long-Running의 브랜치를 4개 운영한다. 각 브랜치 이름은 masternextpu(Proposed Updates), maint 이다. `maint`는 마지막으로 릴리즈한 버전을 지원하는 브랜치다. 기여자가 새로운 기능을 제안하면 관리자는 토픽 브랜치를 동시에 여러 개 관리하는 것은 복잡하다. 처럼 자신의 저장소에 토픽 브랜치를 만들어 관리한다. 그리고 토픽에 부족한 점은 없는지, 안정적인지 계속 테스트한다. 안정화되면 `next`로 Merge 하고 저장소에 Push 한다. 그러면 모두가 잘 통합됐는지 확인할 수 있다.

토픽 브랜치를 동시에 여러 개 관리하는 것은 복잡하다.
Figure 78. 토픽 브랜치를 동시에 여러 개 관리하는 것은 복잡하다.

토픽 브랜치가 좀 더 개선돼야 하면 next`가 아니라 `pu`에 Merge 한다. 그 후에 충분히 검증을 마치면 `pu`에서 `next`로 옮기고 `next`를 기반으로 `pu`를 다시 만든다. `next`에는 아직 `master`에 넣기에 모자라 보이는 것들이 들어 있다. 즉 `next 브랜치는 정말 가끔 Rebase 하고 `pu`는 자주 Rebase 하지만 `master`는 항상 Fast-forward 한다.

토픽 브랜치를 Long-Running 브랜치로 Merge 하기.
Figure 79. 토픽 브랜치를 Long-Running 브랜치로 Merge 하기.

토픽 브랜치가 결국 master 브랜치로 Merge 되면 저장소에서 삭제한다. 그리고 이전 릴리즈 버전에 Patch가 필요하면 maint 브랜치를 이용해 대응한다. Git을 개발하는 프로젝트를 Clone 하면 브랜치가 4개 있고 각 브랜치를 이용하여 진행사항을 확인해볼 수 있다. 그래서 새로운 기능을 추가하려면 적당한 브랜치를 보고 고른다. 이 워크플로는 잘 구조화돼 있어서 코드가 새로 추가돼도 테스트하기 쉽다.

Rebase와 Cherry-Pick 워크플로

히스토리를 한 줄로 관리하려고 Merge 보다 Rebase 나 Cherry-Pick을 더 선호하는 관리자들도 있다. 토픽 브랜치에서 작업을 마친 후 master에 통합할 때 master 브랜치를 기반으로 Rebase 한다. 그러면 커밋이 다시 만들어 진다. master 대신 develop 등의 브랜치에도 가능하다. 문제가 없으면 master 브랜치를 Fast-forward시킨다. 이렇게 히스토리를 한 줄로 유지할 수 있다.

한 브랜치에서 다른 브랜치로 작업한 내용을 옮기는 또 다른 방식으로 Cherry-pick이란 것도 있다. Git의 Cherry-pick은 커밋 하나만 Rebase 하는 것이다. 커밋 하나로 Patch 내용을 만들어 현재 브랜치에 적용을 하는 것이다. 토픽 브랜치에 있는 커밋 중에서 하나만 고르거나 토픽 브랜치에 커밋이 하나밖에 없을 때 Rebase 보다 유용하다. 아래와 같은 예를 들어보자.

Cherry-pick을 실행하기 전의 저장소.
Figure 80. Cherry-pick을 실행하기 전의 저장소.

e43a6 커밋 하나만 현재 브랜치에 적용하려면 아래와 같은 명령을 실행한다.

$ git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

위 명령을 실행하면 e43a6 커밋에서 변경된 내용을 현재 브랜치에 똑같이 적용을 한다. 하지만, 변경을 적용한 시점이 다르므로 새 커밋의 SHA-1 해시값은 달라진다. 명령을 실행하고 나면 아래와 같이 된다.

Cherry-pick 방식으로 커밋 하나를 적용한 후의 저장소.
Figure 81. Cherry-pick 방식으로 커밋 하나를 적용한 후의 저장소.

Rebase 나 Cherry-pick 방식으로 토픽 브랜치를 합치고 나면 필요없는 토픽 브랜치나 커밋은 삭제한다.

Rerere

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

Rerere의 뜻은 “reuse recorded resolution”(충돌 해결방법 재사용)으로 수작업으로 충돌 해결하던 것을 쉽게 해준다. rerere 기능이 활성화 돼 있으면 Merge가 성공할 때 마다 그 이전과 이후 상태를 저장해둔다. 나중에 충돌이 발생하면 비슷한 상황에서 Merge가 성공한 적이 있었는지 찾아보고 해결이 가능하다면 자동으로 해결한다.

Rerere 기능의 동작은 두 부분으로 나누어 볼 수 있다. Rerere 기능을 설정하는 부분과 Rerere 기능을 명령으로 사용하는 부분이다. 설정은 rerere.enabled 값을 설정하면 되는데 글로벌 설정에 저장해 두고 사용하면 편하다.

$ git config --global rerere.enabled true

이제부터 Merge가 성공할 때 마다 전후 상황을 기록해두고 나중에 충돌이 나면 사용할 수 있게 됐다.

필요하다면 git rerere 명령을 사용하여 저장된 캐시를 바탕으로 대화형 인터페이스를 통해 충돌을 다룰 수도 있다. git rerere 명령을 직접 실행하면 현재 Merge 과정에서 발생한 충돌을 해결하는데 참고할 만한 이전 Merge 기록을 찾아준다(rerere.enabled 옵션이 켜져 있다면 자동). Rerere 기능을 사용할 때 기록할 내용을 세세하게 설정하거나, 기록된 내용 중에 특정 기록을 지운다거나 하는 보조 명령도 제공한다. 자세한 내용은 Rerere 에서 다루기로 한다.

릴리즈 버전에 태그 달기

적당한 때가 되면 릴리즈해야 한다. 그리고 언제든지 그 시점으로 되돌릴 수 있게 태그를 다는 것이 좋다.Git의 기초에서 살펴본 대로 태그를 달면 된다. 서명된 태그를 달면 아래와 같이 출력된다.

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

태그에 서명하면 서명에 사용한 PGP 공개키도 배포해야 한다. Git 개발 프로젝트는 관리자의 PGP 공개키를 Blob 형식으로 Git 저장소에 함께 배포한다. 이 Blob 파일을 사용하여 태그에 서명했다. gpg --list-keys 명령으로 어떤 PGP 공개키를 포함할지 확인한다.

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon <schacon@gmail.com>
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

`git hash-object`라는 명령으로 공개키를 바로 Git 저장소에 넣을 수 있다. 이 명령은 Git 저장소 안에 Blob 형식으로 공개키를 저장해주고 그 Blob의 SHA-1 값을 알려준다.

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

hash-object 명령으로 구한 SHA-1 해시값으로 PGP 공개키를 가리키는 태그를 만든다.

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

git push --tags 명령으로 앞서 만든 maintainer-pgp-pub 태그를 공유한다. 다른 사람이 태그의 서명을 확인하려면 우선 Git 저장소에 저장된 PGP 공개키를 꺼내서 GPG키 데이터베이스에 저장해야 한다.

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

사람들은 이렇게 공개키를 얻어서 서명된 태그를 확인한다. 또한, 관리자가 태그 메시지에 서명을 확인하는 방법을 적어 놓으면 좋다. `git show <tag>`으로 어떻게 서명된 태그를 확인하는지 설명한다.

빌드넘버 만들기

Git은 v123 처럼 숫자 형태로 커밋 이름을 만들지 않기 때문에 사람이 기억하기 어렵다. 하지만 git describe 명령으로 좀 더 사람이 기억하기 쉬운 이름을 얻을 수 있다. Git은 가장 가까운 태그의 이름과, 태그에서 얼마나 더 커밋이 쌓였는지, 그리고 해당 커밋의 SHA-1 값을 조금 가져다가 이름을 만든다.

$ git describe master
v1.6.2-rc1-20-g8c5b85c

이렇게 사람이 읽을 수 있는 이름으로 스냅샷이나 빌드를 만든다. 만약 저장소에서 Clone 한 후 소스코드로 Git을 설치하면 git --version 명령은 이렇게 생긴 빌드넘버를 보여준다. 태그가 달린 커밋에 git describe 명령을 사용하면 다른 정보 없이 태그 이름만 사용한다.

git describe 명령은 -a`나 `-s 옵션을 주고 만든 Annotated 태그가 필요하다. 릴리즈 태그는 git describe 명령으로 만드니까 꼭 이름이 적당한지 사전에 확인해야 한다. 그리고 이 값은 checkout이나 show 명령에도 사용할 수 있지만, 전적으로 이름 뒤에 붙은 SHA-1 값을 사용한다. 그래서 이 값으로는 해당 커밋을 못 찾을 수도 있다. 최근 Linux 커널은 충돌 때문에 축약된 SHA-1가 8자에서 10자로 늘어났다. 이제는 8자일 때 생성한 값은 사용할 수 없다.

릴리즈 준비하기

배포할 릴리즈 버전이 준비되었다. 먼저 Git을 사용하지 않는 사람을 위해 소스코드 스냅샷을 압축한다. 쉽게 압축할 수 있도록 Git은 git archive 명령을 지원한다.

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

이 압축 파일을 풀면 프로젝트의 가장 마지막 스냅샷이 나온다. ZIP 형식으로 압축 파일을 만들려면 --format=zip 옵션을 사용한다.

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

이렇게 압축한 스냅샷 파일은 웹사이트나 이메일로 사람들에게 배포할 수 있다.

Shortlog 보기

이메일로 프로젝트의 변경 사항을 사람들에게 알려야 할 때, git shortlog 명령을 사용하면 지난 릴리즈 이후의 변경 사항 목록을 쉽게 얻어올 수 있다. git shortlog 명령은 주어진 범위에 있는 커밋을 요약해준다. 아래는 최근 릴리즈 버전인 v1.0.1 이후의 커밋을 요약해 주는 예제이다.

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (8):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

이렇게 v1.0.1 이후 변경 내용을 Author를 기준으로 정리한 커밋을 이메일로 전송한다.


posted by REDFORCE 2017. 4. 13. 00:45

출처 : git-scm.com


5.2 분산 환경에서의 Git - 프로젝트에 기여하기

프로젝트에 기여하기

프로젝트에 기여하는 방식을 설명하는데 가장 어려운 점은 그 방식이 매우 다양하다는 점이다. Git이 워낙 유연하게 설계됐기 때문에 사람들은 여러 가지 방식으로 사용할 수 있다. 게다가 프로젝트마다 환경이 달라서 프로젝트에 기여하는 방식을 쉽게 설명하기란 정말 어렵다. 기여하는 방식에 영향을 끼치는 몇 가지 변수가 있다. 활발히 기여하는 개발자의 수가 얼마인지, 선택한 워크플로가 무엇인지, 각 개발자에게 접근 권한을 어떻게 부여했는지, 외부에서도 기여할 수 있는지 등이 변수다.

첫 번째로 살펴볼 변수는 활발히 활동하는 개발자의 수이다. 얼마나 많은 개발자가 얼마나 자주 코드를 쏟아 내는가 하는 점이 활발한 개발자의 기준이다. 대부분 둘, 셋 정도의 개발자가 하루에 몇 번 커밋을 하고 활발하지 않은 프로젝트는 더 띄엄띄엄할 것이다. 하지만, 아주 큰 프로젝트는 수백, 수천 명의 개발자가 하루에도 수십, 수백 개의 커밋을 만들어 낸다. 개발자가 많으면 많을수록 코드를 깔끔하게 적용하거나 Merge 하기 어려워진다. 어떤 커밋은 다른 개발자가 이미 기여한 것으로 불필요해지기도 하고 때론 서로 충돌이 일어난다. 어떻게 해야 코드를 최신으로 유지하면서 원하는 대로 수정할 수 있을까?

두 번째 변수는 프로젝트에서 선택한 워크플로다. 개발자 모두가 메인 저장소에 쓰기 권한을 갖는 중앙집중형 방식인가? 프로젝트에 모든 Patch를 검사하고 통합하는 관리자가 따로 있는가? 모든 수정사항을 개발자끼리 검토하고 승인하는가? 자신이 그저 돕는게 아니라 어떤 책임을 맡고 있는지? 중간 관리자가 있어서 그들에게 먼저 알려야 하는가?

세 번째 변수는 접근 권한이다. '프로젝트에 쓰기 권한이 있어서 직접 쓸 수 있는가? 아니면 읽기만 가능한가?'에 따라서 프로젝트에 기여하는 방식이 매우 달라진다. 쓰기 권한이 없다면 어떻게 수정 사항을 프로젝트에 반영할 수 있을까? 수정사항을 적용하는 정책이 프로젝트에 있는가? 얼마나 많은 시간을 프로젝트에 할애하는가? 얼마나 자주 기여하는가?

이런 질문에 따라 프로젝트에 기여하는 방법과 워크플로가 달라진다. 간단한 것부터 복잡한 것까지 예제를 통해 각 상황을 살펴보면 실제 프로젝트에 필요한 워크플로를 선택하는 데 도움이 될 것이다.

커밋 가이드라인

다른 것보다 먼저 커밋 메시지에 대한 주의사항을 알아보자. 커밋 메시지를 잘 작성하는 가이드라인을 알아두면 다른 개발자와 함께 일하는 데 도움이 많이 된다. Git 프로젝트에 보면 커밋 메시지를 작성하는데 참고할 만한 좋은 팁이 많다. Git 프로젝트의 Documentation/SubmittingPatches 문서를 참고하자.

무엇보다도 먼저 공백문자를 깨끗하게 정리하고 커밋해야 한다. Git은 공백문자를 검사해볼 수 있는 간단한 명령을 제공한다. 커밋을 하기 전에 git diff --check 명령으로 공백문자에 대한 오류를 확인할 수 있다.

`git diff --check`의 결과.
Figure 57. `git diff --check`의 결과.

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

그리고 각 커밋은 논리적으로 구분되는 Changeset이다. 최대한 수정사항을 한 주제로 요약할 수 있어야 하고 여러 가지 이슈에 대한 수정사항을 하나의 커밋에 담지 않아야 한다. 여러 가지 이슈를 한꺼번에 수정했다고 하더라도 Staging Area를 이용하여 한 커밋에 이슈 하나만 담기도록 한다. 작업 내용을 분할하고, 각 커밋마다 적절한 메시지를 작성한다. 같은 파일의 다른 부분을 수정하는 경우에는 git add -patch명령을 써서 한 부분씩 나누어 Staging Area에 저장해야 한다(관련 내용은 대화형 명령 에서 다룬다). 결과적으로 최종 프로젝트의 모습은 한 번에 커밋을 하든 다섯 번에 나누어 커밋을 하든 똑같다. 하지만, 여러 번 나누어 커밋하는 것이 다른 동료가 수정한 부분을 확인할 때나 각 커밋의 시점으로 복원해서 검토할 때 이해하기 훨씬 쉽다. 히스토리 단장하기 에서 이미 저장된 커밋을 다시 수정하거나 파일을 단계적으로 Staging Area에 저장하는 방법을 살펴본다. 다양한 도구를 이용해서 간단하고 이해하기 쉬운 커밋을 쌓아가야 한다.

마지막으로 명심해야 할 점은 커밋 메시지 자체다. 좋은 커밋 메시지를 작성하는 습관은 Git을 사용하는 데 도움이 많이 된다. 일반적으로 커밋 메시지를 작성할 때 사용하는 규칙이 있다. 메시지의 첫 라인에 50자가 넘지 않는 아주 간략한 메시지를 적어 해당 커밋을 요약한다. 다음 한 라인은 비우고 그다음 라인부터 커밋을 자세히 설명한다. 예를 들어 Git 개발 프로젝트에서는 개발 동기와 구현 상황의 제약 조건이나 상황 등을 자세하게 요구한다. 이런 점은 따를 만한 좋은 가이드라인이다. 그리고 현재형 표현을 사용하는 것이 좋다. 명령문으로 시작하는 것도 좋은 방법이다. 예를 들어 “I added tests for (테스트를 추가함)” 보다는 “Add tests for (테스트 추가)” 와 같은 메시지를 작성한다. 아래 내용은 Tim Pope가 작성한 커밋 메시지의 템플릿이다.

영문 50글자 이하의 간략한 수정 요약

자세한 설명. 영문 72글자 이상이 되면
라인 바꿈을 하고 이어지는 내용을 작성한다.
특정 상황에서는 첫 번째 라인이 이메일
메시지의 제목이 되고 나머지는 메일
내용이 된다. 빈 라인은 본문과 요약을
구별해주기에 중요하다(본문 전체를 생략하지 않는 한).

이어지는 내용도 한 라인 띄우고 쓴다.

  - 목록 표시도 사용할 수 있다.

  - 보통 '-' 나 '*' 표시를 사용해서 목록을 표현하고
    표시 앞에 공백 하나, 각 목록 사이에는 빈 라인
    하나를 넣는데, 이건 상황에 따라 다르다.

메시지를 이렇게 작성하면 함께 일하는 사람은 물론이고 자신에게도 매우 유용하다. Git 개발 프로젝트에는 잘 쓰인 커밋 메시지가 많으므로 프로젝트를 내려받아서 git log --no-merges 명령으로 꼭 살펴보기를 권한다.

이 책에서 설명하는 예제의 커밋 메시지는 시간 관계상 위와 같이 아주 멋지게 쓰지 않았다. git commit명령에서 -m 옵션을 사용하여 간단하게 적는다. 하지만! 저자처럼 하지 말고 시키는 대로 하는 것이 좋다.

비공개 소규모 팀

두세 명으로 이루어진 비공개 프로젝트가 가장 간단한 프로젝트일 것이다. ‘`비공개’'라고 함은 소스코드가 공개되지 않은 것을 말하는 것이지 외부에서 접근할 수 없는 것을 말하지 않는다. 모든 개발자는 공유하는 저장소에 쓰기 권한이 있어야 한다.

이런 환경에서는 보통 Subversion 같은 중앙집중형 버전 관리 시스템에서 사용하던 방식을 사용한다. 물론 Git이 가진 오프라인 커밋 기능이나 브랜치 Merge 기능을 이용하긴 하지만 크게 다르지 않다. 가장 큰 차이점은 서버가 아닌 클라이언트 쪽에서 Merge 한다는 점이다. 두 개발자가 저장소를 공유하는 시나리오를 살펴보자. 개발자인 John은 저장소를 Clone 하고 파일을 수정하고 나서 로컬에 커밋한다 (예제에서 Git이 출력하는 메시지 중 일부는 `…​`으로 줄이고 생략했다).

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

개발자인 Jessica도 저장소를 Clone 하고 나서 파일을 하나 새로 추가하고 커밋한다.

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Jessica는 서버에 커밋을 Push 한다.

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

John도 서버로 커밋을 Push 하려고 한다.

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

Jessica의 Push는 성공했지만, John의 커밋은 서버에서 거절된다. Subversion을 사용했던 사람은 이 부분을 이해하는 것이 중요하다. 같은 파일을 수정한 것도 아닌데 왜 Push가 거절되는 걸까? Subversion에서는 서로 다른 파일을 수정하는 이런 Merge 작업은 자동으로 서버가 처리한다. 하지만, Git은 로컬에서 먼저 Merge 해야 한다. John은 Push 하기 전에 Jessica가 수정한 커밋을 Fetch 하고 Merge 한다.

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

Fetch 하고 나면 John의 로컬 저장소는 아래와 같이 된다.

Fetch 하고 난 John의 저장소.
Figure 58. Fetch 하고 난 John의 저장소.

John은 Jessica가 저장소로 Push 했던 코드를 로컬 저장소에 가져왔다. 하지만, Push 하기 전에 Fetch 한 브랜치를 Merge 해야 한다.

$ git merge origin/master
Merge made by recursive.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Merge가 잘 이루어지면 John의 브랜치는 아래와 같은 상태가 된다.

`origin/master` 브랜치를 Merge 하고 난 후 John의 저장소.
Figure 59. origin/master 브랜치를 Merge 하고 난 후 John의 저장소.

John은 Merge 하고 나서 자신이 작업한 코드가 제대로 동작하는지 확인한다. 그 후에 공유하는 저장소에 Push 한다.

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

이제 John의 저장소는 아래와 같이 되었다.

Push 하고 난 후 John의 저장소.
Figure 60. Push 하고 난 후 John의 저장소.

동시에 Jessica는 토픽 브랜치를 하나 만든다. issue54 브랜치를 만들고 세 번에 걸쳐서 커밋한다. 아직 John의 커밋을 Fetch 하지 않은 상황이기 때문에 아래와 같은 상황이 된다.

Jessica의 토픽 브랜치.
Figure 61. Jessica의 토픽 브랜치.

Jessica는 John의 작업을 적용하려면 Fetch 해야 한다.

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

위 명령으로 John이 Push 한 커밋을 모두 내려받는다. 그러면 Jessica의 저장소는 아래와 같은 상태가 된다.

John의 커밋을 Fetch 한 후 Jessica의 저장소.
Figure 62. John의 커밋을 Fetch 한 후 Jessica의 저장소.

이제 orgin/master`와 Merge 할 차례다. Jessica는 토픽 브랜치에서의 작업을 마치고 어떤 내용이 Merge 되는지 `git log 명령으로 확인한다.

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   removed invalid default value

issue54..origin/master 문법은 히스토리를 검색할 때 뒤의 브랜치(origin/master)에 속한 커밋 중 앞의 브랜치(issue54)에 속하지 않은 커밋을 검색하는 문법이다. 자세한 내용은 범위로 커밋 가리키기에서 다룬다.

앞의 명령에 따라 히스토리를 검색한 결과 John이 생성하고 Jessica가 Merge 하지 않은 커밋을 하나 찾았다. origin/master 브랜치를 Merge 하게 되면 검색된 커밋 하나가 로컬 작업에 Merge 될 것이다.

Merge 할 내용을 확인한 Jessica는 자신이 작업한 내용과 John이 Push 한 작업(origin/master)을 master브랜치에 Merge 하고 Push 한다. 모든 내용을 합치기 전에 우선 master 브랜치를 Checkout 한다.

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

origin/masterissue54 모두 Upstream 브랜치이기 때문에 둘 중에 무엇을 먼저 Merge 하든 상관이 없다. 물론 어떤 것을 먼저 Merge 하느냐에 따라 히스토리 순서는 달라지지만, 최종 결과는 똑같다. Jessica는 먼저 issue54 브랜치를 Merge 한다.

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

보다시피 Fast-forward Merge 이기 때문에 별 문제 없이 실행된다. 다음은 John의 커밋(origin/master)을 Merge 한다.

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

위와 같이 Merge가 잘 되면 그림 아래와 같은 상태가 된다.

John의 커밋을 Merge 후 Jessica의 저장소.
Figure 63. John의 커밋을 Merge 후 Jessica의 저장소.

origin/master 브랜치가 Jessica의 master 브랜치로 나아갈(reachable) 수 있기 때문에 Push는 성공한다(물론 John이 그 사이에 Push 하지 않았다면).

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

두 개발자의 커밋을 성공적으로 Merge 하고 나면 결과는 아래와 같다.

Jessica가 서버로 Push 하고 난 후의 저장소.
Figure 64. Jessica가 서버로 Push 하고 난 후의 저장소.

매우 간단한 상황의 예제를 살펴보았다. 토픽 브랜치에서 수정하고 로컬의 master 브랜치에 Merge 한다. 작업한 내용을 프로젝트의 공유 저장소에 Push 하고자 할 때는 우선 origin/master 브랜치를 Fetch 하고 Merge 한다. 그리고 나서 Merge 한 결과를 다시 서버로 Push 한다. 이런 워크플로가 일반적이며 아래와 같이 나타낼 수 있다.

여러 개발자가 Git을 사용하는 워크플로.
Figure 65. 여러 개발자가 Git을 사용하는 워크플로.

비공개 대규모 팀

이제 비공개 대규모 팀에서의 역할을 살펴보자. 이런 상황에는 보통 팀을 여러 개로 나눈다. 그래서 각각의 작은 팀이 서로 어떻게 하나로 Merge 하는지를 살펴본다.

John과 Jessica는 어떤 기능을 함께 작업하게 됐다. 물론 각각 다른 일도 한다. 이런 상황이라면 회사는 Integration-manager 워크플로를 선택하는 게 좋다. 작은 팀이 수행한 결과물은 Integration-Manager가 Merge 하고 공유 저장소의 master 브랜치를 업데이트한다. 팀마다 브랜치를 하나씩 만들고 Integration-Manager는 그 브랜치를 Pull 해서 Merge 한다.

두 팀에 모두 속한 Jessica의 작업 순서를 살펴보자. 우선 Jessica는 저장소를 Clone 하고 featureA 작업을 먼저 한다. featureA 브랜치를 만들고 수정하고 커밋한다.

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

이 수정한 부분을 John과 공유해야 한다. 공유하려면 우선 featureA 브랜치를 서버로 Push 한다. Integration-Manager만 master 브랜치를 업데이트할 수 있기 때문에 master 브랜치로 Push를 할 수 없고 다른 브랜치로 John과 공유한다.

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica는 자신이 한 일을 featureA 라는 브랜치로 Push 했다는 이메일을 John에게 보낸다. John의 피드백을 기다리는 동안 Jessica는 Josie와 함께 하는 featureB 작업을 하기로 한다. 서버의 master 브랜치를 기반으로 새로운 브랜치를 하나 만든다.

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

몇 가지 작업을 하고 featureB 브랜치에 커밋한다.

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

그럼 Jessica의 저장소는 그림 아래와 같다.

Jessica의 저장소.
Figure 66. Jessica의 저장소.

작업을 마치고 Push 하려고 하는데 Jesie가 이미 일부 작업을 하고 서버에 featureBee 브랜치로 Push 했다는 이메일을 보내왔다. Jessica는 Jesie의 작업을 먼저 Merge 해야만 Push 할 수 있다. Merge 하기 위해서 우선 `git fetch`로 Fetch 한다.

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Fetch 해 온 브랜치를 git merge 명령으로 Merge 한다.

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Push 하려고 하는데 작은 문제가 생겼다. Jessica는 featureB 브랜치에서 작업을 했는데 서버에는 브랜치가 featureBee 라는 이름으로 되어 있다. 그래서 git push 명령으로 Push 할 때 로컬 브랜치 featureB 뒤에 콜론(:)과 함께 서버 브랜치 이름을 직접 지정해 준다.

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

이것은 refspec 이란 것을 사용하는 것인데 Refspec 에서 자세하게 설명한다. 명령에서 사용한 -u 옵션은 --set-upstream 옵션의 짧은 표현인데 브랜치를 추적하도록 설정해서 이후 Push 나 Pull 할 때 좀 더 편하게 사용할 수 있다.

John이 몇 가지 작업을 하고 나서 featureA 에 Push 했고 확인해 달라는 내용의 이메일을 보내왔다. Jessica는 `git fetch`로 Push 한 작업을 Fetch 한다.

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

어떤 것이 업데이트됐는지 git log 명령으로 확인한다.

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

확인을 마치면 로컬의 featureA 브랜치로 Merge 한다.

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Jessica는 일부 수정하고, 수정한 내용을 다시 서버로 Push 한다.

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

위와 같은 작업을 마치고 나면 Jessica의 저장소는 아래와 같은 모습이 된다.

마지막 Push 하고 난 후의 Jessica의 저장소.
Figure 67. 마지막 Push 하고 난 후의 Jessica의 저장소.

그럼 featureA`와 `featureBee 브랜치가 프로젝트의 메인 브랜치로 Merge 할 준비가 되었다고 Integration-Manager에게 알려준다. Integration-Manager가 두 브랜치를 모두 Merge 하고 난 후에 메인 브랜치를 Fetch 하면 아래와 같은 모양이 된다.

두 브랜치가 메인 브랜치에 Merge 된 후의 저장소.
Figure 68. 두 브랜치가 메인 브랜치에 Merge 된 후의 저장소.

수많은 팀의 작업을 동시에 진행하고 나중에 Merge 하는 기능을 사용하려고 다른 버전 관리 시스템에서 Git으로 바꾸는 조직들이 많아지고 있다. 팀은 자신의 브랜치로 작업하지만, 메인 브랜치에 영향을 끼치지 않는다는 점이 Git의 장점이다. 아래는 이런 워크플로를 나타내고 있다.

Managed 팀의 워크플로.
Figure 69. Managed 팀의 워크플로.

공개 프로젝트 Fork

비공개 팀을 운영하는 것과 공개 팀을 운영하는 것은 약간 다르다. 공개 팀을 운영할 때는 모든 개발자가 프로젝트의 공유 저장소에 직접적으로 쓰기 권한을 가지지는 않는다. 그래서 프로젝트의 관리자는 몇 가지 일을 더 해줘야 한다. Fork를 지원하는 Git 호스팅에서 Fork를 통해 프로젝트에 기여하는 법을 예제를 통해 살펴본다. Git 호스팅 사이트(GitHub, BitBucket, Google Code, repo.or.cz 등) 대부분은 Fork 기능을 지원하며 프로젝트 관리자는 보통 Fork 하는 것으로 프로젝트를 운영한다. 다른 방식으로 이메일과 Patch를 사용하는 방식도 있는데 뒤이어 살펴본다.

우선 처음 할 일은 메인 저장소를 Clone 하는 것이다. 그리고 나서 토픽 브랜치를 만들고 일정 부분 기여한다. 그 순서는 아래와 같다.

$ git clone (url)
$ cd project
$ git checkout -b featureA
# (work)
$ git commit
# (work)
$ git commit
Note

rebase -i 명령을 사용하면 여러 커밋을 하나의 커밋으로 합치거나 프로젝트의 관리자가 수정사항을 쉽게 이해하도록 커밋을 정리할 수 있다. 히스토리 단장하기 에서 대화식으로 Rebase 하는 방법을 살펴본다.

일단 프로젝트의 웹사이트로 가서 “Fork” 버튼을 누르면 원래 프로젝트 저장소에서 갈라져 나온, 쓰기 권한이 있는 저장소가 하나 만들어진다. 그러면 로컬에서 수정한 커밋을 원래 저장소에 Push 할 수 있다. 그 저장소를 로컬 저장소의 리모트 저장소로 등록한다. 예를 들어 myfork로 등록한다.

$ git remote add myfork (url)

자 이제 등록한 리모트 저장소에 Push 한다. 작업하던 것을 로컬 저장소의 master 브랜치에 Merge 한 후 Push 하는 것보다 리모트 브랜치에 바로 Push를 하는 방식이 훨씬 간단하다. 이렇게 하는 이유는 관리자가 토픽 브랜치를 프로젝트에 포함시키고 싶지 않을 때 토픽 브랜치를 Merge 하기 이전 상태로 master 브랜치를 되돌릴 필요가 없기 때문이다. 관리자가 토픽 브랜치를 Merge 하든 Rebase 하든 cherry-pick 하든지 간에 결국 다시 관리자의 저장소를 Pull 할 때는 토픽 브랜치의 내용이 들어 있을 것이다.

$ git push -u myfork featureA

Fork 한 저장소에 Push 하고 나면 프로젝트 관리자에게 이 내용을 알려야 한다. 이것을 'Pull Request’라고 한다. Git 호스팅 사이트에서 관리자에게 보낼 메시지를 생성하거나 git request-pull 명령으로 이메일을 수동으로 만들 수 있다. GitHub의 "pull request" 버튼은 자동으로 메시지를 만들어 주는데 관련 내용은 GitHub 에서 살펴볼 수 있다.

request-pull 명령은 아규먼트를 두 개 입력받는다. 첫 번째 아규먼트는 작업한 토픽 브랜치의 Base 브랜치이다. 두 번째는 토픽 브랜치가 위치한 저장소 URL인데 위에서 등록한 리모트 저장소 이름을 적을 수 있다. 이 명령은 토픽 브랜치 수정사항을 요약한 내용을 결과로 보여준다. 예를 들어 Jessica가 John에게 Pull 요청을 보내는 상황을 살펴보자. Jessica는 토픽 브랜치에 두 번 커밋을 하고 Fork 한 저장소에 Push 했다. 그리고 아래와 같이 실행한다.

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40.
  John Smith (1).
        added a new function

are available in the git repository at.

  git://githost/simplegit.git featureA

Jessica Smith (2).
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

관리자에게 이 내용을 보낸다. 이 내용에는 토픽 브랜치가 어느 시점에 갈라져 나온 것인지, 어떤 커밋이 있는지, Pull 하려면 어떤 저장소에 접근해야 하는지에 대한 내용이 들어 있다.

프로젝트 관리자가 아니라고 해도 보통 origin/master`를 추적하는 `master 브랜치는 가지고 있다. 그래도 토픽 브랜치를 만들고 일을 하면 관리자가 수정 내용을 거부할 때 쉽게 버릴 수 있다. 토픽 브랜치를 만들어서 주제별로 독립적으로 일을 하는 동안에도 주 저장소의 master 브랜치는 계속 수정된다. 하지만 주 저장소의 브랜치의 최근 커밋 이후로 Rebase 하면 깨끗하게 Merge 할 수 있다. 그리고 다른 주제의 일을 하려고 할 때는 앞서 Push 한 토픽 브랜치에서 시작하지 말고 주 저장소의 master 브랜치로부터 만들어야 한다.

$ git checkout -b featureB origin/master
# (work)
$ git commit
$ git push myfork featureB
# (email maintainer)
$ git fetch origin

그림 5-16 처럼 각 토픽은 일종의 실험실이라고 할 수 있다. 각 토픽은 서로 방해하지 않고 독립적으로 수정하고 Rebase 할 수 있다.

featureB 수정작업이 끝난 직후 저장소의 모습.
Figure 70. featureB 수정작업이 끝난 직후 저장소의 모습.

프로젝트 관리자가 사람들의 수정 사항을 Merge 하고 나서 Jessica의 브랜치를 Merge 하려고 할 때 충돌이 날 수도 있다. 그러면 Jessica가 자신의 브랜치를 `origin/master`에 Rebase 해서 충돌을 해결하고 다시 Pull Request을 보낸다.

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

위 명령들을 실행하고 나면 히스토리는 아래와 같아진다.

FeatureA에 대한 Rebase가 적용된 후의 모습.
Figure 71. FeatureA에 대한 Rebase가 적용된 후의 모습.

브랜치를 Rebase 해 버렸기 때문에 Push 할 때 -f 옵션을 주고 강제로 기존 서버에 있던 featureA 브랜치의 내용을 덮어 써야 한다. 아니면 새로운 브랜치를(예를 들어 featureAv2) 서버에 Push 해도 된다.

또 다른 시나리오를 하나 더 살펴보자. 프로젝트 관리자는 featureB 브랜치의 내용은 좋지만, 상세 구현은 다르게 하고 싶다. 관리자는 featureB 담당자에게 상세 구현을 다르게 해달라고 요청한다. featureB담당자는 하는 김에 featureB 브랜치를 프로젝트의 최신 master 브랜치 기반으로 옮긴다. 먼저 origin/master 브랜치에서 featureBv2 브랜치를 새로 하나 만들고, `featureB`의 커밋들을 모두 Squash 해서 Merge 하고, 만약 충돌이 나면 해결하고, 상세 구현을 수정하고, 새 브랜치를 Push 한다.

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
# (change implementation)
$ git commit
$ git push myfork featureBv2

--squash 옵션은 현재 브랜치에 Merge 할 때 해당 브랜치의 커밋을 모두 커밋 하나로 합쳐서 Merge 한다. 이 때 Merge 커밋은 만들지 않는다. 다른 브랜치에서 수정한 사항을 전부 가져오는 것은 똑같다. 하지만 새로 만들어지는 커밋은 부모가 하나이고 커밋을 기록하기 전에 좀 더 수정할 기회도 있다. 다른 브랜치에서 수정한 사항을 전부 가져오면서 그전에 추가적으로 수정할 게 있으면 수정하고 Merge 할 수 있다. 게다가 새로 만들어지는 커밋은 부모가 하나다. --no-commit 옵션을 추가하면 커밋을 합쳐 놓고 자동으로 커밋하지 않는다.

수정을 마치면 관리자에게 featureBv2 브랜치를 확인해 보라고 메시지를 보낸다.

featureBv2 브랜치를 커밋한 이후 저장소 모습.
Figure 72. featureBv2 브랜치를 커밋한 이후 저장소 모습.

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

대규모 프로젝트는 보통 수정사항이나 Patch를 수용하는 자신만의 규칙을 마련해놓고 있다. 프로젝트마다 규칙은 서로 다를 수 있으므로 각 프로젝트의 규칙을 미리 알아둘 필요가 있다. 오래된 대규모 프로젝트는 대부분 메일링리스트를 통해서 Patch를 받아들이는데 예제를 통해 살펴본다.

토픽 브랜치를 만들어 수정하는 작업은 앞서 살펴본 바와 거의 비슷하지만, Patch를 제출하는 방식이 다르다. 프로젝트를 Fork 하여 Push 하는 것이 아니라 커밋 내용을 메일로 만들어 개발자 메일링리스트에 제출한다.

$ git checkout -b topicA
# (work)
$ git commit
# (work)
$ git commit

커밋을 두 번 하고 메일링리스트에 보내 보자. git format-patch 명령으로 메일링리스트에 보낼 mbox 형식의 파일을 생성한다. 각 커밋은 하나씩 이메일 메시지로 생성되는데 커밋 메시지의 첫 번째 라인이 제목이 되고 Merge 메시지 내용과 Patch 자체가 메일 메시지의 본문이 된다. 이 방식은 수신한 이메일에 들어 있는 Patch를 바로 적용할 수 있어서 좋다. 메일 속에는 커밋의 모든 내용이 포함된다. 메일에 포함된 Patch를 적용하는 것은 다음 절에서 살펴본다.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

format-patch 명령을 실행하면 생성한 파일 이름을 보여준다. -M 옵션은 이름이 변경된 파일이 있는지 살펴보라는 옵션이다. 각 파일의 내용은 아래와 같다.

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

메일링리스트에 이메일을 보내기 전에 각 Patch 파일을 손으로 고칠 수 있다. --- 라인과 Patch가 시작되는 라인(`diff --git`로 시작하는 라인) 사이에 내용을 추가하면 다른 개발자는 읽을 수 있지만, 나중에 Patch에 적용되지는 않는다.

특정 메일 프로그램을 사용하거나 이메일을 보내는 명령어로 메일링리스트에 보낼 수 있다. 붙여 넣기로 위의 내용이 그대로 들어가지 않는 메일 프로그램도 있다. 사용자 편의를 위해 공백이나 라인 바꿈 문자 등을 넣어 주는 메일 프로그램은 원본 그대로 들어가지 않는다. 다행히 Git에는 Patch 메일을 그대로 보낼 수 있는 도구가 있다. IMAP 프로토콜로 보낸다. 저자가 사용하는 방법으로 Gmail을 사용하여 Patch 메일을 전송하는 방법을 살펴보자. 추가로 Git 프로젝트의 Documentation/SubmittingPatches 문서의 마지막 부분을 살펴보면 다양한 메일 프로그램으로 메일을 보내는 방법을 설명한다.

메일을 보내려면 먼저 ~/.gitconfig 파일에서 이메일 부분 설정한다. git config 명령으로 추가할 수도 있고 직접 파일을 열어서 추가할 수도 있다. 아무튼, 아래와 같이 설정을 한다.

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = p4ssw0rd
  port = 993
  sslverify = false

IMAP 서버가 SSL을 사용하지 않으면 마지막 두 라인은 필요 없고 host에서 imaps:// 대신 imap://`로 한다. 이렇게 설정하면 `git imap-send 명령으로 Patch 파일을 IMAP 서버의 Draft 폴더에 이메일로 보낼 수 있다.

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

이후 Gmail의 Draft 폴더로 가서 To 부분을 메일링리스트의 주소로 변경하고 CC 부분에 해당 메일을 참고해야 하는 관리자나 개발자의 메일 주소를 적고 실제로 전송한다.

SMTP 서버를 이용해서 Patch를 보낼 수도 있다. 먼저 SMTP 서버를 설정해야 한다. git config 명령으로 하나씩 설정할 수도 있지만 아래와 같이 ~/.gitconfig 파일의 sendemail 섹션을 손으로 직접 고쳐도 된다.

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

이렇게 설정하면 git send-email 명령으로 패치를 보낼 수 있다.

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

명령을 실행하면 아래와 같이 서버로 Patch를 보내는 내용이 화면에 나타난다.

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says.
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

요약

이번 절에서는 다양한 워크플로에 따라 Git을 어떻게 사용하는지 살펴보고 그에 필요한 도구들을 설명했다. 다음 절에서는 동전의 뒷면인 프로젝트를 운영하는 방법에 대하여 살펴본다. 즉 친절한 Dictator나 Integration-Manager가 되어 보는 것이다.


posted by REDFORCE 2017. 4. 13. 00:42

출처 : git-scm.com


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

앞 장에서 다른 개발자와 코드를 공유하는 리모트 저장소를 만드는 법을 배웠다. 로컬에서 작업하는 데 필요한 기본적인 명령어에는 어느 정도 익숙해졌다. 이제는 분산 환경에서 Git이 제공하는 기능을 어떻게 효율적으로 사용할지를 배운다.

이번 장에서는 분산 환경에서 Git을 어떻게 사용할 수 있을지 살펴본다. 프로젝트 기여자 입장과 여러 수정사항을 취합하는 관리자 입장에서 두루 살펴본다. 즉, 프로젝트 기여자 또는 관리자로서 작업물을 프로젝트에 어떻게 포함시킬지와 수 많은 개발자가 수행한 일을 취합하고 프로젝트를 운영하는 방법을 배운다.

분산 환경에서의 워크플로

중앙집중형 버전 관리 시스템과는 달리 Git은 분산형이다. Git은 구조가 매우 유연하기 때문에 여러 개발자가 함께 작업하는 방식을 더 다양하게 구성할 수 있다. 중앙집중형 버전 관리 시스템에서 각 개발자는 중앙 저장소를 중심으로 하는 한 노드일 뿐이다. 하지만, Git에서는 각 개발자의 저장소가 하나의 노드이기도 하고 중앙 저장소 같은 역할도 할 수 있다. 즉, 모든 개발자는 다른 개발자의 저장소에 일한 내용을 전송하거나, 다른 개발자들이 참여할 수 있도록 자신이 운영하는 저장소 위치를 공개할 수도 있다. 이런 특징은 프로젝트나 팀이 코드를 운영할 때 다양한 워크플로를 만들 수 있도록 해준다. 이런 유연성을 살려 저장소를 운영하는 몇 가지 방식을 소개한다. 각 방식의 장단점을 살펴보고 그 방식 중 하나를 고르거나 여러 가지를 적절히 섞어 쓰면 된다.

중앙집중식 워크플로

중앙집중식 시스템에서는 보통 중앙집중식 협업 모델이라는 한 가지 방식밖에 없다. 중앙 저장소는 딱 하나 있고 변경 사항은 모두 이 중앙 저장소에 집중된다. 개발자는 이 중앙 저장소를 중심으로 작업한다

중앙집중식 워크플로.
Figure 54. 중앙집중식 워크플로.

중앙집중식에서 개발자 두 명이 중앙저장소를 Clone 하고 각자 수정하는 상황을 생각해보자. 한 개발자가 자신이 한 일을 커밋하고 나서 아무 문제 없이 서버에 Push 한다. 그러면 다른 개발자는 자신의 일을 커밋하고 Push 하기 전에 첫 번째 개발자가 한 일을 먼저 Merge 해야 한다. Merge를 해야 첫 번째 개발자가 작업한 내용을 덮어쓰지 않는다. 이런 개념은 Subversion과 같은 중앙집중식 버전 관리 시스템에서 사용하는 방식이고 Git에서도 당연히 이런 워크플로를 사용할 수 있다.

팀이 작거나 이미 중앙집중식에 적응한 상황이라면 이 워크플로에 따라 Git을 도입하여 사용할 수 있다. 중앙 저장소를 하나 만들고 개발자 모두에게 Push 권한을 부여한다. 모두에게 Push 권한을 부여해도 Git은 한 개발자가 다른 개발자의 작업 내용을 덮어쓰도록 허용하지 않는다. John과 Jessica가 동시에 같은 부분을 수정하는 상황을 생각해보자. John이 먼저 작업을 끝내고 수정한 내용을 서버로 Push 한다. Jessica도 마찬가지로 작업을 끝내고 수정한 내용을 서버로 Push 하려 하지만 서버가 바로 받아주지 않는다. 서버에는 John이 수정한 내용이 추가되었기 때문에 Push 하기 전에 Fetch로 받아서 Merge 한 후 Push 할 수 있다. 이런 개념은 개발자에게 익숙해서 거부감 없이 도입할 수 있다.

작은 팀만 이렇게 일할 수 있는 것이 아니다. Git이 제공하는 브랜치 관리 모델을 사용하면 수백명의 개발자가 한 프로젝트 안에서 다양한 브랜치를 만들어서 함께 작업하는 것도 쉽다.

Integration-Manager 워크플로

Git을 사용하면 리모트 저장소를 여러 개 운영할 수 있다. 다른 개발자는 읽기만 가능하고 자신은 쓰기도 가능한 공개 저장소를 만드는 워크플로도 된다. 이 Worlflow에는 보통 프로젝트를 대표하는 공식 저장소가 있다. 기여자는 우선 공식 저장소를 하나 Clone 하고 수정하고 나서 자신의 저장소에 Push 한다. 그 다음에 프로젝트 Integration-Manager에게 새 저장소에서 Pull 하라고 요청한다. 그러면 그 Integration-Manager는 기여자의 저장소를 리모트 저장소로 등록하고, 로컬에서 기여물을 테스트하고, 프로젝트 메인 브랜치에 Merge 하고, 그 내용을 다시 프로젝트 메인 저장소에 Push 한다. 이런 과정은 아래와 같다(Integration-manager workflow.).

  1. 프로젝트 Integration-Manager는 프로젝트 메인 저장소에 Push를 한다.

  2. 프로젝트 기여자는 메인 저장소를 Clone 하고 수정한다.

  3. 기여자는 자신의 저장소에 Push 하고 Integration-Manager가 접근할 수 있도록 공개해 놓는다.

  4. 기여자는 Integration-Manager에게 변경사항을 적용해 줄 것을 이메일로 요청한다.

  5. Integration-Manager는 기여자의 저장소를 리모트 저장소로 등록하고 수정사항을 Merge 하여 테스트한다.

  6. Integration-Manager는 Merge 한 사항을 메인 저장소에 Push 한다.

Integration-manager workflow.
Figure 55. Integration-manager workflow.

이 방식은 GitHub나 GitLab 같은 Hub 사이트를 통해 주로 사용하는 방식이다. 프로젝트를 Fork 하고 수정사항을 반영하여 다시 모두에게 공개하기 좋은 구조로 돼 있다. 이 방식의 장점은 기여자와 Integration-Manager가 각자의 사정에 맞춰 프로젝트를 유지할 수 있다는 점이다. 기여자는 자신의 저장소와 브랜치에서 수정 작업을 계속해 나갈 수 있고 수정사항이 프로젝트에 반영되도록 기다릴 필요가 없다. 관리자는 여유를 가지고 기여자가 Push 해 놓은 커밋을 적절한 시점에 Merge 한다.

Dictator and Lieutenants 워크플로

이 방식은 저장소를 여러개 운영하는 방식을 변형한 구조이다. 보통 수백 명의 개발자가 참여하는 아주 큰 프로젝트를 운영할 때 이 방식을 사용한다. Linux 커널 프로젝트가 대표적이다. 여러 명의 Integration-Manager가 저장소에서 자신이 맡은 부분만을 담당하는데 이들을 Lieutenants라고 부른다. 모든 Lieutenant는 최종 관리자 아래에 있으며 이 최종 관리자를 Dictator라고 부른다. 최종 관리자가 관리하는 저장소를 공식 저장소로 하며 모든 프로젝트 참여자는 이 공식 저장소를 기준으로 작업한다. 이러한 워크플로는 아래와 같다(Benevolent dictator workflow.).

  1. 개발자는 코드를 수정하고 master 브랜치를 기준으로 자신의 토픽 브랜치를 Rebase 한다. 여기서 master 브랜치란 Dictator의 브랜치를 말한다.

  2. Lieutenant들은 개발자들의 수정사항을 자신이 관리하는 master 브랜치에 Merge 한다.

  3. Dictator는 Lieutenant의 master 브랜치를 자신의 master 브랜치로 Merge 한다.

  4. Dictator는 Merge 한 자신의 master 브랜치를 Push 하며 다른 모든 개발자는 Dictator의 master 브랜치를 기준으로 Rebase 한다.

Benevolent dictator workflow.
Figure 56. Benevolent dictator workflow.

이 방식이 일반적이지 않지만 깊은 계층 구조를 가지는 환경이나 규모가 큰 프로젝트에서는 매우 쓸모 있다. 프로젝트 리더가 모든 코드를 통합하기 전에 코드를 부분부분 통합하도록 여러 명의 Lieutenant에게 위임한다.

워크플로 요약

이 세 가지 워크플로가 Git 같은 분산 버전 관리 시스템에서 주로 사용하는 것들이다. 사실 이런 워크플로뿐만 아니라 다양한 변종 워크플로가 실제로 사용된다. 어떤 방식을 선택하고 혹은 조합해야 하는 지 살짝 감이 잡힐 것이다. 앞으로 몇 가지 구체적 사례를 들고 우리가 다양한 환경에서 각 역할을 어떻게 수행하는지 살펴본다. 이어지는 내용에서 프로젝트에 참여하고 기여할 때 작업 패턴이 어떠한지 몇 가지 살펴보기로 한다.


posted by REDFORCE 2017. 4. 13. 00:16

출처 : git-scm.com


4.8 Git 서버 - GitLab

GitLab

간단하게 쓰기엔 GitWeb이 꽤 좋다. 그런데 좀 더 기능이 많은 Git 서버를 쓰려면 다른 서버를 찾아 설치해야 한다. GitLab은 널리 사용하는 서버 중 하나이다. 여기서 예제를 통해 설치하고 사용하는 것을 배워보자. GitLab은 기능이 많은 만큼 설정도 복잡하고 유지보수를 위해 해야 할 것도 많다.

설치

GitLab은 데이터베이스와 따로 연동해야하는 웹 애플리케이션이라 다른 Git 서버들보다 설치하기에 복잡하지만, 문서화가 잘 되어있으므로 이를 참고한다.

설치 방법은 여러 가지가 있다. 가상 머신 이미지나 원클릭 인스톨러를 내려받아 빨리 설치하고 환경에 맞게 후다닥 설정해서 사용할 수 있다. https://bitnami.com/stack/gitlab에서 내려받을 수 있다. Bitnami의 로그인 화면은 아래와 같다(alt-→ 를 눌러서 들어간다). 로그인 화면에 설치된 GitLab의 IP와 기본 사용자 이름, 비밀번호가 써있다.

Bitnami GitLab 가상 머신의 로그인 화면
Figure 50. Bitnami GitLab 가상 머신의 로그인 화면

더 많은 것이 알고 싶다면 GitLab 커뮤니티 에디션의 readme 파일을 읽어보면 된다. https://gitlab.com/gitlab-org/gitlab-ce/tree/master에서 내려받을 수 있다. Chef의 레시피나 Digital Ocean(역주 - 호스팅 서비스)의 가상 머신, RPM, DEB 패키지 등에 관한 설치 방법들이 있다. “비공식적인” 설명서도 있다. 흔치 않은 운영체제나 데이터베이스와의 연동하는 법, 스크립트로 완전히 수동으로 설치하는 법 등 많은 주제를 다룬다.

관리자

GitLab의 관리자 도구는 웹 페이지로 되어있다. 웹 브라우저로 GitLab이 설치된 곳의 주소에 들어가면 그냥 보인다. 그리고 관리자로 로그인하자. 기본 사용자 이름은 admin@local.host, 비밀번호는 ‘5iveL!fe`이다(이건 로그인 후에 바꿀 수 있다). 로그인하고 나서 메뉴 오른쪽 위에 있는 ``Admin area’'를 클릭한다.

GitLab 메뉴의 ``Admin area'' 버튼
Figure 51. GitLab 메뉴의 “Admin area” 버튼

사용자

GitLab의 사용자 계정은 한 사람당 하나씩 만든다. 사용자 계정의 내용은 복잡하지 않다. 로그인 데이터에 추가로 개인 정보가 들어있다. 각 사용자마다 네임스페이스가 있다. 네임스페이스는 프로젝트를 묶는 단위이다. jane 사용자가 project라는 프로젝트를 진행 중이라면 프로젝트의 URL은 http://server/jane/project가 될 것이다.

GitLab 사용자의 관리 화면
Figure 52. GitLab 사용자의 관리 화면

사용자를 삭제하는 방법은 두 가지다. 일시적으로 GitLab에 로그인하지 못하게 하는 ‘`정지(Blocking)’'가 있다. 정지한 사용자 데이터와 네임스페이스 안의 프로젝트 데이터는 삭제되지 않고 그대로 남는다. 커밋의 이메일 주소에 대한 링크도 여전히 사용자 프로파일 페이지로 연결된다.

하지만 사용자를 ‘`삭제(Destroying)’'하면 그 사용자와 관련된 모든 데이터가 삭제된다. 삭제한 사용자의 모든 프로젝트와 데이터가 삭제되고 해당 사용자가 소유한 그룹도 삭제된다. 영구히 삭제돼 되돌릴 수 없으므로 조심해야 한다.

그룹

GitLab 그룹은 프로젝트와 누가 어떤 프로젝트에 어떻게 접근할지에 대한 권한 데이터의 모음이다. 그룹에도 사용자처럼 프로젝트 네임스페이스가 있다. +training+라는 그룹이 +materials+라는 프로젝트를 가지고 있으면 URL은 http://server/training/materials가 된다.

GitLab의 그룹 관리 화면
Figure 53. GitLab의 그룹 관리 화면

그룹은 많은 사용자가 모인 곳이다. 그룹의 사용자의 권한은 그룹의 프로젝트에 대한 권한과 그룹 자체에 대한 권한이 따로 있다. 권한은 “Guest”(이슈 등록과 채팅만 할 수 있다.)부터 “Owner”(그룹과 멤버, 프로젝트에 대한 모든 제어가 가능하다.)까지 지정할 수 있다. 여기에서 어떤 권한이 있는지 나열하기엔 너무 많다. GitLab의 관리 화면에서 각 권한에 대한 링크를 참고하길 바란다.

프로젝트

GitLab의 프로젝트는 간단히 이야기하면 하나의 Git 저장소다. 모든 프로젝트는 한 사용자나 한 그룹에 속하게 된다. 사용자에 딸린 프로젝트는 사용자가 관리자로서 그 프로젝트를 완전히 제어한다. 그룹에 딸린 프로젝트는 해당 그룹의 사용자 권한 레벨에 따라 다르다.

프로젝트마다 공개 수준을 지정할 수 있어서 사람마다 프로젝트 페이지와 저장소가 보이거나 안 보이게 할 수 있다. 프로젝트가 Private_이면 프로젝트 소유자가 허락한 사람들만 프로젝트에 접근할 수 있다. _Internal_은 로그인한 사용자에게만 보인다. 그리고 _Public 프로젝트는 모든 사람이 볼 수 있다. 이런 공개 수준은 git ‘`fetch’'같은 접근이나 웹 UI 접근에 다 적용된다.

GitLab은 훅도 지원하는데 프로젝트 훅이나 시스템 훅을 사용할 수 있다. 훅은 어떤 이벤트가 발생하면 해당 이벤트 정보가 담긴 JSON 데이터를 HTTP POST로 보낸다. Git 저장소나 GitLab과 연동해서 CI나 채팅, 개발 도구 등으로 자동화하기에 좋다.

기본 사용법

먼저 새로운 프로젝트를 만들어보자. 툴바의 “+” 아이콘을 클릭한다. 프로젝트의 이름, 프로젝트 네임스페이스, 공개 수준을 입력한다. 지금 입력한 것은 대부분 나중에 다시 바꿀 수 있다. ‘`Create Project’'를 클릭하면 끝난다.

프로젝트가 만들어졌으면 로컬 Git 저장소랑 연결하자. HTTPS나 SSH 프로토콜을 이용해 프로젝트를 Git 리모트로 등록한다. 저장소 URL은 프로젝트 홈페이지 위 쪽에 있다. 아래와 같이 명령어를 이용해 로컬 저장소에 `gitlab`이라는 이름으로 리모트 저장소를 등록한다.

$ git remote add gitlab https://server/namespace/project.git

로컬 저장소가 없으면 그냥 아래 명령어를 실행한다.

$ git clone https://server/namespace/project.git

웹 UI는 꽤 유용하다. 저장소에 대한 각종 정보를 보여준다. 프로젝트 홈페이지에서는 최근 활동을 보여주고 제일 위의 링크를 클릭하면 프로젝트의 파일과 커밋 로그가 나온다.

함께 일하기

함께 일할 사람에게 그냥 Git 저장소의 Push 권한을 주는 걸로 간단하게 협업을 시작할 수 있다. 프로젝트 설정 페이지에서 “Members” 섹션에 같이 일할 사용자를 추가한다. 그리고 그 사용자가 Push 할 수 있도록 설정한다(다른 접근 수준에 대해서는 그룹에서 볼 수 있다). “Developer” 이상의 권한을 주면 그 사용자는 우리 저장소에 Push 하거나 브랜치를 만들 수 있다.

Merge 요청을 하도록 해서 통제권을 유지한 채로 협업하는 방법도 있다. 프로젝트에 접근할 수 있는 모든 사용자가 프로젝트에 기여할 수 있다. 사용자는 마음껏 브랜치를 만들고 커밋, Push 하고 나서 이 브랜치를 ‘master`나 다른 브랜치에 Merge 해달라고 요청한다. Push 권한이 없는 사용자는 저장소를 ``fork’한 다음에 ``fork'한 _자신의 저장소_에 Push 한다. 그리고는 원래 저장소에 내 저장소에 있는 브랜치를 Merge 해달라고 요청하면 된다. 소유자는 이걸로 자신의 저장소에 대한 모든 통제 권한을 가진다. 어떤 데이터가 들어올 수 있는지 언제 들어오는지 소유자가 결정할 수 있다.

Merge 요청과 이슈는 대화의 기본 단위이다. 각 Merge 요청에서는 일반적인 토론뿐만 아니라 라인 단위로까지 대화가 이루어진다. 물론 코드 리뷰가 간단히 끝날 수도 있다. 요청과 이슈는 모두 사용자에게 할당되거나 마일스톤의 과제로 편입된다.

이 섹션에서는 GitLab의 Git과 맞닿은 부분만 설명했지만 이게 전부가 아니다. GitLab은 굉장히 성숙했다. 이 외에도 프로젝트 위키나 토론용 “walls”, 시스템 관리 도구 등 협업용 기능이 많다. GitLab의 장점은 일단 서버가 돌아가면 SSH로 서버에 접속할 일이 별로 없다는 것이다. 대부분 관리는 웹 브라우저로 가능하다.


posted by REDFORCE 2017. 4. 13. 00:15

출처 : git-scm.com


4.7 Git 서버 - GitWeb

GitWeb

프로젝트 저장소를 단순히 읽거나 쓰는 것에 대한 설정은 다뤘다. 이제는 웹 기반 인터페이스를 설정해 보자. Git은 웹에서 저장소를 조회할 수 있는 GitWeb이라는 CGI 스크립트를 제공한다.

Git 웹용 UI
Figure 49. Git 웹용 UI, GitWeb

Git은 GitWeb을 쉽게 사용해 볼 수 있도록 서버를 즉시 띄우는 명령을 제공한다. 시스템에 lighttpd`나 `webrick 같은 경량 웹 서버가 설치돼 있어야 이 명령을 사용할 수 있다. Linux에서는 lighttpd`를 많이 사용한다. `lighttpd`가 설치돼 있으면 프로젝트 디렉토리에서 그냥 `git instaweb`을 실행하면 바로 실행될 것이다. Mac의 Leopard 버전은 Ruby가 미리 설치돼 있기 때문에 `webrick`이 더 나은 선택이다. 사용할 웹 서버가 `lighttpd`가 아니라면 아래와 같이 `--httpd 옵션을 사용해야 한다.

$ git instaweb --httpd=webrick
[2009-02-21 10:02:21] INFO  WEBrick 1.3.1
[2009-02-21 10:02:21] INFO  ruby 1.8.6 (2008-03-03) [universal-darwin9.0]

이렇게 하면 1234 포트로 HTTPD 서버를 시작하고 이 페이지를 여는 웹 브라우저를 자동으로 실행시킨다. 사용자에게는 꽤 편리하다. 필요한 일을 모두 마치고 나서 같은 명령어에 --stop 옵션을 추가하여 서버를 중지한다:

$ git instaweb --httpd=webrick --stop

자신의 프로젝트에서 언제나 웹 인터페이스를 운영하려면 먼저 웹 서버에 이 CGI 스크립트를 설치해야 한다. 몇몇 리눅스 배포판에서는 apt`나 `yum`으로 설치할 수 있게 `gitweb 패키지를 제공한다. 그러니 패키지로 설치할 수 있는지 확인해 보는 것이 좋다. 여기에서는 GitWeb을 수동으로 설치하는 방법을 간단히 살펴보자. 먼저 GitWeb이 포함된 Git 소스 코드를 구한 다음 아래의 CGI 스크립트를 빌드한다.

$ git clone git://git.kernel.org/pub/scm/git/git.git
$ cd git/
$ make GITWEB_PROJECTROOT="/opt/git" prefix=/usr gitweb
    SUBDIR gitweb
    SUBDIR ../
make[2]: `GIT-VERSION-FILE' is up to date.
    GEN gitweb.cgi
    GEN static/gitweb.js
$ sudo cp -Rf gitweb /var/www/

빌드할 때 GITWEB_PROJECTROOT 변수로 Git 저장소의 위치를 알려준다. 이제 Apache가 이 스크립트를 사용하도록 VirtualHost 항목에 설정한다.

<VirtualHost *:80>
    ServerName gitserver
    DocumentRoot /var/www/gitweb
    <Directory /var/www/gitweb>
        Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch
        AllowOverride All
        order allow,deny
        Allow from all
        AddHandler cgi-script cgi
        DirectoryIndex gitweb.cgi
    </Directory>
</VirtualHost>

다시 말해서 GitWeb은 CGI나 Perl을 지원하는 웹 서버라면 아무거나 사용할 수 있다. 이제 `http://gitserver/`에 접속하여 온라인으로 저장소를 확인할 수 있다.


posted by REDFORCE 2017. 4. 13. 00:04

출처 : git-scm.com


4.6 Git 서버 - 스마트 HTTP

스마트 HTTP

지금까지 인증 기능을 갖춘 SSH와 인증 기능이 없는 git 프로토콜을 배웠다. 이제는 이 두 기능을 한꺼번에 가진 프로토콜을 알아보자. 서버에서 git-http-backend 명령어를 이용해 일단 기본적인 스마트 HTTP를 지원하는 Git 서버를 실행한다.git commands, "http-backend" Git 클라이언트에서 `git fetch`나 `git push`를 실행하면 서버로 HTTP 요청을 보낸다. 서버는 그 요청을 보고 경로와 헤더를 읽어 클라이언트가 HTTP로 통신하려 하는지 감지한다. 이는 1.6.6 버전 이상의 클라이언트에서 동작한다. 서버는 클라이언트가 스마트 HTTP 프로토콜을 지원한다고 판단되면 스마트 HTTP 프로토콜을 사용하고 아니면 멍청한 프로토콜을 계속 사용한다. 덕분에 하위 호환성이 잘 유지된다.

이제 설정해보자. CGI 서버로 Apache를 사용한다. Apache가 없다면 Linux에서는 아래와 같이 Apache를 설치할 수 있다.

$ sudo apt-get install apache2 apache2-utils
$ a2enmod cgi alias env rewrite

이 명령어 한 방이면 mod_cgimod_aliasmod_env, `mod_rewrite`도 사용할 수 있다. 다 앞으로 사용할 모듈들이다.

/opt/git 디렉토리의 Unix 사용자 그룹도 `www-data`로 설정해야 한다. 그래야 웹 서버가 저장소를 읽고 쓸 수 있다. Apache 인스턴스는 CGI 스크립트를 이 사용자로 실행시킨다(기본 설정이다).

$ chgrp -R www-data /opt/git

그리고 Apache 설정 파일을 수정한다. 그러면 git http-backend`를 실행했을 때 모든 요청을 `/git 경로로 받을 수 있다.

SetEnv GIT_PROJECT_ROOT /opt/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/

GIT_HTTP_EXPORT_ALL 환경 변수를 설정하지 않으면 git-daemon-export-ok 파일이 있는 저장소에는 아무나 다 접근할 수 있게 된다. 그냥 Git 데몬의 동작과 똑같다.

마지막으로 Apache가 `git-http-backend`에 요청하는 것을 허용하고 쓰기 접근 시 인증하게 한다.

RewriteEngine On
RewriteCond %{QUERY_STRING} service=git-receive-pack [OR]
RewriteCond %{REQUEST_URI} /git-receive-pack$
RewriteRule ^/git/ - [E=AUTHREQUIRED]

<Files "git-http-backend">
    AuthType Basic
    AuthName "Git Access"
    AuthUserFile /opt/git/.htpasswd
    Require valid-user
    Order deny,allow
    Deny from env=AUTHREQUIRED
    Satisfy any
</Files>

.htpasswd 파일에는 접근을 허가하려는 사용자의 비밀번호가 들어가 있어야 한다. 아래는 ‘`schacon’'이란 사용자를 추가하는 방법이다.

$ htpasswd -c /opt/git/.htpasswd schacon

Apache에는 사용자 인증 방법이 많다. 그중 하나를 골라 사용해야 하는데 위에 설명한 방법이 가장 간단한 방법의 하나다. 그리고 이렇게 사용자 인증 설정을 할 때는 보안을 위해 SSL로 접속해 작업하는 것이 좋다.

웹 서버는 Apache 말고도 다른 서버를 사용할 수도 있고, 인증 방식도 다르므로 Apache 설정에 대해서 길게 이야기하지 않는다. 대신 이것만 알아두었으면 한다. HTTP를 이용한 모든 통신에서는 `git http-backend`와 Git을 함께 사용한다는 것이다. Git 그 자체로는 인증 기능을 가지고 있지 않다. 하지만 웹 서버의 인증 레이어와 손쉽게 연동할 수 있다. CGI를 실행할 수 있는 웹 서버라면 어떤 서버든지 붙일 수 있다. 가장 좋아하는 서버를 사용하길 바란다.


'Programming > Git' 카테고리의 다른 글

Git_#4.8_Git 서버 - GitLab  (0) 2017.04.13
Git_#4.7_Git 서버 - GitWeb  (0) 2017.04.13
Git_#4.5_Git 서버 - Git 데몬  (0) 2017.04.13
Git_#4.4_Git 서버 - 서버 설정하기  (0) 2017.04.13
Git_#4.3_Git 서버 - SSH 공개키 만들기  (0) 2017.04.13