Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 103 – git에 관한 정보 모음

Author:
Oleg Broytman <phd at phdru.name>
Status:
Withdrawn
Type:
Informational
Created:
01-Jun-2015
Post-History:
12-Sep-2015

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP has been withdrawn.

×

이 PEP는 너무 일반적이며 Python 개발을 실제로 다루지 않습니다. 더 이상 갱신되지 않습니다.

내용은 Python Wiki로 옮겨졌습니다. 앞으로의 갱신은 위키에서 하십시오.

초록

이 정보성 PEP는 git에 관한 정보를 모읍니다. 물론 git에 관한 문서는 많이 있으므로, 이 PEP는 더 복잡한(그리고 Python 개발과 더 관련된) 문제, 시나리오, 예제에 집중합니다.

앞으로 이 PEP를 확장하여 Mercurial과 git 시나리오의 대응 관계에 관한 정보를 모아, Python 개발을 Mercurial에서 git으로 이전하는 데 도움을 줄 계획입니다.

이 PEP의 작성자는 현재 Python 개발을 Mercurial에서 git으로 이전하는 것에 관한 프로세스 PEP를 작성할 계획이 없습니다.

문서

Git에는 온라인과 오프라인 모두에서 많은 문서가 함께 제공됩니다.

입문자를 위한 문서

Git 튜토리얼: 1부, 2부.

Git User’s manual. Everyday GIT With 20 Commands Or So. Git workflows.

고급 문서

Git Magic, 여러 언어로 번역되어 있습니다.

Pro Git. git에 관한 바로 그 책입니다. Amazon에서 구입하거나 PDF, mobi, ePub 형식으로 내려받으십시오. 여러 언어로 번역되어 있습니다. 러시아어 번역본은 GArik에서 내려받으십시오.

Git Wiki.

Git Buch (독일어).

오프라인 문서

Git에는 도움말이 내장되어 있습니다: git help $TOPIC을 실행하십시오. 예를 들어 git help git 또는 git help help를 실행하십시오.

빠른 시작

내려받기와 설치

Unix 사용자: 패키지 관리자를 사용하여 내려받고 설치하십시오.

Microsoft Windows: git-for-windows를 내려받으십시오.

MacOS X: XCode와 함께 설치되는 git을 사용하거나, MacPorts 또는 git-osx-installer에서 내려받거나, Homebrew로 git을 설치하십시오: brew install git.

git-cola (저장소)는 Python으로 작성되었고 GPL 라이선스가 적용된 Git GUI입니다. Linux, Windows, MacOS X를 지원합니다.

TortoiseGit은 TortoiseSVN을 기반으로 한 Git용 Windows 셸 인터페이스이며, 오픈 소스입니다.

초기 설정

이 간단한 코드는 문서에 자주 나오지만, 중요하므로 여기서 다시 한번 반복합니다. Git은 모든 커밋에 작성자(author)와 커미터(committer)의 이름/이메일을 저장하므로, 실제 이름과 선호하는 이메일을 설정하십시오:

$ git config --global user.name "User Name"
$ git config --global user.email user.name@example.org

이 PEP의 예제

이 PEP의 git 명령 예제는 다음과 같은 방식을 따릅니다. 사용자인 여러분이 origin이라는 업스트림 원격 저장소가 있는 python이라는 로컬 저장소에서 작업한다고 가정합니다. 로컬 저장소에는 v1과 master라는 두 브랜치가 있습니다. 대부분의 예제에서 현재 체크아웃된 브랜치는 master입니다. 즉, 여러분이 다음과 같은 작업을 했다고 가정합니다:

$ git clone https://git.python.org/python.git
$ cd python
$ git branch v1 origin/v1

첫 번째 명령은 원격 저장소를 로컬 디렉터리 python에 복제하고, 새 로컬 브랜치 master를 만들고, remotes/origin/master를 그 업스트림 원격 추적 브랜치로 설정한 다음, 작업 디렉터리에 체크아웃합니다.

마지막 명령은 새 로컬 브랜치 v1을 만들고 remotes/origin/v1을 그 업스트림 원격 추적 브랜치로 설정합니다.

다음 명령으로도 같은 결과를 얻을 수 있습니다:

$ git clone -b v1 https://git.python.org/python.git
$ cd python
$ git checkout --track origin/master

마지막 명령은 새 로컬 브랜치 master를 만들고, remotes/origin/master를 그 업스트림 원격 추적 브랜치로 설정한 다음, 작업 디렉터리에 체크아웃합니다.

브랜치와 브랜치

Git 용어는 다소 오해를 부를 수 있습니다. 예를 들어 “브랜치(branch)”라는 용어를 살펴봅시다. git에서 이 용어는 두 가지 의미를 가집니다. 브랜치는 (병합을 포함할 수도 있는) 커밋들의 방향이 있는 선입니다. 또한 브랜치는 커밋들의 선에 할당된 레이블 또는 포인터이기도 합니다. 커밋에 대해 이야기하는 것인지, 커밋의 레이블에 대해 이야기하는 것인지 구별하는 것이 중요합니다. 커밋들의 선은 그 자체로는 이름이 없으며, 보통 길어지거나 병합될 뿐입니다. 반면에 레이블은 자유롭게 생성, 이동, 이름 변경, 삭제할 수 있습니다.

원격 저장소와 원격 브랜치

원격 추적 브랜치는 로컬 저장소에 있는 브랜치(커밋을 가리키는 포인터)입니다. 원격 추적 브랜치는 git이(그리고 여러분이) 어떤 브랜치와 커밋을 어떤 원격 저장소에서 풀했고 어떤 원격 저장소로 푸시했는지 기억하기 위해 존재합니다(여러 원격 저장소에서 풀하고 여러 원격 저장소로 푸시할 수 있습니다). 원격 추적 브랜치는 remotes/$REMOTE 네임스페이스 아래에 있습니다. 예를 들면 remotes/origin/master입니다.

원격 추적 브랜치의 상태를 보려면 다음을 실행하십시오:

$ git branch -rv

