오픈소스 git 프로젝트에 Pull Request 보내기

리눅스를 개발한 리누스 토발스가 만든 소스 코드 형상관리 도구인 git이 인기를 끌면서 많은 오픈소스 프로젝트가 git을 이용하고 있습니다. git 의 인기에는 git 이 제공하는 개념과 컨셉이 기존 형상관리 도구보다 뛰어나기도 하지만 git 레포지토리를 클라우드로 서비스하고 있는 github.com의 존재도 큰 영향을 했습니다. 최근에는 오픈소스 프로젝트 뿐만 아니라 많은 기업들이 github.com에 private repository를 생성하여 회사의 소스코드를 관리하고 있습니다.

github

http://thenextweb.com/shareables/2013/09/07/githubs-new-office-includes-a-replica-of-the-oval-office

git 은 분산 레포지토리 라는 개념과 다양한 방법으로 코드를 관리할 수 있기 때문에 처음 접하는 사용자에게는 상당히 어렵게 느껴집니다. 필자 역시 아직도 다양한 유즈케이스에 대해 경험해본적이 없으며 한번 굳어진 사용 사례를 계속 사용하고 있기 때문에 git을 제대로 알고 있다고 말하기는 어렵습니다. 하지만 제가 사용하는 방식이 주로 오픈소스 프로젝트에 기여하는데 사용하는 방식이기 때문에 공유해보려고 합니다.

오픈소스 프로젝트에서 git을 사용하는 경우 다음과 같은 형태로 많이 운영하고 있습니다.

  • 정해진 사용자(committer)만 main repository에 merge 가능
    • 즉 committer 개발자가 아닌 개발자는 main repository에 push 할 수 없음
  • 각 개발자는 자신의 개인 repository에서 개발 후 main repository로 pull request를 전송하여 merge 요청

즉, 각 개발자는 자신의 repository에서 개발을 진행하고 개발이 완료된 patch를 main repository로 merge 요청을 보내는 방식으로 개발이 이루어집니다. 오픈소스 뿐만 아니라 최근에는 기업에서도 이와 같은 형태로 git을 운영하고 있는 회사들이 있습니다. 필자가 다니는 회사 역시 이렇게 운영되고 있습니다.

이번 글에서는 이런 레포지토리 관리 정책에서 git을 사용하여 개발, 커밋, 수정, Push, Pull Request를 어떻게 하는지에 대해서 살펴보도록 하곘습니다.

추가로 오픈 소스 코드 분석 자체에 대한 방법은 다음 글을 참고하세요.

Fork repository

가장 먼저 해야할 일은 원본 레포지토리로 부터 fork를 하는 것입니다. Fork는 git 자체에서 제공하는 기능은 아니고 github.com과 같은 git 레포지토리 서비스 사업자가 제공하는 기능입니다. Fork 를 수행하게 되면 본인의 github.com 계정에 기존 Repository 를 바라보고 있는 본인이 모든 권한을 가지게 되는  새로운 레포지토리가 생성됩니다.

github_fork

개인 개발 환경에 레포지토리 구성하기

다음은 개인 PC에 소스 레포지토리를 구성합니다. 다음과 같은 절차를 통해 개인이 fork한 레포지토리와 원본 래포지토리 두개의 remote 레포지토리를 등록합니다.

[~/tmp/workspace]# git clone https://github.com/apache/zeppelin Cloning into 'zeppelin'... remote: Counting objects: 16853, done. remote: Total 16853 (delta 0), reused 0 (delta 0), pack-reused 16853 Receiving objects: 100% (16853/16853), 9.62 MiB | 210.00 KiB/s, done. Resolving deltas: 100% (6571/6571), done. Checking connectivity... done. [~/tmp/workspace]# cd zeppelin

clone 을 하면 기본적으로 다음과 같이 origin이라는 이름으로 remote 레포지토리가 추가되어 있습니다. 이 remote는 레포지토리는 commit 등과 같은 로컬에서 작업하는 동안에는 사용하지 않으며 push, pull, fetch 등 원격에 있는 레포지토리 관련 명령어에서만 사용합니다.

