[Git/JGit] PullRequest 구현하기

코드를 주고 받는 방법엔 크게 두가지가 있는것 같다. 프로깃이라는 책에서도 두가지를 설명하는데 하나는 고전적인 방법으로 패치 파일을 주고 받을 수 있겠고, 나머지 하난 상대방 브랜치를 merge하거나 rebase하는 식으로 코드를 직접 합치는 방법이 있겠다.

나는 후자를 선택했는데 그전까진 패치 파일 주고 받는 방법을 미처 생각하지 못했었고, pull request가 주는 어감때문에도 왠지 “코드 가져가라는 요청”이니까 merge로 코드를 받아가면 되겠군? 이라고 생각해서 이쪽으로 발상을 한것 같다.

암튼, 그래서 어떻게 하기로 했냐면.. 요청 보내는 쪽에서 가져갈 브랜치가 그 브랜치에 있는 코드를 어느 브랜치에 보낼지 선택하게하고 무슨 코드를 보낸다고 대충 적는다.

그럼 서버에 데이터가 하나 저장되는데 그게 PullRequest라는 도메인으로 저장된다. 그 안에는 어떤 Project(역시 도메인)에서 어떤 Project로 어떤 Branch에서 어디로 보내는지 누가 보내는지 누가 받았는지 등의 정보를 가지고 있다.

그럼 코드를 받는 프로젝트의 “코드 주고받기”라는 메뉴에 가보면 열려있는 풀리퀘 메뉴쪽에 하나가 표시되고 거길 열어보면 누군가 요청 보낸걸 볼 수 있다.

거기서 클릭해서 들어가면 이제 두가지를 할 수 있는데.. 하나는 “수락” 하나는 “거절”.

“수락”을 하면 다음과 같은 일이 발생한다.

일단 Git 저장소는 전부 bare모드로 만들어져있기 때문에 Git  저장소에서 직접 브랜치를 옮기거나 checkout을 받는다거나 하는 작업을 할 수 없다. 그래서 요청을 받아줄 프로젝트의 저장소를 bare모드가 아닌 non-bare 모드로 clone을 한다. 다음으로 ‘요청 받을 브랜치’와 ‘요청 보낸 브랜치’를 두개 만들고 각각을 ‘요청 보내온 저장소’와 ‘요청 받아줄 저장소’의 브랜치에서 fetch 해온다. 그런다음 ‘요청 받을 브랜치의 코드를 받아온 브랜치’로 이동해서 ‘요청 보내온 브랜치의 코드를 받아온 브랜치’의 코드를 머지 한다.

머지가 잘 되면 요청 받을 프로젝트의 브랜치로 ‘요청 받을 브랜치의 코드를 받아와서 요청 보낸 브랜치의 코드를 받아온 브랜치와 머지한 코드’를 푸쉬한다.

이렇게하면 풀리퀘가 된다. 그리고 머지 커밋이 항상 남도록 하려면 merge할때 NO_FF 옵션을 주면 된다.

이제부터 다듬기로, 머지하기 전에 머지가 잘되는지 확인해보고(사실 확인이라기보다 그냥 한번 해보면 되는데.. 암튼) 미리 머지가 잘 될 상황인지 아닌지 알려줄 수 있겠고, 잘 안되는 상황이면 직접 콘솔로 머지하고 컨플릭트 해결하라고 안내해 줄 수 있겠다.

그리고 풀리퀘를 만들어 보낼 시점에도 위와 같이 머지가 잘 되는 상황인지 체크해서 알려줄 수 있겠다. 어디서 하는게 좋을지 생각해 봤는데 어차피 보내느 시점과 받는 시점 사이에 받는 브랜치의 코드가 바뀌면서 처음 보낼 땐 머지가 잘 될것 같아도 나중에 안된느 시점이 있을 수 있으니까 그냥 두번 다 해주는게 좋을 것 같다고 결정했다.

그 다음 다듬기로는 풀리퀘가 어떤 커밋을 보내는 건지 커밋 목록과 브랜치 간의 파일 DIff 정보를 보여주는건데.. 후자는 금방 찾았는데 브랜치간에 차이나는 커밋 목록 만드는게 생각처럼 쉽게 안찾아진다. git show-branch와 비슷한 명령어가 jgit에 없는것 같다. 어떻게 해야할런지.. 흠… 좀 더 고민하거나 팀원에게 도움을 받아야겠다.

