GitHub

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

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

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

인터페이스는 변하는 거야.

웹사이트의 UI는 시간에 따라 변한다. 그래서 GitHub 스크린샷들은 시간이 지나면 틀리게 된다. 사실 우리는 변하지 않고 멈춰줬으면 좋겠다. 최신 버전의 스크린샷이 포함된 버전을 읽고 싶다면 이 책의 온라인 버전을 읽어라. 아마 거기엔 좀 더 최신 스크린샷이 적용돼 있을 것이다.

계정 만들고 설정하기

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

GitHub 가입 폼.
GitHub 가입 폼.

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

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

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

SSH 사용하기

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

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

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

그 왼쪽 메뉴에서 “SSH keys”를 선택한다.

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

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

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

아바타

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

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

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

아바타 자르기.
아바타 자르기.

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

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

사용자 이메일 주소

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

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

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

투 팩터 인증

더 안전한 보안을 위해서 “2FA”(2 팩터 인증)을 설정한다. 2FA는 최근 들어 인기가 높아지는 인증 메커니즘이다. 암호를 도둑맞았을 때 위험을 완화하기 위해 사용한다. 2FA를 켜면 GitHub은 두 가지 방법으로 인증받도록 요구한다. 둘 중 한 가지 방법만 뚫려서는 공격자가 계정에 접근할 수 없다.

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

Security 탭에 있는 2FA
Security 탭에 있는 2FA

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

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

프로젝트에 기여하기

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

프로젝트 Fork 하기

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

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

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

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

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

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

GitHub 플로우

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

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

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

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

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

  4. GitHub에 Pull Request를 연다.

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

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

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

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

Pull Request 만들기

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

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

다 좋은데 너무 빠르게 깜빡이는 게 마음에 안 들었다. 매초 깜빡이는 것보다 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 버튼
Pull Request 버튼

녹색 버튼을 클릭하면 Pull Request의 제목과 설명을 입력하는 화면이 보인다. 프로젝트 소유자 마음에 들도록 이해하기 쉽게 쓴다. 왜 수정했는지 얼마나 가치 있는지를 관리자가 가능하면 쉽게 이해할 수 있도록 심혈을 기울이는 것이 좋다.

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

Pull Request 생성
Pull Request를 생성하는 페이지

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

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

Pull Request 놓고 감 놓고 배 놓기

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

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

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

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

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

누구나 Pull Request에 코멘트을 달 수 있다. Figure 6-14은 보면 프로젝트 소유자가 코드 코멘트과 일반 코멘트을 달면서 토론하는 것을 보여 준다. 코드 코멘트로 대화의 맥락을 이끌어 간다는 것을 볼 수 있다.

PR 토론 페이지
Pull Request 토론 페이지

이 토론을 보고 기여자는 자신이 무엇을 해야 자신의 코드가 받아들여질지 알 수 있다. 만약 이 일을 이메일로 하고자 한다면 관련 커밋을 다시 말아서 메일링 리스트에 다시 보내야 한다. 하지만, GitHub에서는 해당 토픽 브랜치에 이어서 커밋하고 Push 하면 된다.

기여자가 Push 하면 프로젝트 소유자에게 다시 알림이 간다. 다시 토론 페이지에 가면 해당 내용을 알 수 있다. 코멘트이 달린 코드를 수정했기 때문에 Github은 이점을 보여주고 때 지난 diff는 숨긴다.

최종 PR
최종 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 프로젝트가 사용하는 기본 Workflow이다. 토픽 브랜치를 만들고 Pull Request를 연다. 거시서 토론을 계속 하고 그 브랜치에 커밋도 좀 하고 한다. 최종적으로 일하면 Merge 하고 닫는다.

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를 처음부터 다시 전체를 작성하지 않아도 된다. 수정한 만큼만 해당 브랜치에 커밋하고 하던 일과 대화를 계속 해 나가면 된다.

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

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

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

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