[~/tmp/workspace/zeppelin]# git remote -v origin https://github.com/apache/zeppelin (fetch) origin https://github.com/apache/zeppelin (push)

저같은 경우 이것을 그냥 두지 않고 다음과 수정해서 사용합니다.

[~/tmp/workspace/zeppelin]# git remote rename origin upstream

원본 remote repository의 이름은 upstream 이라고 하였는데 로컬에서 개인이 사용하는 것이기 때문에 임의의 이름을 사용해도 되지만 보통은 upstream을 사용합니다.

다음은 push 권한이 있는 github.com에서 fork 한 개인 레포지토리를 추가합니다.

[~/tmp/workspace/zeppelin]# git remote add babokim https://github.com/babokim/zeppelin [~/tmp/workspace/zeppelin]# git remote -v babokim https://github.com/babokim/zeppelin (fetch) babokim https://github.com/babokim/zeppelin (push) upstream https://github.com/apache/zeppelin (fetch) upstream https://github.com/apache/zeppelin (push)

작업 브랜치 생성 및 개발 작업

이제 로컬 레포지토리 구성이 완료되었으니 작업을 위한 브랜치를 생성하고 개발 후 commit을 해보겠습니다. 브랜치를 만들기 전에 upstream과 항상 최신 상태를 유지시키는게 좋습니다. 그리고 git에서는 메인 브랜치를 보통은 master 라는 이름으로 사용합니다. 다음과 같이 로컬 레포지토리를 upstream의 최신 레포지토리로 업데이트 합니다.

[~/tmp/workspace/zeppelin]# git checkout master [~/tmp/workspace/zeppelin]# git pull upstream

pull 명령은 내부적으로는 fetch를 실행하여 최신 버전을 자져온 다음에 현재 위치한 브랜치에 merge 하는 과정을 수행한다. 따라서 위 두 명령은 로컬의 master 브랜치로 이동한 다음 upstream의 master 브랜치 내용을 merge 합니다.

이제 작업할 브랜치를 생성하는데 저의 경우  브랜치 이름은 보통"이슈 ID" 또는 "이슈 ID" + "이유 키워드" 형태로 생성합니다. 예를 들어 presto interpreter 를 만드는 이슈라면 다음과 같이 생성합니다.

[~/tmp/workspace/zeppelin]# git checkout -b ZEPPELIN-1485 (또는ZEPPELIN-1485-Presto)

이렇게 하면 현재 브랜치의 코드를 이용하여 새로운 브랜치를 생성합니다. 따라서 브랜치를 만들기 전에는 항상 현재 브랜치가 어디 있는지 확인하는 것이 중요합니다. 필자의 경우 다음과 같이 브랜치를 생성할 때는 항상 upstream의 브랜치를 지정해줍니다.

[~/tmp/workspace/zeppelin]# git checkout -b ZEPPELIN-1485 upstream/master

브랜치를 생성하였으니 이제 코드를 추가하거나 수정 합니다.

개발 중 다른 브랜치로 이동하거나 작업이 경우

하나의 브랜치에서 작업을 완료하고 다른 브랜치 작업을 하면 좋겠지만 실제 업무 환경에서는 브랜치 작업중에 브랜치를 바꾸어야 하는 상황이 빈번합니다. 예를 들어 A 브랜치 작업 중에 기존에 Pull Request 보낸 B 브랜치의 코드 리뷰에서 코드의 수정 요청을 요청하는 경우도 있습니다.

이 경우 기존 작업 중인 코드 상태는 유지를 해야 하는데 git에서는 다음과 같은 두가지 방법으로 현재 작업 중인 코드를 임시로 저장하고 다른 브랜치로 이동할 수 있습니다.

  • 현재 코드를 commit 하고 다른 브랜치로 이동 가장 심플하고 안전한 방법이기는 하지만 불필요한 commit log가 만들어 지고, 대부분의 오픈소스는 하나의 이슈에 대해 하나의 commit log만 작성하기를 원하는 경우가 많다. 따라서 다시 이 브랜치로 돌아와서 작업 완료 후 push 전에 squash 작업을 통해 commit 로그를 머지해야 한다. 이 방법은 뒤에 설명할 예정
  • git의 stash 명령을 이용하여 현재 작업 중인 소스코드를 안정하게 임시 저장하는 방법입니다. stash로 저장하면 브랜치별로 저장되기 때문에 브랜치 이동 중에 임시로 저장하기 위한 기능이라고 할 수 있다.