갈 길이 멀지만… 일단 포크/풀리퀘 구현은 가능하단걸 알았으니까 끝.

[Git/Github] JGit으로 포크는 어떻게 구현해야 할라나.

모르지 나도.. 정답이 어디 있간디? 있으면 나도 보고싶다.

일단 내가 구상한 방법은 git clone이다. 포크는 풀리퀘를 구현하기 위한 발판이나 마찬가지인데 나중에 풀리퀘 구현 방법에 따라 포크 구현 방법이 달라질지도 모르겠다.

“user1/project1을 보고 있는 user2가 fork 버튼을 누르면 user1/project1과 동일한 user2/project1이 생긴다.”

대충 포크의 요구사항을 기술하면 위와 같은데, 이걸 달성하기 가장 쉬운 방법은 Clone 인것 같다.

DVCS 호스팅 서버에서 user2 디렉토리에서 git clone user1/project1을 실행해서 user1/project1을 clone 하면 다음과 같은 일이 벌어진다.

user2/project1이라는 디렉토리가 만들어지고 그 안으로 walking tree가 들어오고 그 안에 .git 디렉토리가 들어온다. 그리고 레퍼런스가 두개 생긴다. 하나는 remote/origin으로 user1/project1을 가리키는 레퍼런스고, 하나는 master 브랜치인데 이건 fork 할 프로젝트의 기본 브랜치가 뭐냐에 따라 달라질 수 있지만 보통은 master겠지.

https://www.kernel.org/pub/software/scm/git/docs/git-clone.html

이제 이걸 JGit으로 할라면 어떻게 해야되냐가 문제인데.. 스택오버를 뒤지면 잘 나온다.

cloneRepository()를 호출하면 CloneCommand 타입의 객체나 나온다. 나머진 API 문서 보면서 코딩.

이제 git clone으로 포크 기능 만들기에 필요한 기능은 확인이 됐는데… 이제부터가 시작이다.

만약에 포크하려는 이름의 프로젝트 이름이 이미 존재한다면? 프로젝트를 삭제하면 더 디렉토리도 잘 지워지나? 더 나가서는 100명이 포크따면 똑같은 워킹 디렉토리 100개 생기는건데 이건 어떻게하지? 그냥 그렇게 생기게 해야되는건가? 아니면 뭔가 중복되는 워킹 디렉토리 수를 줄일 수 있는 방법은 없을까? 등등 많지만.. 일단은 풀리퀘에 집중.

풀리퀘는 그럼 어떻게 구현할까나? 대충은 생각해봤지만 좀 더 정리해야되서 나중으로 미뤄야겠다.

[Git+Github] 포크 따서 작업하다가 원본이랑 싱크 맞춰서 보내기

보통 깃헙으로 포크 따서 작업하다보면 내가 포크 따온 원본의 진도가 더 나가있는 경우가 있다. 부지런한 프로젝트일 경우엔 자주 그러는데… 그럴땐 원본에 push하려는 branch의 HEAD 위에 내가 지금까지 작업한 코드를 올려서 보내는게 좋다. 그래야 내가 보낸 풀리퀘를 머지하려는 쪽에서 별다른 문제없이 머지할 수 있지.. 안그러면 지금 원본이랑 내가 보낸거랑 충돌난걸.. 받는 쪽에서 처리해야 되는데.. 그런짓은 코드 기여가 아니라 괴롭히기 수준이 되는거다.

그러나.. 그렇게 해야하는지 누가 알겠나.. 모르지.. 그러니까 코드 기여하는 방법같은 가이드를 잘 제공해서 최소한 풀리퀘 하기전에 이러 저러한걸 해달라고 문서화 해두는게 오픈 소스 프로젝트 입장에서는 최선이고, 기여하려는 사람 입장에서는 그 문서를 잘 읽고 따라주는게 서로간에 할 일인거 같다.

스프링만해도 그런 문서가 있다.

https://github.com/SpringSource/spring-framework/blob/master/CONTRIBUTING.md

스프링에 코드 기여하기 가이드는 나중에 자세히 살펴보기로하고.. 깃+깃헙이나 공부하자.

먼저 깃헙에는 nforge/hive 프로젝트가 있고 그걸 포크딴 keesun/hive 프로젝트가 있다.

내가 작업할 기능은 프로젝트 첫화면에서 프로젝트와 관련있는 히스토리, 소스 URL, 해결/미해결 이슈 개수 등을 보여주는거고, 이슈는 JIRA로 관리하고 있다.

