Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

GitHub의 Pull Request를 로컬로 가져오기

지난달에 Pull Request를 이용한 개발 흐름을 적용해 보고 나서...를 통해서 Pull Request를 이용한 개발 흐름에 대해서 공유했다.(기대보다 반응이 훨씬 좋은 글이었다.) 오픈 소스 프로젝트를 운영할 때도 마찬가지긴 하지만 Pull Request를 개발 흐름에 넣으면 Pull Request를 볼 일이 아주 많아진다. GitHub에서 변경사항을 보고 리뷰를 하기도 하지만 상황에 따라서는 제대로 동작하는지 로컬에 내려받아서 테스트해야 하는 경우도 생기고 클라이언트나 디자인 검토를 위해서 Pull Request를 개발 서버 등에 적용해서 보아야 하는 경우도 생긴다. 이 단계에서는 아직 작업이 끝난 것이 아니므로 master 같은 메인 브랜치에 머지하기 전에 이루어져야 하는 과정이다.

git checkout -b BRANCH-NAME-FOR-PR master
git pull git@github.com:USERNAME/REPOSITORY.git BRANCH-NAME

GitHub의 Pull Request를 보면 해당 풀 리퀘스트를 로컬에 내려받아서 확인할 수 있게 안내가 나온다. Pull Request를 올린 사람의 저장소에서 로컬로 가져와서 확인할 수 있는데 매번 저장소나 브랜치명도 달라지고 해서 자주 사용하게 되면 꽤 불편해진다. Pull Request를 쉽게 가져오는 방법이 없나 찾아보다가 몇 가지 방법을 찾아냈다.

원격 저장소에서 Pull Request 가져오기

앞에서 얘기한 GitHub 에서 Pull Request를 가져오는 방법은 Pull Request를 올린 사람의 저장소에서 가져오는 방법인데 GitHub를 보면 원래의 원격저장소에도 Pull Request에 대한 브랜치가 있을 것 같다는 예상을 할 수 있다. fork해 간 저장소에서 Pull Request를 지워도 Pull Request가 사라지는 것은 아니므로 단순 링크가 아니라는 예상이다.

여기저기 찾아보다가 Pull Request에 대한 브랜치가 존재한다는 것을 알게 되었다. 이는 원격 저장소의 refs/pull/NUM/head로 존재하고 있으므로(여기서 NUM은 Pull Request의 번호다.) Pull Request의 번호를 알고 있다면 바로 가져올 수 있다. 예를 들어 Pull Reqeust 번호가 110이라면 다음과 같이 로컬에 가져올 수 있다.(여기서 origin은 메인 원격저장소로 등록되어 있다.)

$ git pull origin pull/110/head:pr-110
From github.com:username/project-name
 * [new ref]         refs/pull/110/head -> pr-110

이는 origin 원격 저장소에서 pull/110/headpull받아서 로컬의 pr-110이란 브랜치로 만든 것이다. pr-110이란 브랜치명은 임의로 만든 것이다.

자동으로 Pull Request를 로컬에 가져오기

원격저장소에 레퍼런스가 존재한다는 것을 알았으므로 가져오는 방법을 찾다 보니 잘 정리된 방법을 찾을 수 있었다. .git/config파일을 보면 origin 원격 저장소에 다음과 같이 등록된 것을 볼 수 있다.