커밋을 가리키는 로컬 브랜치와 원격 추적 브랜치(그리고 태그)를 보려면:

$ git log --decorate

원격 추적 브랜치에서 직접 개발하는 일은 절대 없습니다. 원격 브랜치를 업스트림으로 삼는 로컬 브랜치를 만들고, 그 로컬 브랜치에서 개발합니다. 푸시할 때 git은 커밋을 원격 저장소로 푸시하고 원격 추적 브랜치를 갱신합니다. 풀할 때 git은 원격 저장소에서 커밋을 가져오고 원격 추적 브랜치를 갱신한 다음, 로컬 브랜치를 빨리 감기(fast-forward)하거나 병합하거나 리베이스합니다.

다음과 같이 처음으로 클론하면:

$ git clone -b v1 https://git.python.org/python.git

git은 원격 저장소 https://git.python.org/python.git을 python 디렉터리로 클론하고, origin이라는 이름의 원격을 만들고, 원격 추적 브랜치를 만듭니다. 이어서 로컬 브랜치 v1을 만들어 업스트림 remotes/origin/v1 브랜치를 추적하도록 설정한 다음, v1을 작업 디렉터리로 체크아웃합니다.

git status --branch나 git branch --verbose 같은 일부 명령은 로컬 브랜치와 원격 브랜치 사이의 차이를 보고합니다. 이 명령들은 로컬 저장소에 있는 원격 추적 브랜치와만 비교합니다. 그리고 그 원격 추적 브랜치의 상태는 최신이 아닐 수 있다는 점을 기억하십시오. 원격 추적 브랜치를 갱신하는 방법은 두 가지입니다. 원격 저장소에서 커밋을 가져와 병합(또는 리베이스)하거나, 로컬 브랜치는 그대로 두고 원격 추적 브랜치만 갱신합니다.

로컬 브랜치와 원격 추적 브랜치 갱신하기

로컬 브랜치는 갱신하지 않고 원격 추적 브랜치만 갱신하려면 git remote update [$REMOTE...]를 실행하십시오. 예를 들면 다음과 같습니다.:

$ git remote update
$ git remote update origin

페치와 풀

다음 명령과

$ git fetch $REMOTE $BRANCH

다음 명령 사이에는 큰 차이가 있습니다.

$ git fetch $REMOTE $BRANCH:$BRANCH

첫 번째 명령은 $REMOTE 저장소의 $BRANCH라는 브랜치에서 여러분의 저장소에 없는 커밋을 가져오고, 원격 추적 브랜치를 갱신하며, 헤드 커밋의 id(해시)를 .git/FETCH_HEAD 파일에 남깁니다.

두 번째 명령은 $REMOTE 저장소의 $BRANCH라는 브랜치에서 여러분의 저장소에 없는 커밋을 가져오고, 로컬 브랜치 $BRANCH와 그 업스트림 원격 추적 브랜치를 모두 갱신합니다. 하지만 빨리 감기가 불가능한 경우에는 브랜치 갱신을 거부합니다. 또한 현재 브랜치(현재 체크아웃되어 있어 HEAD가 가리키는 브랜치)의 갱신도 거부합니다.

첫 번째 명령은 git pull이 내부적으로 사용합니다.

$ git pull $REMOTE $BRANCH

위 명령은 다음과 동등합니다.

$ git fetch $REMOTE $BRANCH
$ git merge FETCH_HEAD

물론 이 경우 $BRANCH는 현재 브랜치여야 합니다. 다른 브랜치를 현재 브랜치에 병합하려면, 먼저 현재 브랜치가 아닌 그 브랜치를 갱신한 다음 병합하십시오.:

$ git fetch origin v1:v1  # Update v1
$ git pull --rebase origin master  # Update the current branch master
                                   # using rebase instead of merge
$ git merge v1

하지만 v1에 아직 푸시하지 않은 커밋이 있다면 절차가 조금 더 복잡해집니다. Git은 빨리 감기가 불가능한 브랜치의 갱신을 거부합니다. 강제 풀은 하지 않는 것이 좋습니다. 강제 풀을 하면 푸시하지 않은 커밋이 지워져 복구해야 하기 때문입니다. 따라서 v1을 리베이스해야 하지만, 현재 브랜치가 아닌 브랜치는 리베이스할 수 없습니다. 그러므로 병합하기 전에 v1을 체크아웃하고 리베이스하십시오.:

$ git checkout v1
$ git pull --rebase origin v1
$ git checkout master
$ git pull --rebase origin master
$ git merge v1

git이 여러 브랜치나 모든 브랜치를 한 번에 페치/풀하도록 설정할 수 있습니다. 그러면 다음과 같이 간단히 실행할 수 있습니다.

$ git pull origin

또는 다음과 같이 실행할 수도 있습니다.

$ git pull

페치/풀의 기본 원격 저장소는 origin입니다. 페치할 참조의 기본 집합은 매칭 알고리즘으로 정해집니다. git은 양쪽에서 이름이 같은 모든 브랜치를 가져옵니다.

푸시

푸시는 조금 더 간단합니다. 명령은 push 하나뿐입니다. 다음을 실행하면

$ git push origin v1 master

git은 로컬 v1을 원격 v1으로, 로컬 master를 원격 master로 푸시합니다. 다음과 같습니다:

$ git push origin v1:v1 master:master

Git은 커밋을 원격 저장소로 푸시하고 원격 추적 브랜치를 갱신합니다. Git은 fast-forward할 수 없는 커밋의 푸시를 거부합니다. 그래도 강제 푸시할 수는 있지만, 다음을 기억하십시오. 자신의 저장소에는 강제 푸시해도 되지만 공개 저장소나 공유 저장소에는 강제 푸시하지 마십시오. git이 fast-forward할 수 없는 커밋의 푸시를 거부한다면, 원격 저장소에서 커밋을 fetch하여 병합한(또는 fetch한 커밋 위에 자신의 커밋을 리베이스한) 다음 푸시하는 편이 좋습니다. 무엇을 하는지, 왜 하는지 알고 있을 때만 강제 푸시하십시오. 아래의 Commit editing and caveats 절을 참조하십시오.

