Outsider's Dev Story

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

Github를 이용하는 전체 흐름 이해하기 #2

이 포스팅은 Github를 이용하는 전체 흐름 이해하기 #1에서 이어진 글이다.


Fetch & Merge
원격저장소의 변경사항을 업데이트하는 다이어그램

로컬에서 작업을 하다보면 원격저장소에 변경사항이 생긴다. 클론받은 이후에 원격저장소에 누군가 소스를 푸시하면 이 변경사항을 다시 로컬로 가져와야 하는데 이 과정을 fetch로 원격저장소의 변경사항을 로컬로 가져온 뒤에 로컬의 브랜치에 merge하는 과정으로 이루어진다. 이 과정은 일반적으로는 git pull이라는 명령어를 통해서 한방으로 이루어지는데 git pull보다는 git fetch후에 git merge로 나누어서 작업하는 것을 보통 더 권장한다.(권장하는 이유는 여러가지가 있지만 conflict가 생겼을 때 대처가 훨씬 쉽다던지  merge를 훨씬 자유롭게 할 수 있다.)

$ git remote -v
origin    git@github.com:outsideris/jquery.git (fetch)
origin    git@github.com:outsideris/jquery.git (push)

위 와 같이 로컬 저장소에 등록된 원격저장소 목록을 보면 clone을 받아온 대상인 원격 저장소가 origin이라는 이름으로 등록되어 있다. github에서 저장소를 새로 생성하면(fork로 생성하는 경우외에) git remote add origin git@github.com:outsideris/jquery.git와 같이 저장소를 추가하도록 안내를 하는데 이는 관례에 따라 메인 원격저장소를 origin으로 등록한 것이다. 그리고 이어서 소스를 git push -u origin master로 푸시하도록 안내하고 있는데 여기서 -u 옵션은 설정파일에 현재의 master 브랜치를 origin의(여기서는 fork받은 자신의 원격저장소) master 브랜치로 연결해 주어 다음부터는 자동으로 master브랜치에서 git push를 하면 origin의 master브랜치로 푸시가 되고 git pull을 하면 origin의 master를 fetch해서 로컬의 master로 merge하도록 설정하는 것이다. 그래서 .git/config파일의 내용을 보면 다음과 같이 master브랜치의 원격저장소와 머지할 브랜치가 설정되어 있는 것을 볼 수 있다.(clone을 받아오면 이 설정이 자동으로 추가된다.)

[branch "master"]
    remote = origin
    merge = refs/heads/master

그래서 master가 아닌 다른 브랜치를 원격저장소에 푸시하려면 대상 저장소와 브랜치를 직접 지정해 주어야한다.(매번하기 싫으면 위처럼 설정파일에 지정하면 된다.) 여기서는 fetch와 merge에 대해서 설명하는 단계이므로 이 부분에 대해서만 얘기하면 git pull하면 자동으로 master에 머지되도록 설정이 되어 있는 것이고 이를 다시 말하면 반드시 master에만 머지해야 하는 것은 당연히 아니다.

지금까지 origin만 살펴보았는데 이 시나리오에서는 우리에게 필요한 원격저장소가 하나 더 있다. 즉, fork받은 자신의 저장소 외에 원본인 jQuery의 원격저장소가 있다. 이 원격저장소에도 누군가 계속 소스를 수정할 것이므로 jQuery를 기반으로 계속 작업을 할 것이라면 원본 jQuery저장소의 변경사항도 로컬로 가져와야 한다. 처음 git을 배울 때(정확히는 hg로 DVCS를 배웠지만...) 가장 궁금했던 것이 fork한 저장소는 fork한 시점으로 멈춰있으므로 변경사항을 가져오려면 어떻게 하는가?였다. 그래서 매번 새로운 내용이 필요할 때마다 github의 저장소를 삭제하고 다시 fork로 새 저장소를 만들어서 사용했다.(ㅡㅡ;;) 이 당시에는 git을 제대로 이해못했으므로(지금도 ㅠㅠ) 동기화기능을 github에서 제공해야 하는 것이 아닌가 하고 생각했지만 이러한 부분은 git이 자유롭게 다룰 수 있으므로 github에서 제공할 이유가 없는 기능이다.