첫번째 방법은 뒤에서 squash 방법 설명할 때 설명하기로 하고 여기서는 두번째 방법에 대해 설명하겠습니다. stash 는 심플합니다.  임시 저장할 브랜치(현재 작업중인)에서 다음 명령을 수행합니다.

[~/tmp/workspace/zeppelin]# git status On branch ZEPPELIN-123-presto Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.md [~/tmp/workspace/zeppelin]# git stash Saved working directory and index state WIP on ZEPPELIN-123-presto: c717daf ZEPPELIN-1427. Scala z.show() doesn't work on v.0.6.1 HEAD is now at c717daf ZEPPELIN-1427. Scala z.show() doesn't work on v.0.6.1 [~/tmp/workspace/zeppelin]# git checkout other-branch

stash 작업을 수행하면 현재 working directory가 저장되었다는 메시지와 함께 현재의 HEAD가 어디인지 알려줍니다. stash 작업 이후에 브랜치를 이동해도 특별한 메시지가 나타나지 않습니다. 다른 브랜치에서 작업을 완료하고 다시 이전에 작업했던 브랜치로 돌아와서 이전 작업 파일을 로딩하기 위해서는 git stash pop 명령을 사용합니다.

[~/tmp/workspace/zeppelin]# git stash pop On branch ZEPPELIN-123-presto Your branch is up-to-date with 'upstream/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: README.md no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (89c8e9d5a0d635587e0c577425425253fc3c8770)

수정된 코드 commit

코드 수정이 완료되면 commit 명령을 이용하여 수정된 코드를 commit 합니다. commit 작업은 로컬 레포지토리에 반영하는 것이지 remote 레포지토리와는 아무런 상관이 없습니다. 오픈소스에서는 commit 메시지에 대한 가이드를 제공하는 경우도 많은데 가이드를 따라서 작성합니다.

[~/tmp/workspace/zeppelin]# commit -a # commit 후 이전 commit의 메시지를 수정하기 위해서는 --amend 명령을 이용 [~/tmp/workspace/zeppelin]# commit --amend

commit에서 특정 파일 제거하기

가끔 프로젝트와 상관없는 파일이지만 ignore 처리되지 않아서 잘못된 파일이 commit이 추가되는 경우가 있습니다. 이 경우 수정된 commit에서 특정 파일 제거해야 하는데 다양한 방법이 있지만 필자의 경우 다음과 같은 방법을 사용합니다.

[~/tmp/workspace/zeppelin]# git checkout HEAD~ path/to/file [~/tmp/workspace/zeppelin]# commit --amend

amend 옵션 수행 시 editor에서 제거해야할 파일이 빠졌는지 확인할 수 있습니다.

여러 개의 commit 을 하나로 합치기

앞에서 설명한 다른 브랜치로 이동하기 위해 불필요하게 commit이 추가되거나 이미 push 이후에 코드 리뷰에 의해 코드를 수정하고 commit 한 경우 등 여러 개의 commit이 불필요하게 발생하고 이를 하나로 합쳐야 하는 경우가 있습습니다. 커밋을 합치는 것은 rebase와 squash 명령을 조합하여 처리할 수 있습니다.

rebase는 현재 브랜치의 base commit을 바꾸는 명령입니다. 예를 들어 다음 상황을 생각해볼 수 있습니다.

  • 현재 브랜치 생성 당시 최신 commit 번호가 C-10번
  • 로컬에서 개발 중에 다른 개발자에 의해 upstream의 master 브랜치에 여러 개의 commit이 merge됨
  • master의 현재 commit로그가 C-12번이 되었음