브랜치 몇 개 또는 모든 브랜치를 한꺼번에 푸시하도록 git을 설정할 수 있으므로, 간단히 다음을 실행하면 됩니다

$ git push origin

또는 다음과 같이 실행할 수도 있습니다

$ git push

푸시의 기본 원격 저장소는 origin입니다. git 2.0 이전 버전에서 푸시할 참조의 기본 집합은 matching 알고리즘으로 계산됩니다. git은 양쪽에 같은 이름으로 존재하는 모든 브랜치를 푸시합니다. git 2.0 이상에서 푸시할 기본 참조 집합은 simple 알고리즘으로 계산됩니다. git은 현재 브랜치를 그 업스트림인 @{upstream}으로 다시 푸시합니다.

git 2.0 이전 버전이 새 동작을 따르도록 설정하려면 다음을 실행하십시오:

$ git config push.default simple

git 2.0 이상이 이전 동작을 따르도록 설정하려면 다음을 실행하십시오:

$ git config push.default matching

원격 non-bare 저장소에서 현재 체크아웃된 브랜치에는 git이 푸시를 허용하지 않습니다. git은 원격 작업 디렉터리를 갱신하기를 거부합니다. 푸시는 반드시 bare 저장소에만 해야 합니다. non-bare 저장소에 대해서는 git이 pull 기반 워크플로를 선호합니다.

원격 호스트에 코드를 배포하려는데 푸시만 사용할 수 있다면(작업 컴퓨터가 방화벽 뒤에 있어 그곳에서 pull할 수 없는 경우), 저장소 두 개를 사용하여 두 단계로 진행합니다. 작업 컴퓨터에서 원격 호스트의 bare 저장소로 푸시한 다음, 원격 호스트에 ssh로 접속하여 bare 저장소에서 non-bare 배포 저장소로 pull합니다.

이는 git 2.3에서 바뀌었지만, 주의 사항은 블로그 글을 참조하십시오. 2.4에서는 push-to-deploy 기능이 더욱 개선되었습니다.

태그

Git은 fetch/pull 중에 가져오는 커밋을 가리키는 태그를 자동으로 가져옵니다. 모든 태그(와 태그가 가리키는 커밋)를 가져오려면 git fetch --tags origin을 실행하십시오. 특정 태그 몇 개만 가져오려면 다음과 같이 명시적으로 지정하여 가져오십시오:

$ git fetch origin tag $TAG1 tag $TAG2...

예를 들면 다음과 같습니다:

$ git fetch origin tag 1.4.2
$ git fetch origin v1:v1 tag 2.1.7

Git은 태그를 자동으로 푸시하지 않습니다. 덕분에 비공개 태그를 가질 수 있습니다. 태그를 푸시하려면 태그를 명시적으로 나열하십시오:

$ git push origin tag 1.4.2
$ git push origin v1 master tag 2.1.7

또는 모든 태그를 한 번에 푸시하십시오:

$ git push --tags origin

태그를 공개한 후에는 git tag -f로 태그를 옮기거나 git tag -d로 태그를 삭제하지 마십시오.

비공개 정보

클론/페치/풀/푸시할 때 git은 데이터베이스 객체(커밋, 트리, 파일, 태그)와 심볼릭 참조(브랜치와 경량 태그)만 복사합니다. 그 밖의 모든 것은 저장소에 비공개로 남으며 클론되거나 갱신되거나 푸시되지 않습니다. 여러분의 설정, 여러분의 훅, 여러분의 비공개 exclude 파일이 여기에 해당합니다.

훅을 배포하려면 훅을 작업 트리에 복사하고 add, 커밋, push한 다음 팀원들에게 업데이트 후 훅을 수동으로 설치하도록 안내하십시오.

커밋 편집과 주의 사항

공개된(푸시된) 커밋을 편집하지 말라는 경고는 문서에도 나와 있지만, 매우 중요하므로 여기서 다시 한번 반복합니다.

강제 푸시에서 복구할 수는 있지만 팀 전체에게 몹시 골치 아픈 일입니다. 강제 푸시는 피하십시오.

아직 공개되지 않은 커밋을 확인하려면 브랜치의 헤드를 해당 브랜치의 업스트림 원격 추적 브랜치와 비교하십시오:

$ git log origin/master..  # from origin/master to HEAD (of master)
$ git log origin/v1..v1  # from origin/v1 to the head of v1

업스트림 원격 추적 브랜치가 있는 모든 브랜치에 대해 git은 @{upstream}(줄여서 @{u})이라는 별칭을 유지하므로, 위의 명령은 다음과 같이 쓸 수 있습니다:

$ git log @{u}..
$ git log v1@{u}..v1

모든 브랜치의 상태를 확인하려면:

$ git branch -avv

로컬 브랜치의 상태를 원격 저장소와 비교하려면:

$ git remote show origin

업스트림 리베이스에서 복구하는 방법을 읽어 보십시오. 이 내용은 git help rebase에 있습니다.

한편, 커밋 편집을 너무 두려워하지는 마십시오. 아직 푸시하지 않은 커밋은 안전하게 편집하고, 순서를 바꾸고, 제거하고, 합치고, 나눌 수 있습니다. 심지어 커밋을 자신의 (백업) 저장소에 푸시한 뒤 나중에 편집하고, 편집한 커밋을 강제 푸시하여 이미 푸시된 것을 대체할 수도 있습니다. 커밋이 공개 저장소나 공유 저장소에 올라가기 전까지는 문제가 되지 않습니다.

되돌리기

무슨 일이 있어도 당황하지 마십시오. git에서는 거의 모든 것을 되돌릴 수 있습니다.

git checkout: 파일 내용 복원

예를 들어 git checkout을 사용하면 파일의 내용을 특정 커밋의 내용으로 복원할 수 있습니다. 다음과 같이 합니다:

git checkout HEAD~ README

이 명령은 README 파일의 내용을 현재 브랜치에서 마지막 커밋 바로 이전 커밋의 상태로 복원합니다. 기본적으로 커밋 ID는 그냥 HEAD입니다. 즉, git checkout README는 README를 최신 커밋의 상태로 복원합니다.