Figure 6-16 같은 메시지를 보면 해당 브랜치를 고쳐서 녹색으로 만든다. 메인테이너가 고치지 않아도 되도록 한다.

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

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

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

“tonychacon” 예제에 이 Workflow를 적용해보자. 원저자가 뭔가 수정을 했는데 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 고침
Pull Request가 깨끗하게 Merge 할 수 있게 됐다.

연속성은 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가 뭔지 알 수 있도록 새것에서 예전 것을 참조하게 해보고 Figure 6-18같이 Fork 한 저장소의 이슈나 아예 다른 저장소의 이슈도 참조하게 해보자.

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

이 Pull Request를 보내면 Figure 6-19처럼 보인다.

PR 참조
Pull Request의 상호 참조.

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

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

닫은 PR의 트랙백
닫은 Pull Request의 트랙백

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

Markdown

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

Figure 6-21는 Markdown으로 쓴 글이 어떻게 렌더링되는지 보여준다.

Markdown 예제
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 6-22처럼 렌더링된다.

타스크 리스트
타스크 리스트.

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

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

타스크 리스트의 예
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 6-24는 언어 이름을 넣어서 구문 강조된 결과다.

미끈한 코드
구문강조로 미끈해진 코드.

인용

아주 긴 글에서 딱 한 부분만 집어서 논의하고 싶을 때 > 문자로 해당 부분을 인용하고 그 밑에 코멘트을 단다. 이 방법은 매우 유용해서 흔히 사용하는 방법이고 단축키도 있다. 인용하고 싶은 텍스트를 선택하고 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 6-25처럼 렌더링된다.

인용 예제
인용 예제.

Emoji

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

Emoji 자동완성
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:

렌더링되면 Figure 6-27처럼 보인다.

Emoji
Emoji를 많이 쓴 글.

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

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

http://www.emoji-cheat-sheet.com

이미지

GFM은 이미지까지 확장하지 않았지만 GitHub에서 이미지 첨부는 매우 쉽다. Markdown 형식으로 이미지를 첨부하고 싶을 때 이미지 올리고 그 URL을 찾아서 일일이 입력하기 번거롭다. GitHub에서는 그냥 이미지를 바로 Drag-and-Drop으로 붙여 넣을 수 있다.

Drag and drop images
끌어다 놓으면 이미지가 자동으로 붙는다.

Figure 6-18로 돌아가서 보면 Text Area 위에 “Parsed As Markdown”이라는 표시를 볼 수 있다. 그 링크를 클릭하면 GitHub에서 Markdown을 어떻게 사용하는지 알려주는 치트시트를 보여준다.

프로젝트 관리하기

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

새 저장소 만들기

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

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

위 버튼을 누르면 “새 저장소”를 만드는 화면으로 이동한다.

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

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

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

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

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

동료 추가하기

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

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

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

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

저장소의 동료.
저장소의 동료.

Pull Request 관리하기

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

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

“tonychacon”이라는 사람이 “fade”라는 Arduino 프로젝트를 만든 상황을 살펴보자.

이메일 알림

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

Pull Request 이메일 알림
새 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를 만든 사람과 토론할 수 있다. 코멘트를 Pull Request에 대고 남길 수도 있고 특정 커밋을 고르거나 특정 라인을 집어서 남길 수도 있다.

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

답 메일
답변 메일이 Pull Request의 스레드가 된다.

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

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

Merge 버튼
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

“리모트의 브랜치 originrefs/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 처럼 생긴 것을 알아보자. 이 브랜치는 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의 대상 브랜치
Pull Request을 어디로 보낼지 고른다.

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

멘션과 알림

어떤 팀이나 사람에게 질문하거나 피드백을 받고 싶을 때 GitHub의 알림 시스템이 쉽고 편하다.

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

멘션
@만 입력.

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

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

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

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

알림 페이지

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

Notifiation center
Notification center 옵션.

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

웹 알림

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

Notifiation center
Notification center.

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

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