먼저 깃헙/keesun/hive에 있는걸 로컬로 clone을 받아서 JIRA 이슈 번호로 브랜치를 딴다.

git branch HIVE-48

git checkout HIVE-48

그럼 HIVE-48이라는 브랜치가 생기고 그 브랜치로 이동해서 작업을 시작한다.

git commit -m “1”

git commit -m “2”

편의상 두번 저렇게 커밋했다고 치자. 물론 커밋 메시지는 잘 적는게 좋다. 난 지금 편의상 1번 커밋 2번 커밋으로 부를라고 일부러 저렇게 적은거다. 그런데 사실 지금은 대강 적어도 된다. 어차피 지금까지는 나만 보는거니깐… 나만 알아볼 수 있음 머라고 적던지 상관없다.

이 두개의 커밋으로 작업이 끝났다고 생각해보자. 이제 이걸 깃헙/nforge/hive의 master 브랜치로 풀리퀘를 보내고 싶으면 어찌해야 할까…

먼저 깃헙/keeun/hive로 올려야한다. 본인의 깃헙으로 먼저 올리는거다. 그런데 여기서도  깃헙/keesun/hive의 어떤 브랜치로 올리느냐가 중요한데, master로 올리지 말고 지금 쓰고 있는 브랜치를 그대로 올리는게 경험상 좋다. master 브랜치는 nforge/hive master 브랜치를 트래킹하는 브랜치로 쓰는게 어떨까싶어서인데… 뭐 맘대로. 난 HIVE-48 브랜치로 올리기로 결정했다.

git push origin HIVE-48

이렇게하면 깃헙/keeun/hive에 HIVE-48이라는 브랜치가 생기면서 내가 작업했던 두개의 커밋이 올라간다.

자 이제부터 복잡해진다.

풀리퀘를 누를라고 봤더니… 깃헙/nforge/hive에 그새 커밋이 생겼다. 내껄 보낼수가 없다. 물론 깃헙에서 풀리퀘 버튼은 눌리니까 그냥 보내도 되지만 그게 예의가 아니라고 글 처음에 적었다. 그러니까 싱크를 맞춰서 다시 보내야 하는거다. 다행히도 아직 풀리퀘를 보내기 전이다.

싱크부터 맞춰보자. 싱크를 맞추는 방법은 merge도 있고 rebase도 있지만 난 rebase가 좋다.

http://dogfeet.github.io/articles/2012/git-merge-rebase.html

왜 좋은지는 위에 글에 잘 dogfeet님께서 잘 설명해주셨으니 살펴보시길… 패스.

git fetch nforge

참.. 여기서 nforge는 내가 로컬에 등록한 값이다.

git remote add nforge 깃헙/hive/nforge.git

뭐 이런식으로 등록해둔 값이다.

암튼, 패치를 받아오면 트래킹 브랜치라는 곳에 nforge 최신 내용이 들어온다.

http://dogfeet.github.io/articles/2012/git-tracking-vs-remote-tracking.html

트래킹 패치도 dogfeet님글이 이해하기 좋다.

이제는 rebase를 한다.

git rebase nforge/master

nforge/master 위로 내 HEAD를 올리겠다는거다. 올리면서 충돌날꺼 있으면 충돌날꺼고 운좋게 충돌날꺼 없으면 아무일없이 끝날꺼다.

이제 다시 깃헙/keesun/hive로 Push를 해야 하는데 방금 작업한거 때문에 HEAD 밑에 있는 커밋 히스토리가 바껴서 push가 안될꺼다. 그럴때는 강제 push.

git push -f origin HIVE-47

이렇게 해버리면 깃헙/keesun/hive로 지금 로컬에 있느 HIVE-47 커밋 히스토리 그대로 푸쉬된다.

자 이제 어려운거 하나는 지나갔고… 문제는 커밋 두개가 하나의 작업이고 이걸 그냥 풀리퀘해도 되긴하는데… 깔끔한걸 좋아하는 분들은 커밋 여러개를 하나로 합쳐서 하나의 커밋으로 보내달라고 하길 원할꺼다. 스프링도 그렇다. 그런 작업을 squash라고 부르나 본데 어원은 모르겠고 스쿼시를 하려면 이번에도 rebase를 써야된다.

가장 최신 커밋 두개를 하나로 합치면 되니까..