이 경우 현재 작업 중인 로컬 브랜치의 base를 C-10에서 C-12으로 변경할 필요가 있는 경우 다음과 같이 rebase 명령을 사용합니다.

#  현재 작업 중인 내용 보관 [~/tmp/workspace/zeppelin]# git stash # master 브랜치로 이동 [~/tmp/workspace/zeppelin]# git checkout master Switched to branch 'master' Your branch is up-to-date with 'upstream/master'. # master를 최신 상태로 유지 [~/tmp/workspace/zeppelin]# git pull upstream master remote: Counting objects: 85, done. First, rewinding head to replay your work on top of it... Fast-forwarded master to dcc1655e31db36704c46c00bf63720d02d1ecea3. # 작업 브랜치로 이동 [~/tmp/workspace/zeppelin]# git checkout ZEPPELIN-123-presto # master로 rebase [~/tmp/workspace/zeppelin]# git rebase master First, rewinding head to replay your work on top of it... Applying: commit-my-01 # 임시 저장된 파일 복구 [~/tmp/workspace/zeppelin]# git stash pop

이 글이 git의 각 명령에 대해서는 자세하게 다루고자 하는 글이 아니기 때문에 간단하게 여기까지만 설명하겠습니다. rebase와 merge는 git에서 가장 이해하기 어려운 개념중의 하나이면서 실제로는 자주 사용해야 하는 기능이기도 합니다. 여러 사이트나 문서를 봤지만 다음 사이트의 설명이 저는 가장 이해하기 쉬웠습니다.

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

이제 rebase의 개념에 대해서는 어느 정도 이해 했으니 원래 목표했던 여러개의 commit을 하나로 합치는 작업을 rebase 명령을 이용해서 해보겠습니다. rebase 명령의 옵션 중 "-i" 옵션은 대화형으로 rebase를 진행할 수 있도록 해줍니다.대화형이라는 의미는 앞의 rebase 예제에서는 사용자에게 아무런 선택 옵션을 주지 않고 바로 rebase 명령을 실행하는데 대화형의 경우 사용자가 옵션을 선택할 수 있는 에디터를 열어 줍니다. 다음과 같은 명령을 이용합니다.

[~/tmp/workspace/zeppelin]# git rebase -i HEAD~2

이 명령에서 HEAD~2의 의미는 현재 HEAD부터 최신 2개 커밋을 대상으로  rebase를 하겠다는 의미입니다. 하지만 우리는 2개의 commit를 하나의 commit으로 만드는 것이 목적이기 때문에 rebase 도중에 대화형으로 뭔가 처리를 하기 위해서 -i 옵션을 사용했습니다. 이렇게 하면 다음과 같이 에디터가 나타나고 이 에디터에는 2개의 커밋 로그가 표시됩니다. HEAD~3으로 하면 3개가 표시됩니다.

gti_rebase_squash

필자의 경우 에디터를 vi를 사용하고 있기 때문에 화면과 같이 vi 에디터가 나타납니다. 에디터의 아래 주석 부분에 보면 "Commands" 라고 나타나는게 있습니다. 대화형 rebase에서 사용할 수 있는 명령어 목록입니다. 명령의 입력은 각 commit 로그 앞에 있에 주면 되는데 기본적으로는 pick 으로 나타납니다. pick의 의미는 두개의 commit 모두 pick 하겠다는 의미입니다. 두개를 모두 pick 하게 되면 아무런 변경 사항이 없습니다.

각 명령의 의미를 읽어 보시면 "s, squash" 를 보면 "use commit, but meld into previous commit" 라고 되어 있습니다. 즉, 이전 commit을 섞는(meld) 명령입니다. 이 명령을 이용하면 두 개 이상의 commit를 하나의 commit으로 만들 수 있습니다. 위 예제에서는 두번째 commit 앞에 기존 "pick" 명령은 삭제하고 다음과 같이 "s" 또는 "squash"라고 입력합니다.