(커밋 안에 있는 파일의 내용을 보려면 git checkout을 사용하지 말고 git cat-file -p를 사용하십시오. 예: git cat-file -p HEAD~:path/to/README).

git reset: (푸시하지 않은) 커밋 제거하기

git reset은 현재 브랜치의 헤드를 옮깁니다. 헤드는 어떤 커밋이든 가리키도록 옮길 수 있지만, 브랜치 맨 위에서 커밋 하나 또는 몇 개(가급적 푸시하지 않은 것)를 제거하는 데 자주 사용됩니다. 즉, (푸시하지 않은) 커밋 몇 개를 되돌리기 위해 브랜치를 뒤로 옮기는 데 사용됩니다.

git reset에는 soft, hard, mixed의 세 가지 동작 모드가 있습니다. 기본값은 mixed입니다. ProGit은 그 차이를 아주 명확하게 설명합니다. bare 저장소에는 인덱스나 작업 트리가 없으므로 bare 저장소에서는 soft 리셋만 가능합니다.

스테이징 취소하기

경로를 하나 이상 지정한 mixed 모드 리셋은 변경 사항의 스테이징을 취소하는 데 사용할 수 있습니다. 즉, 커밋하려고 git add로 추가한 변경 사항을 인덱스에서 제거할 수 있습니다. 스테이징 취소와 그 밖의 되돌리기 요령에 대한 자세한 내용은 The Book을 참고하십시오.

git reflog: 참조 로그

git reset으로 커밋을 제거하거나 브랜치의 헤드를 옮기는 것은 위험하게 들리며, 실제로도 위험합니다. 하지만 되돌리는 방법이 있습니다. 원래 커밋으로 다시 리셋하면 됩니다. Git은 커밋을 즉시 제거하지 않습니다. 참조되지 않는 커밋(git 용어로는 “dangling 커밋”이라고 합니다)은 한동안(기본값은 2주) 데이터베이스에 남아 있으므로, 그 커밋으로 다시 리셋하거나 원래 커밋을 가리키는 새 브랜치를 만들 수 있습니다.

git commit, git checkout, git fetch, git pull, git rebase, git reset 등으로 브랜치의 헤드가 움직일 때마다 git은 참조 로그(줄여서 reflog)를 저장합니다. 헤드가 움직일 때마다 git은 헤드가 어디에 있었는지를 저장합니다. git reflog 명령으로 이 로그를 확인(및 조작)할 수 있습니다.

git은 모든 브랜치의 헤드 이동 외에도 HEAD의 이동을 저장합니다. HEAD는 (보통) 현재 브랜치를 가리키는 심볼릭 참조입니다. HEAD는 git checkout $BRANCH로 바뀝니다.

기본적으로 git reflog는 HEAD의 이동을 보여 줍니다. 즉, 이 명령은 git reflog HEAD와 같습니다. 브랜치 헤드의 이동을 보려면 git reflog $BRANCH 명령을 사용하십시오.

따라서 git reset을 되돌리려면 git reflog에서 원래 커밋을 찾고, git show나 git log로 확인한 다음 git reset $COMMIT_ID를 실행하십시오. Git은 브랜치 헤드의 이동을 reflog에 저장하므로, 나중에 그 되돌리기를 다시 되돌릴 수도 있습니다.

더 복잡한 상황에서는 브랜치의 헤드를 리셋하면서 일부 커밋을 함께 옮기고 싶을 수 있습니다. 그런 커밋은 새 브랜치로 체리픽하십시오. 예를 들어 master 브랜치를 원래 커밋으로 리셋하되 현재 브랜치에서 만든 커밋 두 개를 보존하고 싶다면 다음과 같이 하십시오:

$ git branch save-master  # create a new branch saving master
$ git reflog  # find the original place of master
$ git reset $COMMIT_ID
$ git cherry-pick save-master~ save-master
$ git branch -D save-master  # remove temporary branch

git revert: 커밋 되돌리기

git revert는 커밋 하나 또는 여러 개를 되돌립니다. 즉, 주어진 커밋의 효과를 되돌리는 새 커밋을 하나 또는 여러 개 만듭니다. 이것은 공개된 커밋을 되돌리는 유일한 방법입니다(git commit --amend, git rebase, git reset은 fast-forward할 수 없는 방식으로 브랜치를 바꾸므로 푸시하지 않은 커밋에만 사용해야 합니다).

머지 커밋을 되돌리는 데에는 문제가 있습니다. git revert는 머지 커밋이 만든 코드는 되돌릴 수 있지만 머지했다는 사실 자체는 되돌릴 수 없습니다. 잘못된 머지를 되돌리는 방법 논의를 참고하십시오.

되돌릴 수 없는 한 가지

무엇을 되돌리든 되돌릴 수 없는 것이 하나 있습니다. 바로 덮어쓴 커밋되지 않은 변경 사항입니다. 커밋되지 않은 변경 사항은 git에 속하지 않으므로 git이 이를 보존해 줄 수 없습니다.

커밋되지 않은 변경 사항을 덮어쓰는 명령을 실행하려 하면 git은 대부분 경고합니다. Git은 git checkout으로 브랜치를 전환하는 것을 허용하지 않습니다. 작업 트리가 깨끗하지 않은 상태에서 리베이스하려고 하면 이를 막습니다. 커밋되지 않은 파일 위로 새 커밋을 풀하는 것을 거부합니다.

하지만 바로 그런 일, 즉 작업 트리의 파일을 덮어쓰는 일을 하는 명령도 있습니다. git checkout $PATHs나 git reset --hard 같은 명령은 커밋되지 않은 변경 사항이 있는 파일도 아무 경고 없이 덮어씁니다.