git rebase -i HEAD~2

이렇게 하면 vim 편집창 같은게 뜨면서 위에 커밋 로그 두개가 보인다. 그중에서 한쪽으로 합쳐버리고 싶은걸 pick 대신 s나 squash로 바꿔주면 된다. 사실 그 화면에서 특정 커밋을 빼버릴 수도 있고 커밋 순서를 바꿀 수도 있고.. 여러가지 작업을 할 수 있다.

http://ko.gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html

단순히 합치는거라면 이글이 도움이 되고…다른 여러 기능에 대해서는

http://git-scm.com/book/en/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages

이 글이 좋다.

커밋 하나를 s로 바꾸고 wq! 하면 또 다시 편집창이 뜨는데 이번에는 커밋 메시지를 고치는 과정이다 잘 바꾸고.. wq! 하면 rebase 끝.

이제 다시 강제 push를 해야하니까..

git push -f origin HIVE-48

오.. 이제야 풀리퀘 준비가 끝났다. 싱크도 맞췄고 커밋도 스쿼시 했으니… 이제 깃헙으로 풀리퀘 보내면 된다.

ps: 단순히 어떤 툴에 포크/풀리퀘 기능이 있다고 협업이 쉽게 될꺼라 생각하는건 큰 착각이었다.

[Git] 원격 저장소에있는 브랜치를 로컬로 가져오기

참고: http://blog.outsider.ne.kr/641

git pull이나 fetch를 아무리해도 원격 저장소에 있는 브랜치를 땡겨오진 않습니다.

git branch를 실행하면 본인의 저장소에 있는 브랜치 목록을 확인할 수 있죠. 여기에 -r을 더 붙이면 원격 저장소의 브랜치를 볼 수 있습니다. -a를 붙이면 원격과 로컬에 있는 브랜치를 전부다 보여주죠.

$ git branch -r
origin/HEAD -> origin/master
origin/dev-env-refactoring
origin/gh-pages
origin/master

원격에 dev-env-refactoring이라는 브랜치가 있는데 이걸 로컬로 가지고 오고 싶다면..

$ git checkout -b env origin/dev-env-refactoring
Branch env set up to track remote branch dev-env-refactoring from origin.
Switched to a new branch ‘env’
$ git branch
* env
master
$ git branch -a
* env
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev-env-refactoring
remotes/origin/gh-pages
remotes/origin/master

끝… 이제 머지를 해볼까;;

[GIthub] 포크 땄는데.. 원본이 계속 커밋되고 있다면.. 싱크를 맞춰야지

$ git remote add telepathy git@github.com:spring-sprout/telepathy.git
$ git remote
origin
telepathy
$ git pull telepathy master
remote: Counting objects: 69, done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 61 (delta 14), reused 55 (delta 8)
Unpacking objects: 100% (61/61), done.
From github.com:spring-sprout/telepathy
* branch master -> FETCH_HEAD
Updating f29593b..15fc160
Fast-forward
.gitignore | 4 ++-
src/chapter3/Sort.java | 17 +++++++++
src/chapter3/bubblesort/BubbleSort.java | 8 ++++
src/chapter3/bubblesort/ILhyunBubbleSort.java | 32 ++++++++++++++++
…/selectionsort/ILhyunSelectionSort.java | 39 ++++++++++++++++++++
…/selectionsort/JungWooSelectionSort.java | 24 ++++++++++++
test/chapter3/bubblesort/ILhyunBubbleSortTest.java | 25 +++++++++++++
…/selectionsort/ILhyunSelectionSortTest.java | 25 +++++++++++++
…/selectionsort/JungWooSelectionSortTest.java | 26 +++++++++++++
9 files changed, 199 insertions(+), 1 deletions(-)
create mode 100644 src/chapter3/Sort.java
create mode 100644 src/chapter3/bubblesort/BubbleSort.java
create mode 100644 src/chapter3/bubblesort/ILhyunBubbleSort.java
create mode 100644 src/chapter3/selectionsort/ILhyunSelectionSort.java
create mode 100644 src/chapter3/selectionsort/JungWooSelectionSort.java
create mode 100644 test/chapter3/bubblesort/ILhyunBubbleSortTest.java
create mode 100644 test/chapter3/selectionsort/ILhyunSelectionSortTest.java
create mode 100644 test/chapter3/selectionsort/JungWooSelectionSortTest.java
$

끝~