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
이제 원격에 올린 변경사항을 원본 jQuery 저장소에 적용하기 위해서 Pull Request를 보내야 한다. Pull Request도 git 자체에 있는 기능은 아니고 Github에서 제공하는 기능이라고 할 수 있다.(써본 적은 없지만 Github를 쓰지 않는다면 패치내용을 메일로 보내면 적용하는 방식을 취했었다.) Pull Request를 사용하는 이유는 jQuery 원본저장소에는 쓰기권한이 없기 때문에 수정한 내용을 Pull Request로 보내면 jQuery 저장소의 커미터들이 내용을 확인한뒤에 승인을 하면 Pull Request의 내용이 jQuery 원본저장소에 merge가 된다.(물론 승인하지 않고 거절할 수도 있다.)
이제 Github의 자신의 저장소에서 변경사항을 적용한 브랜치페이지로 이동해서 상단의 Pull Request버튼을 누르면 된다. 최근에 변경한 내용은 중간에도 Pull Request 버튼이 나온다. 이 버튼을 클릭하면 다음과 같이 Pull Request를 보내는 화면을 볼 수 있다.
상단에 Pull Request를 보내는 자신의 브랜치와 대상이 되는 브랜치를 지정할 수 있고 그 아래 Pull Request에 대한 내용을 볼 수 있다. jQuery의 경우 CONTRIBUTING.md 파일이 존재하기 때문에 위에 해당 부분에 대한 안내가 나온다. 오픈소스 프로젝트마다 Pull Request를 받아주는 약속이 다르므로 보내기 전에 이를 먼저 확인해야 한다. 소스 수정이 잘 되었더라도 이 약속을 제대로 지키지 않으면 받아주지 않는다. 탬에서 Commits나 Files Changed를 클릭하면 Pull Request를 보내는 커밋과 변경사항이 제대로 되었는지 확인할 수 있다.
물론 다른 사람의 소스를 Fork해서 나중에 Pull Request까지 보내는 경우가 많진 않지만(이정도까지 하는 사람은 대부분 이미 Git에 익숙해져있다고 생각한다.) 직접 저장소를 생성해서 혼자 사용한다고 하더라도 이 전체 흐름의 일부로 포함되기 때문에 이 흐름에서 해당 부분만 참고해서 사용하면 된다.
이미지는 무슨 툴로 그리신건가요? 이뻐보입니다.
OmniGraffle 입니다. ㅎㅎㅎ
그때 쓴다고 하고 썼는데 쓰다보니 또 복잡해서 도움이 되실런지... ㅎ
좋은 설명 감사합니다.
내용 중 Pull Request 쪽에 "jQuery 저장소의 커니터들이" 부분에 오타가 있네요.
앗.. 그러네요.. 수정했습니다. ^^
PULL REQUEST 라고 해서 원본 커미터들이 내 변경본을 땡겨 오는줄 알았는데
내가 보내는 거였군요 ㅎㅎ
많이 배우고 갑니다. ^^
정확히는 자신의 변경본을 땡겨가라고 요청을 보내는거죠.. ㅎㅎㅎ
좋은글 감사드립니다.
옙! ^^
git 때문에 해매고 있었는데 자세하고 좋은 글 감사합니다
옙!!
Naver Cafe에 아웃사이더님 Github에 관한 글을 링크를 담았습니다.
문제가 될 요지가 있다면 말씀해 주세요 바로 삭제 하겠습니다. ^^
언제나 좋은 글을 작성해 주셔서 감사해 하고 있답니다 ㅎㅎ
CCL따라주신다면 아무 문제 없고요 CCL 따르기 어려우시면 출처만 밝혀주시면 됩니다. ^^
위 와 같이 로컬 저장소에 등록된 원격저장소에 목록을 보면 clone을 받아온 대상인 원격 저장소가 origin이라는 이름으로 등록되어 있다.
위부분에도 오타가 있습니다.ㅎㅎ
예 수정했습니다. ^^;;
감사합니다 큰 도움이 되었습니다.
네 ^^
오랜만에 GitHub 로 돌아와서 이것저것 해보려고 했더니 기억이 하나도 안 나서 꽤 해멨는데
이렇게 잘 정리된 글 덕분에 한방에 해결됐네요.
좋은 글 감사합니다.
그래서... 링크 좀 업어 가겠습니다...(링크만 띄울께요..문제가 되면 삭제할게욤)
CCL에만 따라서 퍼가시면 됩니다. ^^
좋은글 감사합니다.
1번글에 이어서 2번 글도 블로그로 퍼갑니다.
책을 출간하셔도 좋을 정도로 설명이 훌륭합니다.
그런데 앞의 코멘트 중에 CCL 을 언급하셨는데 그게 뭔가요?
제가 워낙 모르는게 많아서....
아.. 여기도 댓글을 남겨주셨군요
CCL은 Creative Commons License를 의미합니다. 제 글에 대한 저작권을 표시하기 위한 라이센스라고 보시면 되고 왼쪽 사이드바에 보시면 CC라고 표시 되어 있습니다.
http://creativecommons.org/licenses/by-nc-sa/2.0/kr/
제 글은 위 링크에 나오는 대로 "저작권 표시" - "비영리" - "동일조건 변경허락"의 조건 하에 공유를 하고 있습니다.
좀 풀어드리면 저작권을 표시하시고 비영리로 사용하는 경우 따로 제 허락을 받지 않고도 사용하실 수 있고 동일 라이센스로 배포한다는 조건하에서는 변경도 가능하다는 의미입니다.
번역한 것을 짜집기해서 올리는 것보다 완벽하게 소화하신 다음에 올리시는 것이 다른 사람들의 시간낭비를 줄이는 것 아닌지요 ?
뭘 번역해서 짜집기 했다는 건가요?
깔끔한 설명에 개념잡고 갑니다.
고맙습니다.
전체 흐름을 가지고 설명할 필요성을 느껴서 적은 글이었는데 이해하기 좋으셨다니 다행이네요 ^^
안녕하세요~~
제가 WPS Office를 번역하는 중인데요 질문이 있습니다!
1. 원본 저장소를 포크한다
2. 내 저장소를 로컬로 클론한다
3. 터미널에서 내 저장소로 이동한다
4. 원본저장소를 remote에 추가한다
5. (원본저장소를 wps, branch를 master로 지정할 경우) git pull wps master 를 치면 원본저장소와 로컬이 동기화된다
6. 이제 내 로컬을 git push하면 gihub에 있는 포크한 저장소까지 동기화가 완료된다
제가 이해한게 맞나요?
그리고 만약 git pull 대신에 git fetch 후 git merge를 하려면
1. 내 로컬 저장소로 이동
2. git fetch wps master
3. git merge
를 실행하면 되나요?
네 이해하신게 맞습니다. 저는 pull을 거의 안쓰고 fetch, merge를 주로 쓰는데.. 정확히 말씀 드리면 다음의 과정이 됩니다.
1. 내 로컬 저장소로 이동
2. master 브랜치로 체크아웃
3. git fetch wps
- fetch는 브랜치 단위로 하지 않고 리모트 단위로만 지정합니다.
4. git merge wps/master
- 로컬의 리모트 브랜치는 wps 리모트의 master 브랜치는 wps/master 로 사용합니다. 그래서 위처럼 지정하면 현재의 master 브랜치를 wps/master 브랜치와 머지하게 됩니다.
- 싱크를 맞추는 경우에는 master 브랜치에는 별도의 커밋을 하지 않고 fast-forward로 머지되도록 싱크용으로만 사용하고 별도 커밋은 브랜치를 별도로 따서 사용하는게 편합니다.
좋은글 정말 감사드립니다.
Github에 대해서 이해 안되던 부분이 확 이해되네요.
도움되신다니 다행이네요 ㅎㅎ
요즘에 아웃사이더 님의 git 시리즈가 회사 안에서 생존하는데에 매우 커다란 도움이 됩니다!
^^ 회사에서 Git을 쓰시는군요. ㅎㅎ
잘 봤습니다. 감사합니다.
네 감사합니다.
너무 유용했습니다. 감사합니다.
네 ^^
글 잘 봤습니다. 나도 이런 내용을 블로그에 올리고 싶었는데 온라인에 글쓰는 재주가 없어서 포기했습니다.^^
저는 보통 원본 remote이름 을 내 포크 싸이트와 구분하기 위해 parent 라는 이름을 사용합니다.
origin은 포크된 내 github 저장소
parent는 원래 프로그램 github 저장소
더 좋은 이름을 찾고있지만 아직 마땅한 이름이 나오지 않아서 그냥 쓰고 있습니다.
그리고 로컬의 master 브랜치는 sync를 맞추기 위해서 사용하고 내가 테스트등 수정 작업이 필요하다고 생각하면 myrevision이라는 브랜치를 따로 만들어서 사용하고 있습니다. 만약 코드가 개선가능한 내용이면 내 github레포지토리로 push한 후 원본에 pull request를 보내고 그렇지 않으면 폐기합니다. 여기까지 제 사용방법입니다. 참고 가능한 내용인지 모르겠네요. ^^
잘 정리해 주신 듯 합니다. 저는 fork라는 이름을 사용하고 있는데 parent라는 이름도 괜찮네요. 요즘은 프로젝트를 여러개 사용하다보니 fork라는 이름도 좀 헷갈려고 고민하고 있습니다.
좋은 내용 감사드립니다
뭔가 딱딱한 내용 대신에 직접 이해하신걸 풀어 써주시니
이해가 더 잘되는듯 하네요 자주 방문하겠습니다.
깃이 처음에 좀 어렵긴 하죠. ^^ 도움 되셧다니 다행입니다.
좋은정보 감사합니다! 출처밝히고 블로그에 담아가요!!