이메일 알림

이메일 알림을 켜 놓으면 이메일로도 GitHub 알림을 확인할 수 있다. Figure 6-13Figure 6-34의 예를 보면 관련 알림들이 이메일 스레드로 잘 분류되는 것을 볼 수 있다. 그래서 이메일 스레드를 잘 지원하는 메일 클라이언트를 사용하는 것이 좋다.

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

Figure 6-34에서 보여준 이메일의 헤더는 아래와 같다.

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

이메일과 웹 알림이 둘 다 켜져 있으면 알림이 이메일로도 오고 웹으로도 온다. 이메일 클라이언트에 이미지를 읽도록 설정돼 있으면 이메일 클라이언트에서 메일을 읽으면 웹에서도 읽었다고 표시된다.

특별한 파일

저장소에 있는 파일 중에서 GitHub이 사용하는 파일이 있다.

README

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

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

  • 무슨 프로젝트인지

  • 설정하고 설치하는 방법

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

  • 프로젝트의 라이센스

  • 기여하는 방법

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

CONTRIBUTING

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

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

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

프로젝트 관리

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

기본 브랜치 변경하기

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

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

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

프로젝트 넘기기

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

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

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

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

Organization 관리하기

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

Organization 기초

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

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

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

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

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

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

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

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

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

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

orgs 01 page
Organization 페이지

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

orgs 02 teams
팀 페이지.

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

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

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

감사 로그

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

orgs 03 audit
감사 로그.

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

GitHub 스크립팅

지금까지 GitHub의 주요기능과 Workflow를 모두 살펴봤다. 프로젝트가 크거나 그룹이 크면 매우 꼼꼼하게 설정하거나 다른 서비스를 통합시켜야 할 필요도 있다.

다행히 GitHub에는 해커들에게 제공하는 방법이 있다. 이 절에서는 GitHub 훅과 API을 사용하는 법을 설명한다.

GitHub 저장소 관리의 훅과 서비스 절에 보면 다른 시스템과 연동하는 가장 쉬운 방법이 나온다.

서비스

GitHub 서비스부터 살펴보자. 훅과 서비스는 저장소의 설정 페이지에서 연동할 수 있다. 이전에 동료를 추가하거나 기본 브랜치를 설정하던 그곳이다. “Webhooks와 Services” 탭은 Figure 6-49처럼 생겼다.

서비스와 훅
서비스와 훅 설정 화면.

CI, 버그 트래커, 이슈 트래커, 채팅, 문서 시스템 등등과 연동하는 데 사용하는 서비스가 수십 개 준비돼 있다. 여기서는 가장 단순한 Email 훅을 살펴본다. “Add Service” 메뉴에서 “email”을 선택하면 Figure 6-50 같은 설정 화면으로 이동한다.

Email 서비스
Email 서비스 설정.

이메일을 입력하고 “Add service” 버튼을 누르면 누군가 저장소에 Push 할 때마다 이메일이 날아간다. 서비스는 다양한 이벤트를 처리할 수 있지만, 보통은 Push 할 때 그 데이터를 가지고 뭔가를 한다.

연동하려는 시스템을 지원하는 서비스가 이미 있는지 GitHub에서 먼저 찾아봐야 한다. 예를 들어, Jenkins를 사용해서 코드 테스트할 계획이라면 Jenkins 서비스를 이용해서 연동한다. 누군가 저장소에 Push 할 때마다 테스트를 수행되도록 할 수 있다.

GitHub 서비스에 없는 사이트나 외부 서비스와 연동하고 싶거나 좀 더 세세한 설정을 하고 싶으면 GitHub 훅을 이용한다. GitHub 저장소의 훅은 단순하다. URL을 하나 주면 그 URL로 HTTP 페이로드를 보내준다.

GitHub 훅 페이로드를 처리하는 간단한 웹 서비스를 하나 만들고 그 서비스에 원하는 동작을 구현하는 것이 일반적이다.