이 점을 염두에 두면 “일찍 커밋하고, 자주 커밋하라”는 입장을 이해할 수 있습니다. 가능한 한 자주 커밋하십시오. 에디터나 IDE에서 저장할 때마다 커밋하십시오. 푸시하기 전에 커밋을 편집할 수 있습니다. 커밋 메시지를 수정하고, 커밋을 변경하고, 순서를 바꾸고, 합치고, 나누고, 제거할 수 있습니다. 하지만 변경 사항은 git 데이터베이스에 저장하십시오. 변경 사항을 커밋하거나, 적어도 git stash로 스태시해 두십시오.

병합할 것인가, 리베이스할 것인가?

인터넷에는 “병합할 것인가, 리베이스할 것인가?”라는 주제를 두고 열띤 논쟁이 가득합니다. 그 대부분은 무의미합니다. 브랜치가 많은 크고 복잡한 프로젝트에서 대규모 팀이 DVCS를 사용한다면 병합을 피할 방법은 전혀 없습니다. 따라서 질문은 “리베이스를 사용할 것인가, 사용한다면 언제 사용할 것인가?”로 좁혀집니다. 공개된 커밋은 리베이스하지 말 것이 강력히 권장된다는 점을 고려하면 질문은 더욱 좁혀집니다. “푸시하지 않은 커밋에 리베이스를 사용할 것인가?”

그 작은 질문은 팀이 결정할 몫입니다. 선형 히스토리의 아름다움을 유지하려면 풀할 때 리베이스를 사용하는 것이 좋습니다. 즉, git pull --rebase를 실행하거나, 더 나아가 새 브랜치마다 리베이스가 자동으로 설정되도록 구성하십시오:

$ git config branch.autosetuprebase always

그리고 기존 브랜치에도 리베이스를 설정하십시오:

$ git config branch.$NAME.rebase true

예를 들어:

$ git config branch.v1.rebase true
$ git config branch.master.rebase true

그러고 나면 git pull origin master는 git pull --rebase origin master와 동일해집니다.

메인라인 브랜치를 갱신할 때는 리베이스를 사용하고, 새 커밋은 별도의 기능 브랜치나 토픽 브랜치에서 만드는 것이 좋습니다. 토픽 브랜치가 준비되면 메인라인에 병합하십시오. 많은 충돌을 한꺼번에 해결하는 지루한 작업을 피하려면, 때때로 토픽 브랜치를 메인라인에 병합한 뒤 다시 토픽 브랜치로 전환하여 작업을 계속할 수 있습니다. 전체 워크플로는 대략 다음과 같습니다:

$ git checkout -b issue-42  # create a new issue branch and switch to it
    ...edit/test/commit...
$ git checkout master
$ git pull --rebase origin master  # update master from the upstream
$ git merge issue-42
$ git branch -d issue-42  # delete the topic branch
$ git push origin master

토픽 브랜치를 삭제하면 레이블만 제거되고 커밋은 데이터베이스에 그대로 남으며, 이 커밋들은 이제 master에 병합되어 있습니다:

o--o--o--o--o--M--< master - the mainline branch
    \         /
     --*--*--*             - the topic branch, now unnamed

작은 토픽 브랜치들로 브랜치 네임스페이스가 어지러워지지 않도록 토픽 브랜치를 삭제합니다. 어떤 이슈를 수정했는지, 어떤 기능을 구현했는지에 대한 정보는 커밋 메시지에 담겨 있어야 합니다.

하지만 오래 유지되는 병합 브랜치에서는 그 정도의 적은 리베이스조차 지나치게 클 수 있습니다. v1 브랜치와 master 브랜치 모두에서 작업하면서 v1을 master에 정기적으로 병합한다고 가정해 봅시다. 시간이 지나면 master에는 병합 커밋과 비병합 커밋이 많이 쌓이게 됩니다. 그런 다음 완료한 작업을 공유 저장소에 푸시하려고 하는데, 누군가 이미 v1에 커밋 몇 개를 푸시해 두었다는 것을 알게 됩니다. 이제 똑같이 나쁜 두 가지 대안 중 하나를 골라야 합니다. v1을 페치하고 리베이스한 다음 master에서 한 모든 작업을 다시 만들거나(master를 origin으로 리셋하고, v1을 병합하고, 이전 master의 비병합 커밋을 모두 체리픽), 새 v1을 병합하고 선형 이력의 아름다움을 잃는 것입니다.

널 병합

Git에는 Python 코어 개발자들이 “null-merge”라고 부르는 것을 위한 내장 병합 전략이 있습니다.:

$ git merge -s ours v1  # null-merge v1 into master

브랜치 모델

Git은 브랜치 생성과 병합에 관해 특정한 개발 모델을 전제하지 않습니다. 어떤 프로젝트는 패치를 가장 오래된 브랜치에서 가장 새로운 브랜치로 올려 보내는 방식을 선호하고, 어떤 프로젝트는 커밋을 거꾸로 체리픽하는 방식을 선호하며, 어떤 프로젝트는 스쿼시(여러 커밋을 하나로 합치기)를 사용합니다. 무엇이든 가능합니다.

시작하기 좋은 예가 몇 가지 있습니다. git help workflows는 git 개발자들이 직접 git을 어떻게 개발하는지 설명합니다.

ProGit 책에는 여러 프로젝트에서의 브랜치 관리를 다루는 장이 몇 개 있습니다. Git Branching - Branching Workflows와 Distributed Git - Contributing to a Project입니다.

Vincent Driessen이 쓴 잘 알려진 글 A successful Git branching model도 있습니다. 이 글은 메인라인 브랜치, 토픽 브랜치, 버그 수정 브랜치를 만들고 관리하는 매우 상세한 규칙들을 권장합니다. 저자는 이 모델을 지원하기 위해 git flow 확장을 구현했습니다.

고급 설정

줄 끝

Git에는 줄 끝 방식이 서로 다른 플랫폼 사이에서 줄 끝을 처리하는 메커니즘이 내장되어 있습니다. git이 CRLF 변환을 하도록 하려면 .gitattributes를 사용하여 파일에 text 속성을 지정하십시오. 특정한 줄 끝을 가져야 하는 파일에는 eol 속성을 지정하십시오. 바이너리 파일의 속성은 당연히 binary입니다.