이 문제를 해결하려면 그냥 jQuery의 원본저장소도 원격저장소로 추가해주면 된다.

$ git remote add jquery git@github.com:jquery/jquery.git
$ git remote
jquery
origin

앞에서 origin이라는 이름으로 원격저장소를 추가한것처럼 이번에는 jquery라는 이름으로(원하는 대로 지정하면 된다.) jquery의 원본 원격저장소의 주소를 원격저장소로 등록하고 git remote 명령어로 확인해 보면 원격저장소가 2개 등록되어 있는 것을 볼 수 있다. jquery 원본 저장소에는 push할 권한은 없지만 읽기권한은 있으므로 변경사항을 가져오는데는 아무런 문제가 없다. 이러한 점이 git이 DVCS이기 때문에 갖는 강점인데 원격저장소는 필요한대로 추가할 수 있고 어디서나 가져와서 머지할 수 있다.

$ git fetch jquery
From github.com:jquery/jquery
 * [new branch]      1.8-stable -> jquery/1.8-stable
 * [new branch]      master     -> jquery/master
 * [new branch]      promisea   -> jquery/promisea
$ git merge jquery/master
Already up-to-date.

이제 jquery저장소(이름을 그렇게 주었으므로)의 내용을 fetch받아오면 위처럼 새로운 브랜치들이 생기고 이 내용은 jquery/master라는 이름으로 로컬에서 접근할 수 있다.(다른 브랜치도 몇 개 가져온 것을 볼 수 있다.) git은 SVN과는 다르게 히스토리를 검색하기 위해서 원격 저장소에 접근하지 않아도 되고 로컬에 전체 히스토리를 모두 내려온다. 그래서 실제로 origin과 jquery 아래 내려받은 저장소의 브랜치가 존재하고 있다.

$ git branch -r
  jquery/1.8-stable
  jquery/master
  jquery/promisea
  origin/1.8-stable
  origin/HEAD -> origin/master
  origin/master
  origin/promisea

git branch -r은 원격저장소의 브랜치를 보는 명령어인데 위처럼 현재의 원격 브랜치를 모두 볼 수 있다. 이 원격 브랜치는 원격에서 받아온 브랜치를 의미하지만 실제로 그 내용은 모두 로컬에 내려받은 것이다. 하지만 이 브랜치에는 커밋을 하는 등의 수정은 할 수 없고 오로지 읽기 전용이다. 그래서 이 원격브랜치들에서 새로운 브랜치를 생성하거나 기존의 브랜치에 머지를 하는 것이 가능하다. 앞에서 본 것처럼 이 원격 저장소의 변경사항을 업데이트하려면 git fetch jquery처럼 원격저장소별로 업데이트를 하고 브랜치별로 업데이트를 하진 않는다. 앞에서 fetch로 업데이트를 한 후에 현재 브랜치(master)에 머지를 했는데 꼭 master에만 머지해야 하는 것은 아니다. 즉, 필요에 따라서는 작업을 master에다 하고 원격 저장소의 내용을 추적하는 다른 브랜치를 만들어서 머지할 수도 있다.

앞에서 commit과정을 설명할 때 일반적으로 작업을 master에서 하지 않고 별도의 브랜치를 만들어서 한다고 설명했는데 여기서 설명하고 있는 시나리오처럼 jQuery 소스를 수정하는 경우 특별한 이유가 없다면 자신이 작업하는 master브랜치를 jquery/master 브랜치와 동일하게 유지하는 것이 좋다. 그래서 merge할 때 fast-forward되도록 master를 유지하는 것이 히스토리를 좀 더 이쁘게 할 수 있는 방법이다.(fast-forward가 되지 않으면 merge했다는 새로운 커밋이 하나 생기게 된다.)


Push
이제 로컬에서 수정작업을 어느정도 완료했다면 자신의 원격저장소에 수정한 커밋들을 푸시해야 한다.

원격저장소에 푸시하는 다이어그램