pick 678c025 ZEPPELIN-123-presto Add some comments in README.md s 0eb41b2 ZEPPELIN-123-presto Add some comments in README.md # Rebase 8b692f2..0eb41b2 onto 8b692f2 (2 command(s))

그리고 vi의 파일 저장 및 종료 명령을(wp) 이용하여 명령 옵션을 저장, 실행하면 바로 다음과 같은 입력 창이 하나 더 나타나게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# This is a combination of 2 commits.
# The first commit's message is:
ZEPPELIN-123-presto Add some comments in README.md
# This is the 2nd commit message:
ZEPPELIN-123-presto Add some comments in README.md
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Wed Sep 28 01:36:02 2016 +0900
#
# rebase in progress; onto 8b692f2
# You are currently editing a commit while rebasing branch 'ZEPPELIN-123-presto' on '8b692f2'.
#
# Changes to be committed:
# modified: README.md
#

여기서 중복되거나 불필요한 commit 로그는 삭제하고 필요한 commit 로그만 남겨 놓습니다.

1
2
3
4
5
6
7
8
9
10
11
12
ZEPPELIN-123-presto Add some comments in README.md
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Wed Sep 28 01:36:02 2016 +0900
#
# rebase in progress; onto 8b692f2
# You are currently editing a commit while rebasing branch 'ZEPPELIN-123-presto' on '8b692f2'.
#
# Changes to be committed:
# modified: README.md
#

그리고 이 에디터의 내용을 저장하고 종료(wq) 하면 다음과 같이 성공적으로 rebase 되었다는 메시지가 나타나고 git log 명령으로 하나의 commit 로그로 squash가 되었는지 확인할 수 있습니다.

[detached HEAD bcdb087] ZEPPELIN-123-presto Add some comments in README.md Date: Wed Sep 28 01:36:02 2016 +0900e -i HEAD~2 1 file changed, 2 insertions(+) Successfully rebased and updated refs/heads/ZEPPELIN-123-presto. [~/tmp/workspace/zeppelin]# git log commit bcdb08781b2e79b1429fddf52dfe2ffc35f5874c Author: Hyoungjun Kim <babokim@test_xxx.com> Date: Wed Sep 28 01:36:02 2016 +0900 ZEPPELIN-123-presto Add some comments in README.md

commit 로그를 예쁘게 만드는 작업이 가장 어려운 과정이라 할 수 있습니다. 메시지를 간결하고 이해하기 쉽게 작성해야 하고, 여러개의 지저분한 커밋을 하나로 합쳐야 하는 등 작업해야 할 것이 많습니다. commit 로그를 예쁘게 하는 것은 소스의 이력을 잘 관리하는 첫 걸음이기 때문에 오픈소스 진영에서는 아주 중요하게 생각하고 있습니다. 그리고 본인의 작업 내역이 외부에 대표되어 표현되는 부분이기 때문에 개발자 입장에서도 예쁘게 나타나도록 신경쓰는게 좋습니다.

개인 github 레포지토리로 push

이제 로컬에서 해야 하는 과정은 모두 끝났습니다. 로컬의 결과를 원본 레포지토리에 보내는 작업만 하면 되는데 안타깝게도 원본 레포지토리로 push 작업에 대한 권한이 없습니다(이 글의 첫부분에서 설명). push 권한이 있는 레포지토리는 원본으로 fork 한 본인 계정의 github 레포지토리 닙니다. 따라서 다음 명령으로 본인의 github 레포지토리에 push 합니다.

[~/tmp/workspace/zeppelin]# git remote -v babokim https://github.com/babokim/zeppelin (fetch) babokim https://github.com/babokim/zeppelin (push) upstream https://github.com/apache/zeppelin (fetch) upstream https://github.com/apache/zeppelin (push) [~/tmp/workspace/zeppelin]#git push babokim ZEPPELIN-123-presto