예를 들면 다음과 같습니다:

$ cat .gitattributes
*.py text
*.txt text
*.png binary
/readme.txt eol=CRLF

git이 파일에 어떤 속성을 사용하는지 확인하려면 git check-attr 명령을 사용하십시오. 예를 들면 다음과 같습니다:

$ git check-attr -a -- \*.py

유용한 자료

GitAlias (저장소)는 방대한 별칭 모음입니다. 자주 사용하는 명령에 대한 별칭을 신중하게 골라 두면 키 입력을 크게 줄일 수 있습니다!

GitIgnore와 https://github.com/github/gitignore 는 온갖 IDE와 프로그래밍 언어를 위한 .gitignore 파일 모음입니다. Python도 포함되어 있습니다!

pre-commit (저장소)은 다중 언어 커밋 전(pre-commit) 훅을 관리하고 유지하기 위한 프레임워크입니다. 이 프레임워크는 Python으로 작성되었으며 여러 프로그래밍 언어를 위한 플러그인을 많이 갖추고 있습니다.

고급 주제

스테이징 영역

스테이징 영역(인덱스 또는 캐시라고도 합니다)은 git만의 고유한 특징입니다. 스테이징 영역은 git이 패치를 커밋하기 전에 모아 두는 곳입니다. 패치를 모으는 단계와 커밋하는 단계가 분리되어 있어서 git은 매우 유용한 기능을 제공합니다. 커밋하기 전에 모아 둔 패치를 검토할 수 있고, 일부 헝크를 제거하거나 새 헝크를 추가한 뒤 다시 검토하는 식으로 패치를 편집할 수도 있습니다.

인덱스에 파일을 추가하려면 git add를 사용하십시오. 커밋하기 전에 패치를 모은다는 것은 새(추적되지 않는) 파일을 추가할 때뿐만 아니라 모든 변경 사항마다 이 작업을 해야 한다는 뜻입니다. 검토 없이 모든 것을 커밋하고 싶을 때 커밋을 간단히 하려면 git commit --all(또는 그냥 -a)을 실행하십시오. 이 명령은 변경된 모든 추적 파일을 인덱스에 추가한 다음 커밋합니다. 인덱스에 모아 둔 패치와 관계없이 파일 하나 또는 여러 개를 커밋하려면 git commit [--only|-o] -- $FILE...를 실행하십시오.

패치의 헝크를 인덱스에 추가하려면 git add --patch(또는 그냥 -p)를 사용하십시오. 모아 둔 파일을 인덱스에서 제거하려면 git reset HEAD -- $FILE...를 사용하십시오. 모아 둔 헝크를 추가하거나 확인하거나 제거하려면 git add --interactive(-i)를 사용하십시오.

인덱스와 마지막 커밋 사이의 차이(즉, 모아 둔 패치)를 보려면 git diff --cached를 사용하십시오. 작업 트리와 인덱스 사이의 차이(즉, 아직 모으지 않은 패치)를 보려면 그냥 git diff를 사용하십시오. 작업 트리와 마지막 커밋 사이의 차이(즉, 모아 둔 패치와 아직 모으지 않은 패치 모두)를 보려면 git diff HEAD를 실행하십시오.

Git Wiki의 WhatIsTheIndex와 IndexCommandQuickref를 참고하십시오.

루트

Git은 명령을 실행하기 전에 항상 루트(.git 하위 디렉터리가 있는 프로젝트의 최상위 디렉터리)로 이동합니다. 하지만 Git은 이동하기 전의 현재 디렉터리를 기억합니다. 일부 프로그램은 현재 디렉터리를 고려합니다. 예를 들어, git status는 변경된 파일과 알 수 없는 파일의 경로를 현재 디렉터리를 기준으로 보여 줍니다. git grep은 현재 디렉터리 아래를 검색합니다. git apply는 패치의 헝크 중 현재 디렉터리 아래의 파일을 건드리는 헝크만 적용합니다.

그러나 대부분의 명령은 루트에서 실행되며 현재 디렉터리를 무시합니다. 예를 들어, 작업 트리가 두 개 있는데 하나는 v1 브랜치용이고 다른 하나는 master용이라고 가정해 봅시다. 두 번째 작업 트리 안의 하위 디렉터리에서 v1을 병합하려면 최상위 디렉터리에 있는 것처럼 명령을 작성해야 합니다. project-v1과 project라는 두 작업 트리를 예로 들어 봅시다:

$ cd project/subdirectory
$ git fetch ../project-v1 v1:v1
$ git merge v1

명령을 루트가 아닌 하위 디렉터리에서 실행하는데도 git fetch ../project-v1 v1:v1의 경로가 ../../project-v1이 아니라 ../project-v1이라는 점에 유의하십시오.

ReReRe

Rerere는 반복되는 병합 충돌을 해결하는 데 도움을 주는 메커니즘입니다. 병합 충돌이 되풀이되는 가장 흔한 원인은 메인라인에 병합된 뒤 병합 커밋이 제거되는 토픽 브랜치입니다. 이런 작업은 토픽 브랜치를 테스트하고 rerere를 학습시키기 위해 자주 수행됩니다. 병합 커밋을 제거하는 것은 깔끔한 선형 히스토리를 유지하고 토픽 브랜치를 마지막 병합 커밋 하나만으로 마무리하기 위해서입니다.

Rerere는 성공한 커밋 전후의 트리 상태를 기억하는 방식으로 동작합니다. 그래서 같은 파일에서 충돌이 다시 발생하면 rerere가 자동으로 해결할 수 있습니다.

Rerere는 git rerere 명령으로 수동으로 사용할 수도 있지만, 대부분은 자동으로 사용됩니다. 작업 트리에서 다음 명령으로 rerere를 활성화하십시오:

$ git config rerere.enabled true
$ git config rerere.autoupdate true

rerere를 전역으로 켤 필요는 없습니다. bare 저장소나 단일 브랜치 저장소에서는 rerere가 필요하지 않으며, 병합을 자주 수행하고 병합 충돌을 해결하는 저장소에서만 rerere가 필요합니다.