이 과정은 로컬저장소에 커밋한 내역을 원격저장소에 적용하는 것으로 git push origin master처럼 푸시할 원격저장소명과 브랜치를 지정해야 한다.(앞에서 처럼 -u 옵션으로 설정했다면 master에선 git push만 해도 된다.)지정해야 한다는 것은 권한만 있다면 어떤 원격저장소에도 또는 다른 브랜치에도 푸시할 수 있다는 것을 의미한다. 대부분의 git 저장소는 fast-forward할 수 있는 경우에만 푸시할 수 있도록 하고 있고 이는 Github도 마찬가지이다.

앞에서 봤듯이 jQuery의 원본저장소와 자신의 원격저장소의 master 브랜치를 계속 싱크하려면 jquery 원본저장소를 fetch받아서 master에 머지한 뒤에 자신의 원격저장소의 master로 푸시하면 된다. 이 과정을 계속 반복하면 항상 두 master 브랜치의 동기화를 할 수 있다. commit과정에서 수정사항을 보통 별도의 브랜치를 만들어서 작업한다고 했는데 git에서 원격저장소에 branch와 tag를 push하기에서 설명했듯이 git push 원격저장소명 로컬브랜치명:원격브랜치명으로 원격저장소에 새로운 브랜치를 생성하면서 푸시해야 한다.

$ git push origin hotfixes/patched 
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 309 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
To git@github.com:outsideris/jquery.git
 * [new branch]      hotfixes/patched -> hotfixes/patched

이처럼 푸시를 하면 원격에 새로운 브랜치가 생성된 것을 볼 수 있다.


Pull Request
Pull Request를 보내는 다이어그램

이제 원격에 올린 변경사항을 원본 jQuery 저장소에 적용하기 위해서 Pull Request를 보내야 한다. Pull Request도 git 자체에 있는 기능은 아니고 Github에서 제공하는 기능이라고 할 수 있다.(써본 적은 없지만 Github를 쓰지 않는다면 패치내용을 메일로 보내면 적용하는 방식을 취했었다.) Pull Request를 사용하는 이유는 jQuery 원본저장소에는 쓰기권한이 없기 때문에 수정한 내용을 Pull Request로 보내면 jQuery 저장소의 커미터들이 내용을 확인한뒤에 승인을 하면 Pull Request의 내용이 jQuery 원본저장소에 merge가 된다.(물론 승인하지 않고 거절할 수도 있다.)

Github 저장소의 풀리퀘스트 버튼

이제 Github의 자신의 저장소에서 변경사항을 적용한 브랜치페이지로 이동해서 상단의 Pull Request버튼을 누르면 된다. 최근에 변경한 내용은 중간에도 Pull Request 버튼이 나온다. 이 버튼을 클릭하면 다음과 같이 Pull Request를 보내는 화면을 볼 수 있다.

Github에서 풀리퀘스트를 보내는 화면

상단에 Pull Request를 보내는 자신의 브랜치와 대상이 되는 브랜치를 지정할 수 있고 그 아래 Pull Request에 대한 내용을 볼 수 있다. jQuery의 경우 CONTRIBUTING.md 파일이 존재하기 때문에 위에 해당 부분에 대한 안내가 나온다. 오픈소스 프로젝트마다 Pull Request를 받아주는 약속이 다르므로 보내기 전에 이를 먼저 확인해야 한다. 소스 수정이 잘 되었더라도 이 약속을 제대로 지키지 않으면 받아주지 않는다. 탬에서 Commits나 Files Changed를 클릭하면 Pull Request를 보내는 커밋과 변경사항이 제대로 되었는지 확인할 수 있다.



물론 다른 사람의 소스를 Fork해서 나중에 Pull Request까지 보내는 경우가 많진 않지만(이정도까지 하는 사람은 대부분 이미 Git에 익숙해져있다고 생각한다.) 직접 저장소를 생성해서 혼자 사용한다고 하더라도 이 전체 흐름의 일부로 포함되기 때문에 이 흐름에서 해당 부분만 참고해서 사용하면 된다.
2012/11/25 04:30 2012/11/25 04:30