push 바로 뒤에 push 할 remote 레포지토리의 alias를 입력하고 그 다음에는 remote 레포지토리 브랜치를 입력합니다. remote에 이 브랜치가 없는 경우 새로 생성합니다. 처음 해당 브랜치로 push 하면 브랜치가 없기 때문에 생성하지만 다음과 같은 이유로 로컬에서 다시 작업하여 push 하는 경우 에러가 발생합니다.

  • push 하고 다음 절에 있는 Pull Request 전송
  • 코드 리뷰에서 수정 사항 발생
  • 로컬에서 다시 코드 수정 후 commit
  • commit을 하나로 합치기 위해 rebase 실행
  • 다시 push

이렇게 되면 기존 리모트 레포지토리에 이미 존재하는 브랜치에 push 하게 되는데 rebase 때문에 commit id가 변경되어 리모트에서는 push 처리를 받아주지 않습니다. 이 경우에는 다음과 같이 --force 옵션을 주어 강제로 push 시킵니다.

[~/tmp/workspace/zeppelin]#git push babokim ZEPPELIN-123-presto --force

push 명령 수행 시 주의사항은 현재 로컬의 브랜치가 어디 있는지 반드시 확인을 해야 합니다. 필자의 경우 보통은 다음과 같이 로컬 브랜치를 명시적으로 입력하는 방식을 좋아합니다.

[~/tmp/workspace/zeppelin]#git push babokim ZEPPELIN-123-presto:ZEPPELIN-123-presto

원본 프로젝트 레포지토리로 Pull Request

push 명령을 이용하여 본인의 github.com 계정의 fork 한 레포지토리로 작업 내용을 업로드 했습니다. 이제 최종 목적지인 원본 프로젝트 레포지토리로 보내야 하는데 원본 레포지토리로는 Pull Request를 생성하여 요청을 보냅니다.

github.com으로 로그인 한 다음 fork 한 레포지토리에서 다음과 같인 브랜치를 선택합니다.

git_select_branch

브랜치가 많은 경우 "Find or create a branch..." 라고 되어 있는 입력 필드에 브랜치 명을 입력하면 검색이 됩니다. 브랜치를 선택하여 옆에 "New Pull Request"라는 버튼이 생깁니다.

pull_request_button

이 버튼을 클릭하면 Pull Request 관련 정보를 입력하는 화면이 나타납니다. 이 화면에서는 어떤 파일이 수정되었으며 master와 비교하여 수정 사항이 어디인지 보여 줍니다.

git_pull_request_file_changes

자신이 수정한 파일보다 이 화면에서 수정이 되었다고 하는 파일이 훨씬 많이 보이는 경우는 대부분 원본 레포지토리의 master에서 rebase가 발생한 경우 입니다. 이 경우 다음 절에 설명할 conflict가 발생한 경우에 대한 처리와 동일한 방법으로 작업한 후 다시 Pull Request를 생성합니다. 이 화면에서 최종적으로 자신이 변경한 내역이 맞는지 반드시 확인하시기 바랍니다. 잘못된 파일이 들어오지 않았는지 본인의 계정 패스워드가 설정된 파일이 올라오지 않았는지등을 확인합니다.

모든게 정상이면 Pull Request 관련 정보를 입력합니다.

git_pull_request_description

위 예제의 경우 Zeppelin 예제입니다. Zeppelin의 경우 Pull Request 전송 시 Description을 위한  기본적인 템플릿을 제공하고 있습니다. 이런 경우 위 템플릿에 맞추어 내용을 입력합니다. 그리고 제목은 보통은 commit 로그와 동일하게 합니다.

마지막으로 아래 녹색 버튼인 "Create pull request" 버튼을 클릭하면 완료됩니다.

Conflict가 발생한 경우

Pull Request 전송 후 본인의 코드와 원본의 코드와 충돌이 발생하는 경우가 있습니다. Pull Request를 전송하는 시점에는 conflict가 발생하지 않았어도 다음과 같은 상황이 되면 발생할 수 있습니다.

  • 개발자 A가 PR-1 전송
  • 개발자 B가 PR-2 전송
    • 이 코드에 개발자 A가 수정한 코드와 동일한 부분을 수정
  • Committer가 개발자 B를 먼저 merge
  • 개발자 A의 PR-1은 conflict 발생