The Book의 Rerere를 참조하십시오.

데이터베이스 유지 관리

Git 객체 데이터베이스와 .git 아래의 다른 파일/디렉터리는 주기적인 유지 관리와 정리가 필요합니다. 예를 들어, 커밋을 편집하면 참조되지 않는 객체(git 용어로는 dangling 객체)가 남는데, DB에 찌꺼기가 쌓이지 않도록 이러한 객체는 정리(prune)해야 합니다. 유지 관리에는 git gc 명령을 사용합니다. Git은 빠른 유지 관리를 위해 일부 명령을 실행할 때 git gc --auto를 자동으로 함께 실행합니다. 사용자는 때때로 git gc --aggressive를 실행하는 것이 좋습니다. git help gc는 변경 집합 수백 개마다 실행할 것을 권장합니다. 활동이 많은 프로젝트라면 일주일에 한 번 정도, 활동이 적은 프로젝트라면 그보다 드물게(격주 또는 매월) 실행하는 것이 좋습니다.

git gc --aggressive는 dangling 객체를 제거할 뿐만 아니라, 객체 데이터베이스를 색인이 있고 더 잘 최적화된 팩으로 다시 압축합니다. 또한 심볼릭 참조(브랜치와 태그)도 팩으로 묶습니다. 다른 방법으로는 git repack을 실행하는 것이 있습니다.

git gc --aggressive의 “어리석음”에 관해 Linus Torvalds가 쓴 잘 알려진 메시지가 있습니다. 이제는 이 메시지를 무시해도 괜찮습니다. 그 메시지는 오래되어 더 이상 유효하지 않으며, git gc --aggressive는 그 이후로 훨씬 좋아졌습니다.

여전히 git gc --aggressive보다 git repack을 선호하는 사람에게 권장하는 매개변수는 git repack -a -d -f --depth=20 --window=250입니다. 이 매개변수들의 효과에 대한 설명은 이 상세한 실험을 참조하십시오.

데이터베이스의 무결성을 검증하려면 때때로 git fsck [--strict]를 실행하십시오. git fsck는 dangling 객체 목록을 출력할 수 있는데, 이는 오류가 아니라 정기적인 유지 관리를 하라는 알림일 뿐입니다.

팁과 요령

명령줄 옵션과 인자

git help cli는 짧은 옵션/플래그를 결합하지 말 것을 권장합니다. 대부분의 경우 결합해도 동작합니다. git commit -av는 완벽하게 동작하지만, 그렇지 않은 상황도 있습니다. 예를 들어, git log -p -5는 git log -p5로 결합할 수 없습니다.

어떤 옵션은 인자를 받으며, 어떤 옵션은 기본 인자까지 가지고 있습니다. 이 경우 그런 옵션의 인자는 붙여서 써야 합니다. -Oarg처럼 쓰고, 절대 -O arg처럼 쓰지 마십시오. 기본 인자가 있는 옵션에서 후자는 “옵션 -O에 기본값을 사용하고 arg는 옵션 파서에 그대로 넘기라”는 의미가 되기 때문입니다. 예를 들어, git grep에는 찾은 파일의 이름 목록을 프로그램에 전달하는 -O 옵션이 있습니다. -O의 기본 프로그램은 페이저(보통 less)이지만, 편집기를 사용할 수도 있습니다:

$ git grep -Ovim  # but not -O vim

참고로, git이 페이저로 less를 사용하도록 지정된 경우(즉, git에 페이저가 전혀 설정되지 않아 기본값으로 less를 사용하는 경우, GIT_PAGER 또는 PAGER 환경 변수에서 less를 가져오는 경우, git config [--global] core.pager less로 설정된 경우, 또는 git grep -Oless 명령에서 less를 사용하는 경우) git grep은 less에 +/$pattern 옵션을 전달하는데, 이는 꽤 편리합니다. 안타깝게도 페이저가 정확히 less가 아니면, 매개변수가 붙은 less라도(git config [--global] core.pager less -FRSXgimq 같은 경우) git grep은 패턴을 전달하지 않습니다. 다행히 git grep -Oless는 항상 패턴을 전달합니다.

bash/zsh 자동 완성

명령줄을 즐겨 쓰는 사람이라도 git rebase --interactive --preserve-merges HEAD~5를 직접 입력하기는 조금 번거로운데, 바로 이럴 때 셸 자동 완성이 큰 도움이 됩니다. Bash/zsh에는 프로그래밍 가능한 자동 완성 기능이 있으며, 대개 자동으로 설치되고 활성화되어 있습니다. 따라서 bash/zsh와 git이 설치되어 있다면 이미 준비가 끝났을 가능성이 높으니, 명령줄에서 바로 사용하십시오.

필요한 구성 요소가 설치되어 있지 않다면 bash_completion 패키지를 설치하고 활성화하십시오. git 자동 완성을 최신 버전으로 업그레이드하려면 git contrib에서 필요한 파일을 내려받으십시오.

Git-for-windows에는 git-bash가 함께 제공되며, git-bash에는 bash 자동 완성이 설치되고 활성화되어 있습니다.

bash/zsh 프롬프트

명령줄을 즐겨 쓰는 사람에게 셸 프롬프트는 유용한 정보를 많이 담아 보여 줄 수 있습니다. 프롬프트에 git 정보를 포함하려면 git-prompt.sh를 사용하십시오. 자세한 사용법은 파일 안의 설명을 읽으십시오.

다른 프롬프트 변형을 찾으려면 인터넷에서 “git prompt”를 검색하십시오.

SSH 연결 공유

SSH 연결 공유는 OpenSSH의 기능이며, PuTTY 같은 파생 프로그램에도 있을 수 있습니다. SSH 연결 공유는 연결 하나를 맺은 뒤 같은 서버에 접속하는 이후의 모든 클라이언트가 그 연결을 재사용하게 하여 ssh 클라이언트 시작 시간을 줄이는 방법입니다. SSH 연결 공유를 사용하면 scp, sftp, rsync, 그리고 물론 ssh를 통한 git처럼 짧은 ssh 세션을 많이 여는 작업의 속도를 높일 수 있습니다. ssh로 접근할 수 있는 원격 저장소에서 fetch/pull하거나 그 저장소로 push하는 일이 잦다면 ssh 연결 공유를 사용하는 것을 권장합니다.