Figure 6-49의 “Add webhook” 버튼을 클릭하면 아래와 같은 페이지로 이동한다.

웹훅
웹훅 설정.

웹훅 설정은 매우 간단하다. URL와 보안 키를 입력하고 “Add webhook” 버튼을 클릭한다. 어떤 이벤트의 페이로드가 필요한 것인지 선택할 수 있지만 push 이벤트의 페이로드만 보내는 것이 기본이다. 그래서 누군가 아무 브랜치에나 코드를 Push 하면 HTTP 페이로드가 전송된다.

웹훅을 처리하는 간단한 웹서비스 예제를 하나 살펴보자. 이 웹서비스는 Ruby 웹 프레임워크인 Sinatra를 사용했다. 간략하기 때문에 무엇을 하는 웹 서비스인지 쉽게 이해할 수 있을 것이다.

이메일을 보내는 서비스를 만들어 보자. 이 서비스는 누가 어느 브랜치에 어떤 파일을 Push 했는 지를 알려준다. 이런 서비스는 매우 간단하게 만들 수 있다.

require 'sinatra'
require 'json'
require 'mail'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON

  # gather the data we're looking for
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # get a list of all the files touched
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # check for our criteria
  if pusher == 'schacon' &&
     branch == 'ref/heads/special-branch' &&
     files.include?('special-file.txt')

    Mail.deliver do
      from     'tchacon@example.com'
      to       'tchacon@example.com'
      subject  'Scott Changed the File'
      body     "ALARM"
    end
  end
end

GitHub은 누가 Push 했는지, 어느 브랜치에 Push 했는지, Push 한 커밋에서 어떤 파일을 수정했는지에 대한 정보를 JSON 페이로드에 담아서 보낸다. 여기서는 특정 조건을 검사해서 만족할 때만 이메일을 보낸다.

Github은 개발하고 테스트할 때 사용하는 개발자 콘솔도 제공한다. 이 콘솔은 혹을 설정한 페이지에 있다. 콘솔에서 해당 웹훅의 최근 히스토리 몇 개를 확인할 수 있다. 어떤 데이터가 전송됐는지 확인할 수 있다. 만약 전송에 성공했으면 요청과 응답의 바디와 헤더를 모두 확인할 수 있다. 이것으로 훅을 쉽게 테스트하고 디버깅할 수 있다.

웹훅 디버그
웹훅 디버깅 정보.

서비스를 테스트할 수 있도록 히스토리에 있는 페이로드를 재전송할 수 있다.

어떤 이벤트가 있고 각각 어떻게 웹훅을 만드는지가 자세히 알고 싶다면 GitHub 개발 문서를 봐라: https://developer.github.com/webhooks/

GitHub API

서비스와 훅은 저장소에서 발생한 이벤트의 알림을 받는 방법이다. 그런데 이벤트의 정보를 좀 더 자세히 알고 싶으면, 자동으로 동료를 추가하거나 이슈에 레이블을 달도록 하고 싶으면, 뭐 좋은 방법이 없을까?

이런 일을 위해서 GitHub API가 준비돼 있다. GitHub이 제공하는 API Endpoint는 매우 많아서 웹 사이트에서 하는 웬만한 일은 자동화할 수 있다. 이 절에서는 인증하고 API에 연결하고, 이슈에 코멘트하고, Pull Request의 상태를 변경하는 법을 배운다.

기본 사용법

인증이 필요하지 않은 API Endpoint에 GET 요청을 보내기가 가장 쉽다. 사용자 정보나 오픈 소스 프로젝트의 정보를 읽어오는 것들이 이에 해당한다. 아래처럼 요청을 보내면 “schacon”이라는 사용자에 대해 자세히 알 수 있다.

$ curl https://api.github.com/users/schacon
{
  "login": "schacon",
  "id": 70,
  "avatar_url": "https://avatars.githubusercontent.com/u/70",
# 
  "name": "Scott Chacon",
  "company": "GitHub",
  "following": 19,
  "created_at": "2008-01-27T17:19:28Z",
  "updated_at": "2014-06-10T02:37:23Z"
}