어떤 PR을 먼저 merge 할 것인지는 committer가 결정하기 때문에 PR-2의 코드 리뷰가 먼저 완료되면 Committer 는 PR-2 부터 먼저 merge 하게 됩니다. 따라서 conflict 상황은 언제든지 발생할 수 있다고 생각해야 합니다. conflict가 발생해도 github에서는 메일 등에서 알려주지 않기 때문에 자신의 PR 상태를 체크해보는 것이 좋습니다.

잘 운영되는 프로젝트는 Committer가 conflict 상황을 보게 되면 PR의 댓글로 conflict 가 발생했으니 rebase 후 다시 보내라고 코멘트를 달아 주기도 하지만 본인이 확인하는게 최선입니다. Conflict 가 발생하면 앞에서 살펴본 rebase를 이용하여 conflict를 해결합니다.

[~/tmp/workspace/zeppelin]# git checkout master [~/tmp/workspace/zeppelin]# git pull upstream [~/tmp/workspace/zeppelin]# git checkout ZEPPELIN-123-presto [~/tmp/workspace/zeppelin]# git rebase master Falling back to patching base and 3-way merge... Auto-merging test/Test.java CONFLICT (content): Merge conflict intest/Test.java When you have resolved this problem, run "git rebase --continue". If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort". # Conflict 발생한 부분을 해결한 다음 rebase continue [~/tmp/workspace/zeppelin]# git rebase --continue [~/tmp/workspace/zeppelin]# git push babokim ZEPPELIN-123-presto:ZEPPELIN-123-presto --force

Rebase 는 크게 문제되지 않지만  자동으로 merge 되지 않는 코드는 매뉴얼하게 merge를 해야 합니다. 저는 보통 IDE의 코드 merge를 하는데 IntelliJ나 RubyMine와 같은 JetBrain 제품을 이용합니다. 제가 JetBrain 제품을 이용하는 이유 중에 하나가 three-way merge  화면을 아주 직관적이면서도 심플하고 merge와 함께 바로 편집할 수 있는 막강한 기능을 제공하기 때문입니다.

Pull + Rebase를 한번에

위에서 보시는 것 처럼 master 브랜치로 이동해서 pull 명령을 이용하여 master를 최신 상태로 유지하고 다시 작업 브랜치로 이동해서 rebase 하는 등의 작업이 많이 있습니다. 필자의 경우 pull 명령 실행 시 rebase 도 같이 수행하도록 설정을 하여 사용하고 있습니다. 사용자 home 디렉토리의 ".gitconfig" 에 다음 내용을 추가하면 pull 실행 시 rebase도 같이 수행됩니다.

[~/tmp/workspace/zeppelin]# vi ~/.gitconfig # 다음 두 라인 추가 [pull] rebase = true # 이제 브랜치 이동 없이 바로 master pull & rebase 수행 [~/tmp/workspace/zeppelin]# git status On branch ZEPPELIN-123-presto [~/tmp/workspace/zeppelin]# git pull upstream/master

글을 마치며

지금까지 오픈소스 프로젝트에 git의 Pull Request를 어떻게 보내는지에 대해 살펴 보았습니다. 간단하게 쓰려고 시작한 글이 다소 긴 글이 되어 버렸습니다. git은 다양한 사용방법이 존재하고 문서마다 설명이 조금씩 다릅니다. rebase가 좋은지 merge가 좋은지 등에 대한 개인적이거나 프로젝트의 성향도 있습니다. 따라서 자신만의 사용방법을 익히고, 특정 프로젝트의 제약 사항을 확인하여 PR을 보내는 것이 좋습니다. 이 글에서 제시하는 방법 역시 여러 방법 중의 하나이며 제가 잘못 사용하고 있는 부분도 있을 수 있습니다. 더 좋고 효율작인 방법이 있으면 알려주시면 고맙겠습니다.

이 글이 오픈소스 프로젝트에 참여하는데 조금이나마 도움이 되었으면 합니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.