[remote "origin"]
  url = git@github.com:username/project-name.git
  fetch = +refs/heads/*:refs/remotes/origin/*

보통은 clone을 받으면 해당 저장소 주소에 대해서 위와 같은 정보가 등록된다. 여기에 다음과 같이 fetch = +refs/pull/*/head:refs/remotes/origin/pr/* 패치 정보를 하나 더 추가한다.

[remote "origin"]
  url = git@github.com:username/project-name.git
  fetch = +refs/heads/*:refs/remotes/origin/*
  fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

이는 Git에서 fetch할 때 가져오는 레퍼런스에 대한 정보인데 간단히 설명하자면 refs/pull/*/head를 가져와서 로컬에 refs/remotes/origin/pr/*로 만드는 것이다. 여기서 *는 당연히 와일드카드인데 앞에서 본 형태를 생각하면 Pull Request의 번호를 의미한다. 위 정보를 등록하고 다시 fetch로 원격 저장소의 정보를 가져오면 원격저장소의 Pull Request를 모두 가져오는 것을 볼 수 있다.(원격 저장소에 Pull Request가 많다면 시간이 오래 걸릴 수도 있다.)

$ git fetch origin
remote: Counting objects: 30, done.
remote: Total 30 (delta 15), reused 15 (delta 15), pack-reused 15
Unpacking objects: 100% (30/30), done.
From github.com:username/project-name
 * [new ref]         refs/pull/104/head -> origin/pr/104
 * [new ref]         refs/pull/112/head -> origin/pr/112
 * [new ref]         refs/pull/12/head -> origin/pr/12
 * [new ref]         refs/pull/16/head -> origin/pr/16
 * [new ref]         refs/pull/29/head -> origin/pr/29
 * [new ref]         refs/pull/93/head -> origin/pr/93
 * [new ref]         refs/pull/94/head -> origin/pr/94
 * [new ref]         refs/pull/97/head -> origin/pr/97

원격저장소의 Pull Request를 자동으로 모두 가져온다. 이는 원격 저장소를 패치할 때마다 가져오므로 따로 신경을 쓰지 않아도 된다. 물론 여기서 로컬에 생긴 브랜치는 일반적인 로컬 브랜치가 아니라 원격 저장소에 대한 브랜치일 뿐이므로 읽기 전용이라서 여기서는 바로 작업을 할 수 있다. 나 같은 경우에는 git checkout -b pr-104 origin/pr/104같은 식으로 필요할 때 로컬 브랜치를 만들어서 테스트를 해보고 브랜치를 제거하는 방식으로 사용한다. 모든 Pull Reqest를 모두 가져오는 것이 과도해 보일 수도 있지만(당연히 용량도 같이 늘어난다.) 회사의 메인 프로젝트처럼 계속 Pull Request를 이용하는 흐름으로 작업을 하다 보면 Pull Request를 가져오는 경우가 빈번하므로 꽤 편하다. 그리고 클라이언트가 있어서 개발 서버에 Pull Request를 적용해서 테스트해 보거나 하는 경우 이를 적용할 때마다 서버에서 적용하는 것이 상당히 귀찮은 일인데 자동으로 가져오도록 설정을 해두면 빈번하게 Pull Request를 바꿔가면서 서버에 적용해서 테스트해보기도 쉽고 다른 2개의 Pull Request를 서버에 적용해서 테스트해야 할 때에도 두 Pull Request를 머지한 브랜치를 하나 만들면 되므로 아주 간단하다.

그리고 이렇게 Pull Request를 이용해서 작업하는 경우 코드 리뷰를 하거나 테스트를 수행하므로 Pull Request의 커밋이 업데이트되는 경우가 많다. 새로운 커밋을 추가하는 때도 있지만 커밋 히스토리를 관리하는 프로젝트라면 Pull Request는 push force를 이용해서 기존 커밋을 바꿔치기하는 경우가 더 일반적이다. 이러한 경우에도 다음과 같이 자동으로 업데이트되므로 문제가 없다.

$ git fetch origin
From github.com:username/project-name
 + 111b8fe...ca41ab9 refs/pull/104/head -> origin/pr/104  (forced update)

해당 레퍼런스로 만든 로컬 브랜치만 제거한 뒤에 다시 만들면 된다.(이 부분은 좀 귀찮;;)

특정 Pull Request만 로컬에 가져오는 별칭

자주 쓰는 프로젝트 같은 경우는 모든 Pull Request를 가져오게 하는 게 편하지만 그렇지 않은 프로젝트는 오히려 귀찮다. 특히 오픈소스 프로젝트들이 그렇고 Pull Request가 아주 많다면 훨씬 더 불편하다. 이럴 때는 로컬에서 테스트나 수정을 해봐야 하는 특정 Pull Request만 쉽게 가져오고 싶어지는데 앞에서 수동으로 가져오는 방법은 외우고 있기도 힘들고 사용하기도 귀찮다. 다행히도 이러한 설정을 쉽게 쓸 수 있도록 만들어 놓은 사람이 있다.

[alias]
  pr  = "!f() { git fetch -fu ${2:-origin} refs/pull/$1/head:pr/$1 && git checkout pr/$1; }; f"
  pr-clean = "!git for-each-ref refs/heads/pr/* --format='%(refname)' | while read ref ; do branch=${ref#refs/heads/} ; git branch -D $branch ; done"

전역 Git 설정파일인 ~/.gitconfig를 열어서 위의 두 가지 별칭을 추가한다. 이는 앞에서 설명한 Pull Request를 가져오는 방식을 간단한 명령어로 사용할 수 있도록 별칭을 추가한 것이다. pr을 Pull Request를 가져오는 별칭이고 pr-clean은 로컬에 만든 Pull Request용 브랜치를 제거하는 별칭이다.

$ git pr 94
remote: Counting objects: 6, done.
remote: Total 6 (delta 3), reused 3 (delta 3), pack-reused 3
Unpacking objects: 100% (6/6), done.
From github.com:username/project-name
 * [new ref]         refs/pull/94/head -> pr/94
Switched to branch 'pr/94'

사용하는 방식은 git pr PR_NUM과 같이 사용하면 된다. 해당 번호의 Pull Request를 가져와서 pr/PR_NUM식으로 브랜치를 만들어 주고 해당 브랜치로 체크아웃까지 해준다. 사용하는 저장소에서 Pull Request를 리뷰하다가 로컬에서 테스트해보고 싶을 때 아주 간단하게 사용할 수 있다.

$ git pr-clean
Deleted branch pr/94 (was 8dae336).
Deleted branch pr/112 (was 97abd00).

git pr-clean 명령어를 사용하면 Pull Request를 가져온 브랜치를 한꺼번에 지워준다. 사용하다가 필요할 때마다 한 번씩 브랜치를 지워서 정리해 주면 된다.

2016/03/19 04:24 2016/03/19 04:24