이렇게 Organization, 프로젝트, 이슈, 커밋 정보를 가져오는 Endpoint가 많이 있다. GitHub에서 공개된 것은 전부라고 생각해도 된다. Markdown을 렌더링하거나 .gitignore 템플릿을 제공하는 API도 있다.

$ curl https://api.github.com/gitignore/templates/Java
{
  "name": "Java",
  "source": "*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

이슈에 코멘트하기

이슈나 Pull Request에 코멘트를 달거나 공개하지 않은 정보를 얻으려고 할 때는 인증이 필요하다.

몇 가지 방법으로 인증할 수 있다. 사용자 이름과 암호가 필요한 Basic 인증도 가능하지만, 개인 엑세스 토큰을 사용하는 게 낫다. 설정 페이지의 “Applications” 탭에서 생성할 수 있다.

엑세스 토큰
설정 페이지의 “Applications” 탭에서 엑세스 토큰을 생성한다.

토큰을 어디에 쓸지 범위를 선택하고 설명을 입력한다. 나중에 다 쓰고 삭제하기 쉽도록 이해하기 쉬운 설명을 다는 게 좋다.

토큰이 생성되면 복사해서 사용한다. 이제 스크립트에서 사용자 이름과 암호를 사용하지 않고 이 토큰을 사용할 수 있다. 토큰은 허용하는 범위가 제한돼 있고 언제든지 폐기할 수 있어서 좋다.

인증을 하지 않으면 API 사용 횟수 제한이 매우 낮다. 인증을 하지 않으면 한 시간에 60번만 허용되지만, 인증을 하면 한 시간에 5,000번까지 허용된다.

이제 이슈에 코멘트를 달아보자. #6 이슈에 코멘트를 달 거다. repos/<user>/<repo>/issues/<num>/comments 형식의 URL로 POST 요청을 보내는데 Authorization 헤더에 생성한 토큰을 넣어서 함께 보낸다.

$ curl -H "Content-Type: application/json" \
       -H "Authorization: token TOKEN" \
       --data '{"body":"A new comment, :+1:"}' \
       https://api.github.com/repos/schacon/blink/issues/6/comments
{
  "id": 58322100,
  "html_url": "https://github.com/schacon/blink/issues/6#issuecomment-58322100",
  ...
  "user": {
    "login": "tonychacon",
    "id": 7874698,
    "avatar_url": "https://avatars.githubusercontent.com/u/7874698?v=2",
    "type": "User",
  },
  "created_at": "2014-10-08T07:48:19Z",
  "updated_at": "2014-10-08T07:48:19Z",
  "body": "A new comment, :+1:"
}

해당 이슈 페이지에 가면 코멘트를 확인할 수 있다. Figure 6-54처럼 잘 써진다.

API 코멘트
GitHub API로 쓴 코멘트.

웹사이트에서 할 수 있는 일은 전부 API로도 할 수 있다. 마일스톤을 만들고 설정하기, 사람들에게 이슈나 Pull Request를 할당하기, 레이블을 만들고 수정하기, 커밋 데이터 사용하기, 커밋을 하거나 브랜치 만들기, Pull Request를 만들고 닫고 Merge 하기, 팀을 만들고 수정하기, Pull Request 코드에 코멘트하기, 사이트에서 검색하기 등등 다 된다.

Pull Request의 상태 변경하기

우리가 살펴볼 마지막 예제는 Pull Request에 관한 것인데 굉장히 유용하다. 커밋은 하나 이상의 상태를 가질 수 있는데 API를 통해서 상태를 추가하거나 조회할 수 있다.

대부분의 CI나 테스팅 서비스들은 코드가 푸시되면 바로 테스트를 하고 나서 이 API를 사용한다. 커밋이 모든 테스트를 통과하면 리포트한다. 이 API로 커밋 메시지가 규칙에 맞게 작성됐지 리포트할 수 있다. 코드를 보낸 사람이 제대로 가이드라인을 지켰는지나 커밋에 제대로 서명했는지도 기록할 수 있다.

커밋 메시지에 Signed-off-by라는 스트링이 있는지 검사하는 웹 서비스를 만들어 보자. 먼저 저장소에 이 웹서비스를 호출하는 웹훅을 등록한다.

require 'httparty'
require 'sinatra'
require 'json'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON
  repo_name = push['repository']['full_name']

  # look through each commit message
  push["commits"].each do |commit|

    # look for a Signed-off-by string
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # post status to GitHub
    sha = commit["id"]
    status_url = "https://api.github.com/repos/#{repo_name}/statuses/#{sha}"

    status = {
      "state"       => state,
      "description" => description,
      "target_url"  => "http://example.com/how-to-signoff",
      "context"     => "validate/signoff"
    }
    HTTParty.post(status_url,
      :body => status.to_json,
      :headers => {
        'Content-Type'  => 'application/json',
        'User-Agent'    => 'tonychacon/signoff',
        'Authorization' => "token #{ENV['TOKEN']}" }
    )
  end
end

이 웹훅 서비스는 별로 어렵지 않다. 누군가 Push 하면 모든 커밋을 훑는데, 커밋 메시지에서 Signed-off-by 스트링을 찾는다. 그 결과의 상태를 /repos/<user>/<repo>/statuses/<commit_sha>라는 Endpoint 주소에 POST 요청으로 보낸다.

커밋의 상태는 success, failure, error일 수 있다. 커밋의 상태(state)와 설명(description), 자세한 정보를 확인할 수 있는 URL(target_url), 상태를 구분하는 “컨텍스트(context)”를 함께 전송한다. 한 커밋에 대해서 테스팅 서비스도 어떤 상태를 보내올 것이고 검증 서비스도 어떤 상태를 보내올 것이라서 “컨텍스트”가 필요하다.

이 훅을 적용하고 나서 누군가 Pull Request를 새로 열면 Figure 6-55같은 상태 메시지를 보게 된다.

커밋 상태
API로 표기한 커밋 상태.

“Signed-off-by” 스트링이 있는 커밋 메시지에는 녹색 체크 아이콘이 달리고 그렇지 않은 커밋에는 빨간 X 표시가 달린다. 그리고 Pull Request의 상태는 마지막 커밋의 상태를 보여주는데 상태가 failure면 경고해준다. 이 API를 사용해서 테스트 결과를 Pull Request에 리포트하는 것은 매우 유용하다. 테스트에 실패하는 커밋을 Merge 하는 일을 미연에 방지할 수 있다.

Octokit

이 책에서는 단순한 HTTP 요청을 보냈기 때문에 curl만 사용했다. 하지만, 더 편리하게 API를 사용할 수 있게 해주는 오픈소스 라이브러리가 있다. 이 책을 쓰는 시점에서는 Go와 Objective-C, Ruby, .NET을 지원한다. 자세한 정보는 http://github.com/octokit에 가서 확인하면 되고 이미 많은 기능을 지원한다.

이 도구로 프로젝트가 요구하는 데로 GitHub의 Workflow를 최적화할 수 있다. 전체 API에 대한 구체적인 문서와 상황별 가이드는 https://developer.github.com에서 확인해야 한다.

요약

이제 GitHub 사용자가 됐다. 계정을 생성하는 방법, Organization을 만드는 방법, 저장소를 만들고 Push 하는 방법, 다른 사람의 프로젝트에 참여하는 방법, 다른 사람의 참여를 받아들이는 방법을 배웠다. 다음 장에서는 Git의 전지전능한 도구로 복잡한 상황을 헤쳐나가는 방법을 살펴본다. 진정한 Git 고수가 될 수 있을 것이다.