ssh 연결 공유를 켜려면 ~/.ssh/config에 다음과 같은 내용을 추가하십시오.:

Host *
ControlMaster auto
ControlPath ~/.ssh/mux-%r@%h:%p
ControlPersist 600

자세한 내용은 OpenSSH wikibook을 참고하고 검색해 보십시오.

SSH 연결 공유는 GitHub, GitLab, SourceForge 저장소에서 사용할 수 있지만, BitBucket은 이를 허용하지 않으며 짧은 시간 동안 활동이 없으면 마스터 연결을 강제로 닫는다는 점에 유의하십시오. 그러면 ssh에서 “Connection to bitbucket.org closed by remote host.” 같은 오류가 나타납니다.

서버에서의 git

저장소 하나 또는 여러 저장소를 공개하는 가장 간단한 방법은 git daemon입니다. 이 데몬은 익명 접근을 제공하며, 기본적으로 읽기 전용입니다. 저장소는 git 프로토콜(git:// URL)로 접근할 수 있습니다. 쓰기 접근을 활성화할 수도 있지만 이 프로토콜에는 인증 수단이 전혀 없으므로, 신뢰할 수 있는 LAN 안에서만 활성화해야 합니다. 자세한 내용은 git help daemon을 참고하십시오.

저장소를 사용자 또는 그룹이 쓸 수 있게 만들 수 있으므로, ssh를 통한 git은 인증과 저장소 수준의 권한 부여를 제공합니다(git help config의 core.sharedRepository 매개변수를 참고하십시오). 이것이 어떤 프로젝트에 필요한 것보다 너무 느슨하거나 너무 엄격하다면, 접근 권한을 아주 세밀하게 설정할 수 있는 래퍼인 gitolite가 있습니다. gitolite는 Perl로 작성되었으며 문서가 풍부합니다.

저장소를 둘러보는 웹 인터페이스는 gitweb 또는 cgit으로 만들 수 있습니다. 둘 다 CGI 스크립트입니다(각각 Perl과 C로 작성되었습니다). 둘 다 웹 인터페이스 외에도 git에 대한 읽기 전용 dumb http 접근(http(s):// URL)을 제공합니다. Klaus는 웹 인터페이스와 git smart HTTP 전송을 모두 구현하는 작고 단순한 WSGI 웹 서버로, Python 2와 Python 3을 지원하며 문법 강조 기능을 제공합니다.

사용자, 그룹, 프로젝트를 관리하고 비공개 저장소, 그룹 접근 저장소, 공개 저장소를 관리하는 기능을 갖춘 더 고급 웹 기반 개발 환경도 있습니다. 이러한 환경에는 이슈 트래커, 위키 페이지, 풀 리퀘스트를 비롯해 개발과 소통을 위한 여러 도구가 포함된 경우가 많습니다. 이러한 환경으로는 Kallithea와 pagure가 있으며, 둘 다 Python으로 작성되었습니다. pagure는 Fedora 개발자들이 작성했으며 일부 Fedora 프로젝트 개발에 사용되고 있습니다. GitPrep은 Perl로 작성된 또 하나의 GitHub 클론입니다. Gogs는 Go로 작성되었습니다. GitBucket은 Scala로 작성되었습니다.

마지막으로 빼놓을 수 없는 것이 GitLab입니다. 아마도 git을 위한 가장 발전된 웹 기반 개발 환경일 것입니다. Ruby로 작성되었으며, 커뮤니티 에디션은 무료 오픈 소스입니다(MIT 라이선스).

Mercurial에서 git으로

Mercurial 저장소를 git으로 변환하는 도구는 많습니다. 가장 유명한 것은 아마도 hg-git과 fast-export일 것입니다(오래전에는 hg2git이라는 이름으로 알려져 있었습니다).

하지만 더 나은 도구, 어쩌면 최고의 도구는 git-remote-hg입니다. 이 도구를 사용하면 git에서 Mercurial 저장소에 투명하게 양방향(pull과 push)으로 접근할 수 있습니다. 이 도구의 작성자는 대체로 객관적인 것으로 보이는 대안 비교를 작성했습니다.

git-remote-hg를 사용하려면 이를 설치하거나 클론한 뒤 PATH에 추가하고(또는 git-remote-hg 스크립트를 이미 PATH에 있는 디렉터리로 복사하고) Mercurial URL 앞에 hg::를 붙이십시오. 예를 들면 다음과 같습니다.:

$ git clone https://github.com/felipec/git-remote-hg.git
$ PATH=$PATH:"`pwd`"/git-remote-hg
$ git clone hg::https://hg.python.org/peps/ PEPs

저장소로 작업할 때는 git fetch/pull/push를 포함한 일반적인 git 명령을 그대로 사용하면 됩니다.

Mercurial 습관을 git으로 바꾸기 시작하려면 Mercurial 위키의 Mercurial for Git users 페이지를 참고하십시오. 페이지 후반부에는 서로 대응하는 Mercurial 명령과 git 명령을 나열한 표가 있습니다. 이 표는 양방향 모두에서 완벽하게 통할 것입니다.

Python 개발자 가이드에도 git과 hg의 몇 가지 차이점을 설명하는 Mercurial for git developers 장이 있습니다.

Git과 GitHub

gitsome - Git/GitHub 명령줄 인터페이스(CLI)입니다. Python으로 작성되었으며 MacOS, Unix, Windows에서 동작합니다. 자동 완성을 지원하는 Git/GitHub CLI로, 모든 셸에서 동작하는 다수의 GitHub 통합 명령, 셸 명령과 함께 Python 명령을 실행할 수 있는 Python REPL을 갖춘 내장 xonsh, 명령 기록, 사용자 정의 가능한 강조 표시를 제공하며 문서화도 충실하게 되어 있습니다.