Git Magic

Ben Lynn

2007년 8월

서문

Git 은 버전 관리계의 스위스아미나이프 정도로 보면 됩니다. 아주 유연하고 믿을 수 있지만 그만큼 배우기는 어려울 수도있는 이 Version Control System을 마스터 해봅시다!

Arthur C. Clarke는 충분히 발전한 기술은 마술과 같다고 말하였습니다. Git도 마찬가지입니다. 초보자들은 Git이 어떻게 돌아가는지 알 필요가 없으며 Git이라는 간단한 장치가 어떻게 친구들과 적들을 놀라게 하는지만 알면됩니다^^.

세부사항들을 설명하는 대신에, 우리는 몇몇 기능들의 대략적인 설명을 하려합니다. 여기에 설명된 기능들을 자주 사용하다 보면 각각의 명령어들이 어떻게 작동하는지 알게 될 것입니다. 그리고 그 명령어들을 적용하여 새로운 일들을 해낼 수 있겠지요.

고맙습니다!

많은 분들 께서 번역에 힘써주셔서 저는 어떻게 몸둘 바를 모르겠습니다. 이 분들을 통해 더 많은 독자들을 만날 수 있어서 정말 기쁘고 감사드립니다.

Dustin Sallings, Alberto Bertogli, James Cameron, Douglas Livingstone, Michael Budde, Richard Albury, Tarmigan, Derek Mahar, Frode Aannevik, Keith Rarick, Andy Somerville, Ralf Recker, Øyvind A. Holm, Miklos Vajna, Sébastien Hinderer, Thomas Miedema, Joe Malin, Tyler Breisacher, Sonia Hamilton, Julian Haagsma, Romain Lespinasse, Sergey Litvinov, Oliver Ferrigni, David Toca, Сергей Сергеев, Joël Thieffry, and Baiju Muthukadan 수정 및 편집에 힘써주셧습니다..

François Marier 는 Daniel Baumann이 개발한 Debian 패키지를 관리합니다.

고마워 해야할 사람들이 많지만은 여기에 다 쓸수는 없는 노릇입니다.

그래도 만약에 제가 이 웹사이트에 실수로 이름을 개제하지 않았다면 연락을 주시거나 패치를 만들어 주세요!

라이센스

이 가이드는 the GNU General Public License version 3 통해 발간되었습니다. 자연스레 소스콛드들은 Git 저장소에 저장되어있습니다:

$ git clone git://repo.or.cz/gitmagic.git  # Creates "gitmagic" directory.

아니면 다음 미러사이트들에도 소스코드가 저장되어 있을겁니다.:

$ git clone git://github.com/blynn/gitmagic.git
$ git clone git://gitorious.org/gitmagic/mainline.git
$ git clone https://code.google.com/p/gitmagic/
$ git clone git://git.assembla.com/gitmagic.git
$ git clone git@bitbucket.org:blynn/gitmagic.git

GitHub, Assembla, Bitbucket은 사적인 저장소를 지지합니다. Assembla와 Bitbucket은 무료로 제공되고 있습니다.

도입부

Github에 대해 설명하기 쉽게 비유법을 사용하여 버전 관리 시스템 (Version Control System; 이하 VCS)를 설명해보려 합니다. 제가 하려는 설명에 비해 덜 정신나간 버전의 설명을 원하시면 http://en.wikipedia.org/wiki/Revision_control 를 방문하시길 권장합니다.

"일하는 것"은 곧 "노는 것"

저는 거의 평생을 컴퓨터게임을 하며 지냈습니다. 그에 반해, 어른이 되서야 VCS를 사용하기 시작했지요. 이런 건 저 혼자가 아닐 것이라 생각하니, Git을 게임에 비유하며 설명하는 것이 Git을 이해하는 데 도움이 될 것이라 생각합니다.

자, 이제 코드나 문서를 편집하는 작업이 게임을 하는 것과 같다고 생각해보세요. 편집을 마친 후에는 세이브하고 싶겠지요? 그렇게 하기 위해서는 당신의 든든한 코딩에디터에서 세이브 버튼을 누르면 될 것입니다.

그러나 단순히 '세이브’를 누르는 것은 예전 세이브를 덮어쓰는 결과를 초래하죠. 세이브 슬롯이 한 개밖에 없는 옛날 구형 게임을 생각하면 됩니다. 다시 말하자면, 세이브를 할 수는 있지만, 예전 세이브포인트로 돌아갈 수 없는 것 입니다. 마치, 게임 진행하다가 정말 재미있는 파트에 적재적소에 맞게 세이브를 해 놓았는데 그 포인트로 다신 돌아갈 수 없다는 것이죠. 좀 더 심하게 말하자면, 절대로 깰 수 없는 보스 앞에서 세이브를 한 당신은 그 상태에 평생 머무르게 될수도 있다는 것 입니다. 그럴 경우에는 아주 처음부터 다시 시작해야 된다는 말이 되겠지요.

버전 관리

코드 등을 편집 시, 다른 이름으로 저장 아니면 사본을 다른 디렉토리에 카피 떠 놓는 방법 등을 이용해 옛 버전의 코드들을 보존할 수는 있습니다. 컴퓨터 용량을 더욱 효율적으로 사용하기 위해서 압축을 할 수도 있죠. 이것은 참 원시적인 버전 컨트롤 방법입니다. 컴퓨터게임은 이런 과정에서 이미 발전해 나간지 오래되었지요. 요즘 게임들은 여러개의 세이브 슬롯에 시간을 기록해가며 세이브를 영리하게 해냅니다.

이 문제를 좀 더 꼬아서 바라봅시다. 당신이 어떤 프로젝트나 웹사이트를 구성하는 소스코드와 같이 여러개의 파일을 보유한다고 가정합시다. 현 버전의 프로젝트/웹사이트를 세이브하고 싶다면 모든 디렉토리를 기록해야 한다는 번거로움이 있겠지요. 일일이 그 수 많은 버전들을 수동으로 관리한다는 것은 그리 효율적이지 않을 겁니다. 컴퓨터 용량도 더불어 많이 사용하게 될꺼고요.

어떤 컴퓨터게임들은 정말로 모든 디렉토리를 각개 관리하는 형식으로 게임을 세이브하기도 합니다. 이런 게임들은 이런 불필요하게 세부적인 사항들을 게이머들이 보지 못 하게 하고 간편한 인터페이스를 통해 게이머들이 세이브파일들을 관리할 수 있게 해둡니다.

VCS는 이런 컨셉과 그리 다르지 않습니다. VCS들은 파일 디렉토리들을 관리하기에 아주 편한 인터페이스로 구성되어 있습니다. 원하는 횟수 만큼 세이브를 할 수 있고, 원하는 세이브포인트를 특정지어 불러오기를 실행할 수도 있습니다. 그리고 컴퓨터게임들과는 다르게 용량을 효율적으로 사용하는 데에는 탁월한 성능을 보여주죠. 대부분의 어떤 코드의 버전을 바꿀 때에는 소수의 파일들만 살짝 바꾸게되죠. 코드자체가 아주 많이 바뀌는 경우는 드뭅니다. 디렉토리 전체를 세이브하는 것 보다는 버전과 버전사이의 차이를 세이브하는 것이 용량을 효율적으로 쓰는 VCS의 비밀입니다.

분산 제어

여러분이 어려운 컴퓨터 게임을 한다고 생각해보세요. 너무 어렵기 때문에 전 세계의 프로게이머들이 팀을 구성해 이 게임을 끝내보겠다고 합니다. 게임을 빨리 끝내는 것에 초점을 두는 스피드런 방식의 게임 스타일이 현실적인 예시 이지요: 각기 다른 특기를 가지고 있는 게이머들이 한 게임 안에서 각자 자신있는 부분을 담당함으로써 성공적인 결과를 만들어내는 것을 예로 들어봅니다.

어떻게 시스템을 구축해 두어야 게이머들이 서로의 세이브파일들을 쉽게 업로드 하거나, 바통을 이어 받을 수 있을까요?

프로그래밍 프로젝트들은 예전에 중앙 집중식 VCS를 사용하였습니다. 한 개의 서버가 모든 세이브파일을 저장했었지요. 그 서버외에는 아무 것도 그 세이브파일들을 관리할 수 없었습니다. 게임으로 말하자면, 게이머들은 각자의 게임기에 몇 개의 세이브파일들을 가지고 있었고, 게임을 다른 사람으로부터 이어받아 진행하고 싶을 때에는, 모든 세이브파일들이 저장되어있는 중앙서버에서 파일들을 다운로드 받은 후, 게임을 좀 하다가, 다시 다른 게이머들이 진행할 수 있게 그 서버에 업로드 해 놓아야 합니다.

만약에 어떤 한 게이머가 예전에 세이브 해두었던 오래된 파일을 불러오고 싶다면 어떻게 될까요? 현재 최신의 세이브 시점은 누군가 게임의 전 단계에서 다음 단계에 필요한 아이템을 주워오지 않아서 아무리 잘 해도 게임을 진행 할수없는 상태로 저장이 되어있을지도 모르고, 그런게 아니라면 그들은 아마도 세이브파일 두 개를 비교하여 한 특정 게이머가 얼마나 진행을 하였는지 알고 싶어할지도 모릅니다.

예전 세이브 파일을 불러오고 싶은 이유는 여러가지일 수 있습니다, 그러나 방법은 한 가지일 수 밖에 없지요. 중앙서버에서 불러오는 방법 말입니다. 더 많은 세이브파일을 원할 수록 서버와의 통신이 더 잦아질 수 밖에 없지요.

(Git을 포함하여) 새로운 세대의 VCS들은 분산 제어를 기본으로 합니다. 예전의 중앙관리 방식의 보편화된 방식이라고 생각하면 되지요. 한 게이머가 서버로부터 (가장 최신) 세이브파일을 받는다해도 그 하나만 받게되는 것이 아니라 모든 예전 버전의 세이브파일까지도 같이 받게 되는 겁니다. 마치 중앙서버를 각자의 컴퓨터에 미러링한다고 보시면 됩니다.

그렇기에 처음에는 Git을 셋업할 때 시간이 많이 걸릴 수 있습니다. 특히, 그 세이브파일이 오래되었고, 아주 긴 역사를 가지고 있다면 말이지요. 그러나 이 것은 길게보면 아주 효율적인 방법입니다. 이 방법을 통해 즉시 이득을 볼 수있는 점을 따진다면, 예전 세이브파일을 원할 때 중앙서버와 교신을 하지 않아도 된다는 점이지요.

멍청한 미신

사람들이 분산 제어 시스템에 대해 일반적으로 오해하는게, 분산 제어 시스템은 공식적인 중앙 저장소가 필요한 프로젝트에는 적합하지 않다고 생각하는 것입니다. 이 것은 말도 안되는 오해이지요. 이 오해는 누군가의 사진을 찍는다는 것은 그 피사체의 영혼을 같이 담아버린다는 말도 안 되는 논리와 같습니다. 다시 말하면, 중앙 저장소의 파일을 카피하여 분산 제어하는 것이 중앙 저장소의 중요성을 훼손한다는 것이 아닙니다.

첫번째 이해해야하는 부분이, 중앙 버전 관리 시스템이 할 수 있는 모든 일들은 잘 짜여진 분산 관리 시스템이 더 잘 할수 있다는 것을 인식해야 한다는 것입니다. 네트워크상의 자원들은 기본적으로 로컬상의 자원들보다 시간적으로나 물질적으로 비경제적일 수 밖에 없습니다. 물론, 나중에도 말씀드리겠지만 분산 제어 시스템도 문제점이 없는 시스템은 아닙니다. 그러나 주먹구구식의 생각으로 중앙 관리 시스템과 분산 관리 시스템을 비교하는 일은 없어야 할 것입니다. 다음 인용문이 이것을 대변해 줍니다.

"규모가 작은 프로젝트들은 시스템의 부분적인 특성만으로도 실행 할 수 있겠지만, 이런 프로젝트들을 스케일업 할 수 없는 확장성이 낮은 시스템으로 계속 실행하는 것은 마치 옛 로마숫자를 이용해 계산기를 두드리는 것과 같다."

더욱이 당신의 프로젝트는 당신이 처음 생각했던 것보다 더 거대한 일이 될지도 모르는 겁니다. 처음부터 Git을 사용한다는 것은 단순히 병뚜껑을 여는데 스위스아미나이프를 들고 다니는 것과 같은 것입니다. 그러나, 어느 날, 드라이버가 필요할 경우, 당신은 병따개만 들고다니지 않았다는 사실에 안도의 한 숨을 쉬게 될 것입니다.

병합 충돌

이 주제를 설명하기 위해서는 컴퓨터게임에 비유하는 것은 더 이상 적합하지 않을 수 있습니다. 대신에 여기서는 문서편집에 비유해서 설명드리도록 하죠.

다음 예시를 생각해 봅시다. 앨리스(Alice)는 파일을 편집하던 도중에 새로운 줄을 첫 줄로 추가하고, 밥(Bob)은 그 같은 파일의 마지막에 코드 한 줄을 더한다고 가정합시다. 그리고 그들은 편집된 파일을 각자 중앙서버에 업로드 합니다. 대부분의 시스템은 자동으로 두 사람이 각자 한 편집을 받아들이고 병합할 것입니다. 결과적으로는 앨리스와 밥 두 사람의 편집이 모두 한 파일에 적용되겠죠.

자 이제 앨리스와 밥이 어떤 파일의 정확히 같은 부분에서 서로 다른 편집을 한다고 가정해 봅시다. 이럴 경우에는 인간의 직접적인 개입없이는 온순한 편집이 불가능 하겠지요? 누가 편집을하던 두번째로 편집하는 사람은 작업을 결합하는 과정에서 오류메세지 ("merge conflict")를 볼 수밖에 없겠지요. 이런 오류메세지를 피하기 위해선 한 사람만의 작업을 선택하거나 두 사람의 작업을 아우를 수 있는 새로운 코딩작업을 추가로 해줘야하는 번거로움이 발생하지요.

예시보다 더 복잡한 상황이 일어날수도 있습니다. VCS는 간단한 상황들을 알아서 해결해 주고, 어려운 상황은 인간의 손에 맡기지요. 이런 VCS의 행동은 대체적으로 조정가능합니다.

==기본적인 요령==

Git의 수많은 명령어 속으로 곧바로 다이빙 하는 것 보단, 다음 간단한 예시들을 통해서 천천히 배우는 방법이 좋을 것 같습니다. 제가 소개하는 예시들은, 표면적으로는 간단하게 보이지만, 앞으로 여러방면으로 많은 도움이 될 것입니다. 저 역시도 처음 Git을 사용할 때에는 아래에 있는 예시 외에는 건들여 보지도 않았습니다.

상태 (state) 저장하는 방법===

파일에 무엇인가 큰 변화를 주고 싶으시다고요? 그러시기 전에, 현 디렉토리에 들어있는 모든 파일의 스냅샷을 찍어봅시다:

$ git init
$ git add .
$ git commit -m "My first backup"

위 명령어들을 입력 후, 만약에 편집을 하다가 잘못됬다면, 편집되기 전의 깨끗한 버전으로 되돌리면 됩니다:

$ git reset --hard

또 어떤 작업 후 state를 저장하고 싶다면:

$ git commit -a -m "Another backup"

파일 더하기 (add), 지우기 (delete), 이름 바꾸기 (rename)

위의 간단한 요령들은 처음 git add 명령어를 실행했을 때 이미 존재하던 파일들만 저장하게 됩니다. 존재하던 파일의 편집 이외의 새로운 파일들이나 하위 디렉토리들을 추가했다면, Git에게 알려줘야 합니다:

$ git add readme.txt Documentation

그리고 만약에 원하지 않는 파일을 Git에서 없애려면 그것 역시 Git에게 알려줘야 합니다:

$ git rm kludge.h obsolete.c
$ git rm -r incriminating/evidence/

이렇게 함으로써 Git은 지정한 파일들을 지워주게 됩니다.

Git 파일이름을 바꿀때에는 원치않는 일반파일들의 이름을 지우고 새로운 이름을 새롭게 지정하는 간단한 절차와 같습니다. 좀 더 손쉬운 방법으로는 git mv 명령어가 있습니다. 예를 들어:

$ git mv bug.c feature.c

고급 undo와 redo

가끔씩은 작업을 하다가 하던 일을 멈추고 전 버전으로 돌아가고 싶다거나, 어느 시점 이후의 모든 편집을 지우고 싶을 때가 있을 것입니다. 그렇다면:

$ git log

이 명령어는 최근의 commit들을 정리한 리스트와 그의 SHA1 hashes를 보여줍니다:

commit 766f9881690d240ba334153047649b8b8f11c664
Author: Bob <bob@example.com>
Date:   Tue Mar 14 01:59:26 2000 -0800

    Replace printf() with write().

commit 82f5ea346a2e651544956a8653c0f58dc151275c
Author: Alice <alice@example.com>
Date:   Thu Jan 1 00:00:00 1970 +0000

    Initial commit.

Hash 앞의 알파벳 몇 개만으로도 commit을 세분화 설정하실 수 있습니다; 다른 방법으로는, 아래의 명령어와 같이 hash 전문을 복사/붙여넣기 하는 방법도 있지요:

$ git reset --hard 766f

위 명령어를 입력하시면 설정된 commit으로 돌아갈 수 있으며 그 후의 새로운 commit들은 영구적으로 삭제됩니다.

가끔씩은 또 아주 예전의 state로 잠시만 돌아가길 원하실 수 있습니다. 그럴 경우에는:

$ git checkout 82f5

이 명령어는 82f5 이후의 commit들을 보존함과 동시에 과거의 시간으로 잠시 돌아가게 해줍니다. 그러나, SF영화에서 처럼, 과거에 돌아간 상태에서 편집을하고 commit을 한다면 또 다른 시간대의 현실을 만들어가게 되는 것이죠. 왜냐하면 당신의 편집이 과거의 편집과는 다르게 입력이 되었기 때문입니다.

이렇게 새롭게 만들어진 대체현실을 'branch (나뭇가지)'라고 부릅니다 에 관해선 추후에 자세히 설명합니다. 지금 알고계셔야 할 것은

$ git checkout master

이 명령어는 과거에서 현재의 state로 돌아오게 해줄 것입니다. 그리고 Git이 유저에게 푸념을 놓기전에 과거에서 편집했던 사항들이 있다면 master branch로 돌아오기전 commit을 하거나 reset을 한번 실행하시길 바랍니다.

게임과 또 다시 비교해본다 하면:

예전의 파일/하위 디렉토리들을 되돌리고 싶을 때 다음 명령어를 이용함으로써 필요한 파일/하위 디렉토리만을 되돌릴 수 있습니다:

$ git checkout 82f5 some.file another.file

그러나 이 checkout 명령어가 다른 파일들을 조용히 덮어씌우기 할 수 있다는 점을 알아두세요! 이러한 사고를 방지하고 싶다면 checkout 명령어를 쓰기전에 commit을 이용하세요. Git을 처음 이용하는 분들은 특히 더 조심하시기 바랍니다. 대체적으로 파일이 삭제될까 두려우시다면 *git commit -a*를 우선해놓고 생각하세요.

긴 hash 전체를 복붙하기 싫으시다고요? 그렇다면:

$ git checkout :/"My first b"

이 명령어를 사용함으로써 이 commit message를 사용해서 commit했었던 state로 돌아갈 수 있습니다. 그리고 이 다음 명령어로 5번 스텝 전의 state로 돌아갈 수도 있습니다:

$ git checkout master~5

되돌리기 (Reverting)

법정에서는 어떠한 일에 관해서는 기록에서 지울 수 있습니다. 이런 식으로, Git에서는 원하는 commit을 정해서 없던 일로 할 수 있습니다.

$ git commit -a
$ git revert 1b6d

이렇게 하는 것으로 특정 hash에 대한 commit을 undo 할 수 있습니다. 이렇게 되돌린 state는 새로운 commit으로 인식되어 *git log*에 기록됩니다.

변경기록 만들기

어떤 프로젝트들은 changelog. 필요로 합니다. 다음 명령어를 이용해 변경기록을 만들어 봅시다.:

$ git log > ChangeLog

파일 다운로드하기

Git으로 관리되는 프로젝트 사본을 얻기위해서는:

$ git clone git://server/path/to/files

예를 들어, 본 웹사이트를 만들기 위해 사용한 파일들을 얻기위해서는:

$ git clone git://git.or.cz/gitmagic.git

clone 명령어에 관해 많은 것을 소개하도록 하겠습니다.

최첨단 기술

git clone 명령어를 이용해 어떤 프로젝트의 사본을 다운로드 해뒀다면, 다음 명령어를 이용해 그 프로젝트의 최신버전으로 업데이트 할 수 있습니다:

$ git pull

즉석 발행

당신이 다른 사람들과 공유하고 싶은 스크립트를 작성했다고 가정합니다. 당신은 그들에게 당신의 컴퓨터에서 다운로드를 받으라고 할 수있지만, 당신 친구들이 만약 당신이 해당 스크립트를 편집하는 도중에 받게된다면, 그들은 예상치 못한 트러블에 걸릴 수 있습니다. 이러한 이유 때문에 릴리스 사이클이란 것이 존재하는 것입니다. 개발자들은 개발 중인 프로젝트 디렉토리에 자주 들락날락 거릴 것이고, 그들은 그들이 한 작업이 다른 사람들 앞에 내놓을 만한 상태로 만들어지기 전까지 남들에게 보여주지 않을겁니다.

Git으로 릴리스 사이클을 맞추려면, 당신의 스크립트가 들어있는 디렉토리에서:

$ git init
$ git add .
$ git commit -m "First release"

그리고 당신들 친구들에게 다음 명령어를 사용하도록 하십시오:

$ git clone your.computer:/path/to/script

그들이 이렇게하면 당신의 스크립트를 다운로드 할 수 있을 것입니다. 이 작업은 다른 유저들이 ssh 접근을 할수있다고 가정합니다. 그렇지 않다면, 소유주인 당신이 git daemon 명령어를 쓴 후 친구들에게 다음 명령어를 쓰라고 하십시오:

$ git clone git://your.computer/path/to/script

이렇게 하고 난 다음부터 당신의 스크립트가 준비되었을 때마다 다음 명령어를 실행하면 됩니다:

$ git commit -a -m "Next release"

당신의 친구들은 다음 명령어를 사용함으로써 가장 최근 버전으로 당신의 스크립트를 보유하고 있을 수 있게 되죠:

$ git pull

그들은 절대로 당신이 보여주고 싶지않은 버전의 스크립트를 보는 일이 없을 것입니다.

제가 도대체 뭘 한거죠?

마지막으로 한 commit으로 부터 어떤 변화가 있었는지 확인하기 위해서는:

$ git diff

어제부터 어떤 변화가 있었는지 확인하기 위해서는:

$ git diff "@{yesterday}"

어떤 특정 버전에서 부터 2번째 전 버전 사이의 변화를 확인하기 위해서는:

$ git diff 1b6d "master~2"

각각의 결과는 *git apply*와 함께 적용할 수 있는 패치가 될 것입니다. 다음 명령어도 사용해 보세요:

$ git whatchanged --since="2 weeks ago"

저는 윗 방법대신 qgit 를 따로 다운받아서 commit 히스토리를 체크하곤 합니다. 이 프로그램은 깨끗한 그래픽 인터페이스로 구성되어 있어보기 쉽지요. 아니면, tig, 텍스트형식 인터페이스 역시 느린 인터넷속도를 가지고 있는 분들에겐 도움이 될 것입니다. 또 다른 방법으로는 웹 서버를 설치한 후 *git instaweb*명령어를 사용하는 방법도 있겠지요.

연습

우선 A, B, C, D 를 각각 연속된 한 파일에 대한 commit이라고 가정합니다. 그리고 B는 A 에서 몇 개의 파일들이 삭제된 버전으로 가정합니다. 문제는 여기서 그 삭제된 파일들을 D에 더하고 싶을 때 어떻게 하는 것 인가 입니다.

적어도 세가지의 방법이 있습니다. 우선 우리가 현재 D에 있다고 생각합시다:

  1. A와 B의 차이점은 몇 개의 지워진 파일들 뿐입니다. 우리는 이 차이점을 패치로 따로 작성하여 본래의 디렉토리에 적용할 수 있습니다:

    $ git diff B A | git apply
  2. 우리는 A에 파일을 저장해 두었기에, 그 곳에서 다시 받아올 수 있겠지요:

    $ git checkout A foo.c bar.h
  3. 또는 A에서 B까지로 갈 때의 변화를 undo한다고 생각하셔도 됩니다:

    $ git revert B

어떤 방법이 가장 좋은 해답일까요? 답은 본인이 원하는 것이 곧 해답입니다. Git을 이용한다면 당신이 원하는 것은 쉽게 해낼 수 있고, 그 것을 해내는 방법은 한가지만 있는 것이 아닐겁니다.

클론 만들기

구형 VCS에서는 체크아웃 명령어가 어딘가에 저장되어 있는 파일들을 가져오는 보편적인 방법이었습니다.

Git을 포함한 다른 분산 제어식 VCS에서는 클론만들기를 체크아웃을 대체하는 보편적인 방법으로 채택하고 있습니다. 어떤 저장된 파일을 얻기위해서는, 원하는 파일 원본들이 저장되어있는 저장소에서 내 컴퓨터로 끌고와 '클론’을 만들어야 합니다. 즉, 중앙관리서버를 미러링해오는 것과 같은 이치라고 설명할 수 있습니다. 클론을 본떠온다면 중앙관리서버가 할 수 있는 모든 것들을 당신이 이제 할 수 있는 것이죠.

컴퓨터 동기화

기본적인 동기화 및 백업을 할 때 tarball을 만드는 것과 *rsync*명령어를 사용하는 것은 이해할 수 있는 행동입니다. 그러나 저는 가끔씩 노트북에서 편집을 할 때도 있고, 데스크탑에서 할 때도 있는데, 이 두 개의 컴퓨터는 *rsync*같은 명령어를 사용하면서 작업할때 잦은 동기화를 하지 않을지도 모릅니다.

한 컴퓨터에서 Git Repository를 초기화하고 파일들을 commit함으로써 이 문제를 해결할 수 있습니다. 그 후 다른 컴퓨터에서:

$ git clone other.computer:/path/to/files

위 명령어를 이용해서 두 번째 Git repository 사본을 만들 수 있습니다. 그 다음부터는,

$ git commit -a
$ git pull other.computer:/path/to/files HEAD

을 이용하여 현재 작업중인 컴퓨터로 다른 컴퓨터에서 작업하던 파일들을 당겨올 (pull) 수 있습니다. 만약에 같은 파일에 대해서 전후가 맞지않는 작업을 했을 경우, Git은 당신에게 에러메세지로 먼저 이 모순을 해결 후 commit을 할 것을 알려줄 것입니다.

고전적인 소스 관리

우선 Git 저장소를 초기화 해줍니다:

$ git init
$ git add .
$ git commit -m "Initial commit"

그리고 중앙 서버에서, 아무 디렉토리에서나 태초의(bare) repository를 초기화 해줍니다:

$ mkdir proj.git
$ cd proj.git
$ git --bare init
$ touch proj.git/git-daemon-export-ok

필요하다면 Git daemon을 실행합니다:

$ git daemon --detach  # 아마 이미 daemon이 실행하고 있을지도 모릅니다.

Git 호스팅 서비스를 한다면 우선 빈 Git repository를 만들어야 합니다. 대부분 웹페이지에서 어떠한 문서를 작성하곤 하죠.

다음 명령어를 사용해 당신의 프로젝트를 중앙서버로 '밀어넣기 (push)' 할 수 있습니다:
$ git push central.server/path/to/proj.git HEAD

소스를 확인하고 싶을 때에 개발자는 다음 명령어를 사용합니다:

$ git clone central.server/path/to/proj.git

편집작업이 끝난 후에 개발자는 다음명령어를 사용해 로컬드라이브에 각종 바뀐 사항들을 저장을 합니다:

$ git commit -a

가장 최신 버전으로 로컬파일들을 갱신하려면:

$ git pull

merge할때 일어날 수 있는 오류들은 수동으로 해결 후, commit 명령어를 사용하여 작업을 commit 해주셔야 합니다:

$ git commit -a

로컬에서 바뀐 사항들을 중앙 저장소로 저장하기 위해서는:

$ git push

중앙 서버가 다른 개발자들로 인하여 새로운 변경사항이 생겼을 경우에는, 당신의 밀어넣기 (Push)'는 실패할 것입니다. 그렇다면 당신은 '밀어넣기 (Push) 하기 전에 최신 버전을 다시 당겨서 (pull) 오류를 수동으로 해결 후 다시 밀어넣기를 시도해야 하겠지요.

모든 개발자들은 특정 Git repository에 대한 SSH 접근권한이 있어야 push와 pull를 할 수 있습니다. 그러나 개발소스는 대부분 모든 이들에게 개방된 것으로써 다음 명령어를 이용하면 조회 및 클로닝이 가능합니다:

$ git clone git://central.server/path/to/proj.git

Git 프로토콜은 HTTP와 비슷합니다: 증명서가 존재하지 않죠. 그래서 아무나 프로젝트를 조회할 수 있는겁니다. 그런 이유에서 '밀어넣기 (push)'는 Git 프로토콜로는 할 수없게 디폴트 설정이 되어있지요.

숨겨진 소스

개방되어 있지않은 소스의 프로젝트를 진행할 때에는 Git의 터치 (Touch) 명령어를 생략합니다. 그리고 'git-daemong-export-ok’라는 이름의 파일을 절대 만들지 않도록 주의합니다. 이렇게하면 git 프로토콜을 사용해서 원치않는 사람들이 당신의 저장소를 조회하거나 클로닝 할 수 있는 일은 없을 것입니다; 이제는 SSH 접근권이 있는 사람들만 조회할 수 있게 될겁니다. 당신의 모든 repository가 개방되지 않은 경우에는 git daemon명령어는 필요없겠지요. 모든 repository는 SSH 를 통해서만 허락된 개발자들에게만 공개될테니까요.

태초의 저장소

이 괴상한 이름의 저장소 (bare repository)는 현재 작업중인 디렉토리가 아니기에 이렇게 이름이 붙여졌습니다; 이 저장소는 하위 .git 디렉토리에서 숨겨진 파일들만을 저장하는 저장소입니다. 풀어 설명하면, 이 저장소는 현 프로젝트의 과거를 관리하기만 하고, 아무런 버전도 저장하지 않는 저장소입니다.

헐벗은 저장소는 중앙서버관리식 VCS와 비슷한 기능을 담당하고 있고 당신의 프로젝트가 저장되어 있는 집과같은 기능을 담당하고 있습니다. 개발자들은 이 곳에서 부터 클론을 만들 수 있고, 작업한 내용을 밀어넣기 (Push) 할 수 있습니다. 보편적으로 이 bare repository는 서버에서 상주하고 있다가 데이터를 퍼트리는 역할을 맡고있습니다. 개발은 개발자 각자가 만들어 놓은 로컬컴퓨터에서의 클론에서 이루어짐으로써, 워킹 디렉토리없이 서버내에서 보호받는 저장소 역할을 할 수 있습니다.

많은 Git 명령어들은 GIT_DIR 환경 변수가 repository로 경로설정이 되어 있지않다면 이 bare repository에 인식되지 않을 것입니다. --bare 옵션을 이용한다면 모를까.

밀어넣기 (push) vs. 당기기 (pull)

당기기 (pull) 커맨드에만 의존하는 대신에 왜 제가 '밀어넣기 (push)'를 소개했을까요? 먼저, 당기기 (pull)는 아까 소개드린 bare repository에서는 실행이 되지 않는 명령어입니다: 물론 나중에 소개할 '물어오기 (fetch)'라는 명령어로 같은 일을 할 수 있지만요. 그러나 중앙서버에 저장 되어있는 보통 일반적인 repository에서도, 당기기 (pull)는 번거로울 수 밖에 없습니다. 서버에 로그인을 해야 될 것이고 그런 후에야 당기기 (pull)을 사용해야 하다는 말이지요. 파이어월이 이런 절차를 방해할 수도 있습니다. 그리고 쉘 접근 권한이 없다면 중앙 서버에 접속이나 가능할런지요?

그러나 이러한 특수상황들이 아니라면 밀어넣기 (push)를 사용하시는 것을 비추합니다. 목적지가 현재 작업중인 디렉토리가 있을 경우에는 굉장히 햇갈릴 수 있기 때문입니다.

줄여서, Git을 배울 때에는, bare repository일 경우에는 '밀어넣기 (push)'를 진행하시고 아니면 '당기기 (pull)'을 사용합시다.

프로젝트 포크질 (forking) 하기

현재 프로젝트가 진행되고 있는 방식이 마음에 안 드신 다고요? 당신이 좀 더 잘할 수 있다고 생각하세요? 그렇다면 당신 서버에서:

$ git clone git://main.server/path/to/files

이 명령어를 쓴 후에, 다른 사람들에게 당신이 포크질 (fork)을 한 프로젝트에 대해 알리세요.

이후 작업이 끝난 후 아무때나 원래 프로젝트 파일에서 다음 명령어를 씀으로써 포크질 해놓은 프로젝트로부터 오리지널 프로젝트 파일로 병합을 실행할 수 있습니다:

$ git pull

궁극의 백업

아무도 건들 수 없고 지리적으로 다양한 곳에 저장해놓고 싶은 기록 보관소를 소유하고 싶다고요? 만약 당신의 프로젝트에 많은 개발자들이 참여한다면 아무 것도 하지 마십시오. 그 수많은 개발자들이 각자 클론을 만들었다면 그 클론 자체가 아주 효율적인 프로젝트 백업이 될 것 입니다. 현 상태의 프로젝트 뿐만이 아니라, 그 프로젝트의 모든 과거 버전까지 말이죠. 만약이라도 어떤 개발자 분의 클론이 훼손 된다면 암호화 된 hashing 덕에 다른 모든 개발자들이 프로젝트 훼손여부에 관해 알 수 있게 될 것입니다.

만약 당신의 프로젝트에 그리 많은 개발자들이 참여하지 않는다면, 최대한 많은 서버를 확보해서 클론을 만들어 놓으십시오.

편집증이 걸린 개발자들은 언제나 프로젝트 HEAD의 20-바이트 SHA1 hash를 어딘가에는 안전하게 모셔놓죠. 안전하다는 말이 꼭 사적인 공간에 저장해놓는다는 말은 아닙니다. 예를 들면, 어떤 신문에 기사를 개제하는 것도 안전한 기록보관의 한 방법이 될 수 있지요. 그 정보를 훼손하고자하는 작자들이 세상에 발간된 모든 신문 카피를 바꿀 수는 없기 때문입니다.

광속의 멀티테스킹

만약에 어떠한 프로젝트의 여러 부분을 동시에 작업하고 싶으실 때에는 우선 현재상태의 프로젝트를 한 번 commit 한 후 다음 명령어를 사용합니다:

$ git clone . /some/new/directory

hardlinking 이라는 기능 덕분에 로컬시스템에 생성된 클론들은 일반 백업에 비해 비교적 적은 시간과 공간만 필요로 합니다.

이렇게 하면 두개의 독립적인 작업을 동시진행 할 수 있습니다. 예로, 한 클론이 컴파일 중일때 다른 클론에서 또 다른 작업을 진행하고 있을 수 있습니다. 그리고 다른 클론으로 부터 아무 때나 commit과 당기기 (pull)도 사용할 수 있습니다.

$ git pull /the/other/clone HEAD

게릴라 버전 관리

당신은 현재 다른 VCS를 사용하고 있지만, Git을 그리워하고 있진 않습니까? 그렇다면 현재 작업중인 디렉토리에서 Git을 초기화 시켜주십시오:

$ git init
$ git add .
$ git commit -m "Initial commit"

그리고 클론을 만들고:

$ git clone . /some/new/directory

이제 방금 클론 된 디렉토리에서 작업을 진행하시면 됩니다. 가끔은 다른 개발자 분들과 동기화하고 싶으시겠죠. 그 개발자분들은 아직 Git을 사용하고 있지 않아도 우리 Git에서는:

$ git add .
$ git commit -m "Sync with everyone else"

그리고는 새로운 디렉토리에서:

$ git commit -a -m "Description of my changes"
$ git pull

다른 분들에게 당신의 작업을 공유하는 일은 그 쪽 분들이 쓰시는 VCS에 따라 다릅니다. 새로운 디렉토리는 이제 당신이 실행한 작업들이 포함되어 있겠죠. 위의 명령어를 쓰신 후에 다른 VCS에서 쓰는 명령어를 통해서 그들의 중앙 서버에 업로드 하실 수 있습니다.

Subversion은 가장좋은 중앙관리식 VCS로써 개발자들 사이에서 애용되고 있습니다. Git에서 *git svn*을 사용해서 위에서 언급한 일들은 Subversion 저장소를 대상으로 행할 수 있습니다.Git 프로젝트를 Subversion 저장소로 보내기.

Mercurial

Mercurial 역시 비슷한 VCS으로써 Git과 쉽게 연동될 수 있습니다. 'hg-git’플러그인을 통해서 Mercurial 유저들은 Git 저장소에 쉽게 '밀어넣기 (push)'와 '당기기 (pull)'을 사용할 수 있죠.

Git으로 hg-git 플러그인을 구하는 방법:

$ git clone git://github.com/schacon/hg-git.git

Mercurial로 'hg-git’플러그인을 구하는 방법:

$ hg clone http://bitbucket.org/durin42/hg-git/

유감스럽지만 다른 VCS에선 Git과 비슷한 플러그인이 있는지는 모르겠습니다. 그렇기 때문에 Mercurial보다는 Git을 주 저장소를 쓰길 선호합니다. Mercurial로 프로젝트를 진행할 경우에는 대부분 한 개발자가 Git 저장소를 같이 병행관리하는 업무를 떠맡곤 합니다. 그러나 Git으로 Mercurial 프로젝트를 진행할 경우에는 'hg-git’플러그인의 도움으로 그러한 번거로움이 필요없겠죠.

Mercurial에 있는 repository를 Git repository로 '밀어넣기 (push)'를 사용하여 쉽게 바꿀 수 있으나, 'hg-fast-export.sh’스크립트를 사용해 이 작업을 더 쉽게 끝낼 수 있습니다. 다음 저장소에서 이 스크립트를 구할 수 있습니다:

$ git clone git://repo.or.cz/fast-export.git

빈 저장소에서 이 작업을 한번 해봅시다:

$ git init
$ hg-fast-export.sh -r /hg/repo

위 명령어는 스크립트를 '$PATH’에 넣은 후에 실행합니다.

Bazaar

Bazaar는 Git과 Mercurial 다음으로 많이 알려진 VCS입니다.

Bazaar는 만들어진지 별로 되지않은 시스템이기에 엄청난 가능성이 잠재하고 있지요; Bazaar 개발자들은 다른 VCS의 단점을 배우고 고쳐나가는 중입니다. 그리고 Bazaar 개발자들은 Bazaar VCS가 다른 VCS들과 연동하는 문제에 많은 노력을 기울이고 있습니다.

bzr-git’플러그인은 Bazaar 이용자들이 Git과 함께 연동해 작업할 수 있도록 해줍니다. 'tailor 프로그램은 Bazaar repository를 Git repository로 바꿔줍니다. 'bzr-fast-export’도 한 번 검색해보세요.

내가 Git을 사용하는 이유

제가 Git을 처음에 사용했던 이유는 제가 듣기에 Git은 Linux kernel source 관리에 용이하다고 들었기 때문입니다. Git을 사용한 이후로는 다른 VCS로 바꿔야겠다는 생각은 들지도 않았지요. Git은 저에게 매우 유용했고 저는 아직 Git으로 인한 어떠한 심각한 오류를 겪어보지도 않았습니다.저는 Linux를 주로 이용하기 때문에 다른 플랫폼에서 발생할 수 있는 문제는 생략하겠습니다.

그리고 저는 C, bash scripts, Python을 이용하는 사람이고 프로그램 런타임에 목숨을 거는 사람 중 하나입니다.

Git이 어떻게 좀 더 발전할 수 있을지, 또 Git과 비슷한 프로그램도 직접 짜보기도 했지만 학교 프로젝트 정도로만 썻었을 뿐입니다. 그러나 제가 직접 저만의 VCS를 만들었더라도 저는 Git을 계속 이용했을 겁니다. 제 프로그램을 써도 별로 투자한 것에 비해 얻을 것이 적어보였기 때문이지요.

자연스레 여러분들이 필요로하고 원하는 프로그램은 계속해서 바뀝니다. 그러나 Git과는 그럴 가능성이 매우 적지요. == 브랜칭 마법 ==

Git의 끝내주는 기능들 중에는 즉석으로 브랜칭 (brancing) 및 병합 (merging)이 가능하다는 것입니다.

예시: 외부적인 요소들은 가끔 불가피하게 당신이 하던 일을 그만두게 합니다. 예를 들어, 치명적인 버그가 경고없이 퍼져나가게 생겼습니다. 프로그램에 새로 넣어야 할 기능이 있는데 데드라인은 가까워져 옵니다. 당신이 도움을 요청하고자 했던 개발자는 퇴사할려고 하니 도움을 요청할 수도 없고요. 시간이 촉박한 만큼 하던 일을 멈추고 버그를 고치는 데에 올인을 해야겠지요.

위의 예시와 같이 하던 일을 갑자기 멈추는 것은 일의 생산성을 치명적으로 떨어트립니다. 특히나 지금까지 하던 일과 정 상관없는 부분의 프로그램을 건들어야 할 때 말이죠. 이럴 때, 중앙관리식 VCS를 사용하는 경우엔 버그없는 버전의 프로그램을 새로 다시받아야 할껍니다. 분산관리식 VCS일 경우에는 원하는 버전만 로컬 컴퓨터로 받아내면 되죠.

하지만 클로닝은 작업 중인 디렉토리 포함 그 디렉토리의 히스토리를 어느 선까지는 같이 다운로드 받게 합니다. Git은 최대한 효율성있게 시스템이 디자인되어 있긴하지만, 클로닝 명령어를 쓴다면 프로젝트 파일들이 (비효율적으로) 현재 작업 중인 디렉토리에 전부 다시 생성될 것입니다.

해답: Git은 이런 상황에서 좀 더 빠르고 공간적으로 효율성있게 클로닝을 할 수 있는 명령어를 가지고 있습니다: git branch

이런 환상적인 명령어를 이용하여 디렉토리에 있는 파일들은 탈바꿈을 감행해 이 버전과 저 버전을 넘나들 수 있습니다. 이 변형기법은 버전 사이를 넘나드는 것 외에도 더 많은 것을 할 수 있습니다. 당신의 프로젝트는 브랜칭을 통해 구버전에서 임시버전, 개발버전, 친구들이 보유하고 있는 버전 등으로 아무렇게나 변형할 수 있습니다.

일하는 척 하기 버튼

버튼 하나만 누르면 ("일하는 척 하기 버튼") 게임화면이 최소화되고 엑셀파일이 화면상에 나타나는 기능을 보신 적이 있을겁니다. 이 기능을 활용하면 직장상사의 눈을 속이고 일하던 척 할 수 있지요?

어떤 디렉토리에서:

$ echo "I'm smarter than my boss" > myfile.txt # 난 내 상사보다 똑똑하다
$ git init
$ git add .
$ git commit -m "Initial commit"

우리는 "난 내 상사보다 똑똑하다"라는 내용을 가진 텍스트파일을 Git repository에 만들었습니다. 그리고:

$ git checkout -b boss  # 이 명령어를 사용한 후엔 아무것도 바뀌지 않은 것처럼 보일겁니다.
$ echo "My boss is smarter than me" > myfile.txt
$ git commit -a -m "Another commit"

겉으로 보기에는 그 텍스트파일을 새로운 문장으로 덮어씌우고 commit을 한 것처럼 보일겁니다. 그러나 그건 착각입니다. 다음 명령어를 입력해보세요:

$ git checkout master  # 처음 버전으로 돌아가기

자! 그럼 처음 생성했던 텍스트파일이 돌아왔을 겁니다. 만약에 그 상사가 이 사실을 알아채고 당신의 디렉토리를 살펴본다고 할 때는:

$ git checkout boss  # 아까 두 번째로 만들어놓은 "상사는 나보다 똑똑합니다"라는 메세지를 담은 myfile.txt 파일로 돌아갑니다.

이런 식으로 두 가지 다른버전의 파일 사이를 오갈 수 있습니다. 그리고 각각 따로 commit을 할 수 있지요.

힘든 작업

당신이 어떤 작업을하고 있다고 가정합니다. 작업 도중에 세 버전 전으로 돌아가서 새로운 print 라인을 넣고 테스팅 해보고 싶다는 생각이 들었습니다. 그럴 때엔:

$ git commit -a
$ git checkout HEAD~3

이제 테스팅하고 싶었던 파일에 더하고 싶은 것을 걱정없이 마구 넣어도 됩니다. 이 미친 짓(?)을 commit 해놓을 수도 있습니다. 작업이 다 끝났다면,

$ git checkout master

를 사용해 아까 미친 짓을 하기 전의 작업상태로 돌아올 수 있습니다. Commit하지 않았던 작업들이 같이 딸려 왔다는 것을 확인 (조심!)할 수 있을 겁니다.

아까 그 임시작업 (미친 짓)을 세이브하고 싶다면 어떻게 해야할까요? 쉽습니다:

$ git checkout -b dirty

를 실행하여 그 나뭇가지 (branch) 에서 마스터 나뭇가지로 돌아오기 전에 commit을 하면 됩니다. 그런 후 다시 미친 짓을 할 때의 상태로 돌아가고 싶다면:

$ git checkout dirty

우리는 이 체크아웃이라는 명령어를 전에도 설명했었죠. 여기서는 이 명령어가 어떻게 예전 버전들을 불러오는 지 살펴볼 수 있었습니다: 파일을 원하는 버전으로 돌아가게 할 수 있으나, master 나뭇가지를 우선 벗어나야 하지요. 벗어난 후의 commit은 master 나뭇가지와는 다른 길을 걷게 될 것입니다. 그 길을 나중에 이름도 지어줄 수 있지요.

다시 말하면, 예전 상태 (state)에서 벗어나면 Git은 자동으로 이름이 없는 새로운 나뭇가지로 이동시켜 줍니다. 이 나뭇가지는 *git checkout -b*로 이름을 바꿔 저장해줄 수 있죠.

빠른 코드수정

작업 중에 갑자기 하던 일을 멈추고 '1b6d…'commit에 있는 버그를 고치라고 부탁을 받았다고 생각해 봅시다:

$ git commit -a
$ git checkout -b fixes 1b6d

버그를 다 고친 후에:

$ git commit -a -m "Bug fixed"
$ git checkout master

이제 아까 잠시 중단했던 작업으로 돌아갈 수 있습니다. 버그가 고쳐진 파일도 병합해올 수 있죠:

$ git merge fixes

병합 (Merging)

Git을 제외한 다른 VCS들을 이용할 땐 나뭇가지 (branch)들을 만드는 것은 쉽지만 나뭇가지들을 병합하기는 어려웠습니다. Git에서는 병합작업이 정말 쉽고 병합이 진행되고 있는 중인지 우리도 모르는 사이에 끝날 것입니다.

병합은 전에도 소개했었습니다. 당겨오기 (pull) 명령어는 commit들을 가져와 지금 머물고있는 branch에 병합하여 줍니다. 로컬에서 아무런 작업을 진행하지 않았더라면 *pull*은 현 branch를 빨리 감기 하여 중앙 서버에서 가장 최근의 정보를 가져와 병합합니다. 로컬에서 작업을 한 기록이 있었다면, Git은 자동으로 병합을 시도할 것이고, 병합 중에 버전간의 차질이 있다면 당신에게 친절히 알려줄 것 입니다.

Commit은 보통 하나의 부모 commit’이 (과거의 commit) 있습니다. 그러나 병합을 하게 된다면 하나의 commit이 적어도 '아버지 commit’와 '어머니 commit 이 있다고 생각할 수 있는 것이죠. 그럼 'HEAD~10’은 어떤 commit을 가르키는 걸까요? 부모 commit 두 개 이상이라면 어떤 commit을 받아 순조롭게 작업을 계속 진행할 수 있을까요?

Git은 먼저 시간적으로 제일 먼저 commit되었던 부모를 따르게 설정되어 있습니다. 현재 작업중인 branch가 병합이 실행될 경우 이 branch 자체가 첫번째 부모가 되기때문에 당연한 겁니다: 당신은 언제나 현 나뭇가지에서 가장 최근에 한 작업에만 관심이 있을 가능성이 크기 때문이지요. 다른 나뭇가지에서 한 작업은 다음 일입니다.

탈자 기호 (^)를 이용하서 부모를 수동으로 정해줄 수도 있습니다. 예를 들어 두번째 부모의 기록을 조회하고 싶다면:

$ git log HEAD^2

첫번째 부모의 기록을 조회할 때는 탈자기호 이후의 번호는 생략해도 됩니다. 굳이 보여드리자면:

$ git diff HEAD^

이 표기법은 다른 형식의 표기법과도 병행해서 사용할 수 있습니다:

$ git checkout 1b6d^^2~10 -b ancient

(집중하십시오) 새로운 나뭇가지인 "ancient"를 시작하고 두번째 부모의 첫번째 부모 나뭇가지에서 1b6d로 시작하는 commit과 그 commit 전 10개의 commit을 불러와 줄 것입니다.

방해받지 않는 작업진행

하드웨어 관련작업을 하다보면 현재 작업중인 단계가 완료되어야만 다음 단계 진행이 가능할 것입니다. 자동차를 예로들면 외부로부터 오기로했던 부품들이 도착해야 비로소 수리에 들어갈 수 있겠지요. 프로토타입들은 칩들이 가공되어야 건축이 가능해 지겠죠.

소프트웨어 관련작업도 비슷합니다. 다음 작업이 진행될려면 현재 작업이 이미 발표 및 테스트가 되어있어야 할 겁니다. 어떤 작업들은 당신의 코드가 받아 들여지기 전 검토부터 되어야 겠지요. 그래서 당신은 그 검토가 끌날 때까지는 다음 작업으로 진행하지 못 할것입니다.

하지만 나뭇가지와 병합기능 덕분에 이 규칙을 깨고 파트 1이 완료되기도 전에 파트 2에서 미리 작업을 진행하고 있을 수 있습니다. 파트 1을 commit하고 검토를 위해 어디론가 보냈다고 생각해봅시다. 만약 당신이 Master 나뭇가지에서 작업하고 있었다면, 그 branch에서 다른 branch로 갈아타야합니다:

$ git checkout -b part2

그리곤 파트 2에서 commit을 하며 작업을 계속 진행하세요. 인간은 실수를 많이하는 동물이기에 파트 1으로 다시 돌아가서 무엇인가 고치고 싶을지도 모릅니다. 만약에 당신이 천재적인 프로그래머라면 다음 명령어를 사용할 일은 없겠지요.

$ git checkout master  # 파트 1로 돌아갑니다.
$ fix_problem # 수정 작업
$ git commit -a        # 수정 작업을 commit합니다.
$ git checkout part2   # 파트 2로 다시 갑니다.
$ git merge master     # 아까 파트 1의 수정을 파트 2로 병합합니다.

이 때 즈음이면 이미 파트 1은 허가 받았겠지요.

$ git checkout master  # 파트 1로 돌아갑니다.
$ submit files         # 파일 배포!
$ git merge part2      # 파트 2도 파트 1으로 병합.
$ git branch -d part2  # 파트 2 나뭇가지 삭제.

이제 파트 2의 모든 것과 함께 업데이트 된 master 나뭇가지로 돌아왔습니다.

branch는 갯수의 제한 없이 원하는 만큼 생성할 수 있습니다. 역순으로 branch를 만들 수도
있죠: 만약에 7번의 commit전에 나뭇가지를 하나 만들어 놓았어야 함을
늦게 깨닫았을 때, 다음 명령어를 이용해 보세요:
$ git branch -m master part2  # master 나뭇가지의 이름을 part2로 바꿉니다.
$ git branch master HEAD~7    # 7 commit 전의 상황에서 master 나뭇가지를 새로 만듭니다.

Master 나뭇가지는 이제 part 1만 들어있고, 나머지는 모두 part 2에 들어가게 되었습니다. 그리고 우리는 지금 part 2에서 작업을 하고 있는 중이겠지요; master를 만들면서 master로는 현재 옮겨간 상태가 아닙니다. 처음 보시죠? 여태까지 설명한 예제들에서는 나뭇가지를 만들면서 곧바로 작업공간도 같이 옮겨갔었는데 말이죠. 이런 식으로요:

$ git checkout HEAD~7 -b master  # 나뭇가지를 만들고 바로 작업공간도 그 나뭇가지로 옮긴다.

메들리의 재정리

하나의 branch에서 모든 작업을 끝내고 싶을 수도 있습니다. 작업중인 일들은 혼자만 알고 중요한 commit들만 다른사람들에게 보여주고 싶을 수 있습니다. 그럴경우엔 두 개의 branch를 우선 만드세요:

$ git branch sanitized    # 정돈된 commit을 보여주기 위한 나뭇가지를 만듭니다.
$ git checkout -b medley  # 작업을 하게 될 "메들리" 나뭇가지를 만들어 이동합니다.

버그를 고치던, 어떤 기능을 더하던, 임시코드를 더하던 작업을 진행합니다. 물론 commit을 해가면서 말이죠. 그리고:

$ git checkout sanitized
$ git cherry-pick medley^^

위의 명령어들을 차례로 사용한다면 "메들리" 나뭇가지의 commit들을 "sanitzed" 나뭇가지에 붙입니다. "cherry-pick"명령어를 잘 사용한다면 마지막 결과물에만 첨부된 코드들이 들어있는 branch를 만들 수 있습니다. 잡다한 commit들도 잘 정리되어 있을겁니다.

Branch 관리하기

여태까지 프로젝트에서 생성한 나뭇가지들을 보려면:

$ git branch

기본적으로 "master" 나뭇가지에서 작업을 시작하는 것이 디폴트로 지정되어 있습니다. 그러나 어떤 개발자들은 "master" 나뭇가지는 그대로 냅두고 새로운 나뭇가지를 만들어서 그 곳에서 작업하는 것을 선호합니다.

-d*와 *-m 옵션들은 각각 branch들을 지우거나 branch 이름을 바꿔줄 수 있는 파라메터들 입니다. *git help branch*를 보시면 더욱 자세히 설명되어 있을겁니다 (번역 주: 어차피 영어입니다)

"master" 나뭇가지는 관례적인 이름의 branch일 뿐입니다. 다른 개발자들은 당신의 저장소에 당연히 "master"라는 이름을 가진 branch가 있을 것이라고 생각하겠지요. 그리고 그 branch는 모든 공식적인 자료들이 들어있다고 넘겨짚을 것입니다. 물론 당신은 "master"를 없에거나 새로운 이름을 지정해줄 수 있으나, "master" 나뭇가지를 쓰는 관례를 따르는 것을 추천합니다.

임시 branch

Git을 사용하다보면 당신은 쓸모없는 하루살이 같은 쓸때없는 branch들을 많이 만들고 있다는 사실을 깨달을 것입니다. 이유는 다음과 같겠지요: 그 많은 branch들은 작업의 경과를 저장하기 위해 임시로 만들어놓고 무엇인가 고칠 것이 있을 때 빨리 돌가가기 위해서 쌓아두기만 하고있는 거겠죠.

다른 TV채널에서 무얼하나 확인할 때 잠시 채널을 바꾸는 것과 같은 아이디어입니다. 그러나 리모콘 버튼 몇 개 누르면 되는 것과는 달리, 많은 나뭇가지를 만들고, 설정하고, 병합하고, 나중에 다쓰면 지워야합니다. 다행히도 Git에서는 TV 리모트와 비슷하게 지름길이 있습니다:

$ git stash

이 명령어는 현 버전을 임시저장소 (stash)에 저장해 주고 작업하기 전의 상태로 돌아갑니다. 작업중인 디렉토리는 작업 (편집, 버그고침 등) 하기 전의 상태로 돌아가겠지요. 그리고 임시 (stash)로 돌아가고 싶다면:

$ git stash apply  # 에러 (version conflict)가 날지도 몰라요. 수동적으로 해결하세요.

물론 여러개의 임시저장소 (stash)를 만들수도 있습니다. *git help stash*에 설명이 되어있으니 읽어보세요. 눈치챘을지 모르겠지만, Git은 올바른 임시저장소 (stash) 기능을 쓰게 해주기 위해서 자체적으로 임의 생성된 branch들을 몰래 이용한답니다.

내 방식대로 작업하기

Branch를 이용하는 것이 꼭 필요한지 생각할지도 모르겠습니다. 파일들을 클로닝하는게 제일 빠르고 *cd*를 이용해 디렉토리를 바꿈으로써 branch 사용을 대체하고 싶을지도 모릅니다.

웹브라우저의 예를 들어보겠습니다. 여러개의 창 아니면 여러개의 탭을 지원하는 이유는 무엇일까요? 여러 이용자들의 작업방식을 존중하여 주기 위해서랍니다. 어떤 이용자들은 웹브라우저 창 하나만 열고 여러 탭을 열어서 작업하는 방식을 추구합니다. 다른 이용자들은 반대의 형식으로 작업하는 것을 추구할지도 모르죠: 여러개의 창을 만들고 탭이 없이 작업하는 것을 말이죠. 또 어떤 이용자들은 이 두 방법들을 섞어서 작업하는 걸 선호할지도 모릅니다.

Branch들은 마치 작업중인 디렉토리의 탭과 같습니다. 클로닝은 새로운 브라우저 창을 여는 것과 같은 것이죠. 이 두가지 방법은 모두 빠르고 로컬에서 진행됩니다. 그러니 당신에게 맞는 방법을 찾아보는 건 어떨까요? Git은 당신이 원하는 대로 일하게 도와줄 것입니다.

히스토리 레슨

분산관리식 시스템을 택한 Git은 개발자들이 history관리를 용이하게 할 수 있게 해줍니다. 그러나 프로그램의 과거를 들춰내려면 조심하세요: 당신이 소유하고 있는 파일들만 다시쓰기 하세요. 세계 각국의 나라들이 누군가 어떤 잘못을 하나하면 누가했는지 끝임없이 따지는 것처럼 만약 한 개발자가 당신이 가지고 있는 파일과 기록 (history)이 다른 파일들을 클론하여 갔을 때 추후 병합시 문제가 생길지도 모릅니다.

어떤 개발자들은 파일의 수정기록들이 절대로 조작되면 안되는 것이라고 믿고 있습니다. 반면에 어떤 개발자들은 수정 기록들이 깨끗하게 정리되어 보여져야 할 것만 선택하여 보여져야 한다고 합니다. Git은 이렇게 다른 성향의 개발자들을 모두 포용할 수 있습니다. 클로닝, branch, 병합과 같은 기능들이 당신이 어떤 개발자 타입이던 당신의 일처리를 도와줄 것입니다. 어떻게 영리하게 사용하는지는 당신에게 달려있죠.

오류 수정합니다

방금 commit을 했는데, 그 commit에 달린 메세지를 바꾸고 싶다고요? 그렇다면:

$ git commit --amend

위 명령어를 사용하면 마지막으로 한 commit의 메세지를 바꿀 수 있습니다. 파일을 더하는 것을 잊어버리고 commit을 했다고요? *git add*를 사용하고서 위의 명령어를 사용하세요.

마지막으로 했던 commit에 편집을 더 하고 싶으신가요? 그렇다면 작업 후에 다음 명령어를 쓰세요.

$ git commit --amend -a

… 더 있습니다

이제 전보다 더 꼬인 상황을 마주했다고 생각합시다. 우선 당신이 긴 시간동안 작업해서 많은 commit을 하였다고 가정해봅시다. 그러나 당신은 그 commit들의 난잡한 구성이 마음에 들지 않습니다. 그리고 몇몇 commit 메세지들을 다시쓰고 싶다면:

$ git rebase -i HEAD~10

위 명령어를 사용한다면 당신의 작업용 에디터에 지난 열 개의 commit이 출력될 것입니다. 샘플을 보자면:

pick 5c6eb73 Added repo.or.cz link
pick a311a64 Reordered analogies in "Work How You Want"
pick 100834f Added push target to Makefile

여기서는 'log’와는 달리 가장 오래된 commit 부터 가장 최근의 commit의 순서로 나열되어 출력되었습니다.여기서는 5c6eb73 가 가장 오래된 commit이고 100834f이 가장 최근 commit 이죠. 그리고:

예를들어, 두번째 행의 pick’을 'squash 명령어로 바꾸어 봅니다:

pick 5c6eb73 Added repo.or.cz link
squash a311a64 Reordered analogies in "Work How You Want"
pick 100834f Added push target to Makefile

저장 후 프로그램을 종료하면, Git은 a311a64를 5c6eb73로 병합시킵니다. squash (짓누르기)는 지정된 commit을 바로 다음 commit으로 밀어붙어버린다고 생각하시면 됩니다.

또, Git은 로그메세지들을 합친후 나중에 편집할 수 있게 해주기도 합니다. fixup 명령어를 사용하면 이런 절차를 하지않아도 됩니다; 짓눌려진 (squash 된) 로그메세지들은 간단히 삭제되기 때문입니다.

*edit*을 이용하여 어떤 commit을 마킹해두었다면, Git은 같은 성향의 commit들 중에 가장 오래전에 했던 commit의 작업상태로 당신을 되돌려 보냅니다. 이 상태에서 아까 전 말했듯이 편집작업을 할 수도 있고, 그 상태에 맞는 새로운 commit을 만들수도 있습니다. 모든 수정작업이 만족스럽다면 다음 명령어를 사용해 앞으로 감기를 실행할 수 있습니다.:

$ git rebase --continue

Git은 다음 *edit*까지 아니면 아무런 *edit*이 없다면 현재 작업상태까지 commit을 반복실행 할것입니다.

새로운 rebase를 포기할 수도 있습니다:

$ git rebase --abort

그러니 commit을 부지런하게 자주하십시오: 나중에 rebase를 사용하여 정리할 수 있으니까요.

로컬에서의 수정작업

어떤 프로젝트를 진행하고 있습니다. 당신의 컴퓨터에서 commit을 하며 작업을 하다가 이제 공식적인 프로젝트 파일들이 있는 branch와 동기화 해야합니다. 이런 절차는 메인 branch에 올리기전에 거쳐야 할 과정이지요.

그러나 당신의 로컬 Git클론은 당신 혼자만 이해할 수 있는 수많은 기록이 뒤죽박죽 섞여있을 것입니다. 아무래도 개인적인 기록이 깨끗하게 정리되어 공식적인 기록만 볼 수 있게 된다면 좋겠지요:

위에서 설명했듯이 git rebase 명령어가 이 작업을 해줄것입니다. --onto 플래그를 사용하여 상호작용을 피할수도 있습니다.

*git help rebase*를 확인해서 좀 더 자세한 예를 한번 봐보세요. Commit을 나눌 수 있다는 걸 알게될 것입니다. 여러 branch들을 재정리할 수도있죠.

*rebase*는 유용한 명령어입니다. 여러가지 실험을 하기전에 *git clone*으로 복사본을 만들어두고 놀아보세요.

기록 다시쓰기

가끔은 어떤 그룹사진에서 포토샵으로 몇 사람지우는 기능과 같은 명령어가 필요할지도 모릅니다. 스탈린식 스타일로 사람을 무자비하게 지우는 명령어 말입니다. 예를들어 이제 어떤 프로젝트를 대중에게 공개할 시간이 왔다고 가정합니다. 그러나 어떤 파일들은 일반 유저들이 보지 못하도록 하고싶습니다. 당신 크레딧카드 번호를 실수로 썻다던지 했다면 더욱 더 그러고 싶겠지요. 이런 경우, 파일을 지우는 것 만으로는 부족합니다. 예전 commit으로 파일을 지워진 파일을 복구하는 것이 가능하기 때문이죠. 당신은 이 파일을 모든 commit으로 부터 없에야 할 것입니다:

$ git filter-branch --tree-filter 'rm top/secret/file' HEAD

*git help filter-branch*를 보세요. 여기서는 본 예시에 대해 설명하고 있고 더 빠른 방법을 제시하여 줄 것입니다. 대체적으로 *filter-branch*은 하나의 명령어만으로도 대량의 파일기록을 변화시킬 수 있을 것입니다.

그리고 +.git/refs/original+ 디렉토리는 이렇게 만든 변화가 오기 전의 기록을 보여줄 것입니다. *filter-branch* 명령어가 어떻게 작용했는지 확인하고, 필요하다면 이 디렉토리를 지우면 됩니다.

마지막으로, 당신의 클론을 새로운 버전의 클론으로 바꾸시면 됩니다.

기록 만들기

어떤 프로젝트를 Git으로 옮겨오고 싶다고요? 다른 VCS에서 옮겨오는 것이라면, 어떤 개발자가 이미 프로젝트의 기록을 Git으로 쉽게 옮겨오는 스크립트를 써두었을지도 모릅니다.

아니라면, 특정 포맷으로 텍스트를 읽어 Git 기록을 처음부터 작성하여 주는 git fast-import 명령어를 확인해 보세요. 대체적으로 한번에 간편하게 프로젝트를 Git에서 사용할수 있게 해줄겁니다.

예를들어, /tmp/history 같은 임시파일에 다음 텍스트를 붙여넣기 해보세요:

commit refs/heads/master
committer Alice <alice@example.com> Thu, 01 Jan 1970 00:00:00 +0000
data <<EOT
Initial commit.
EOT

M 100644 inline hello.c
data <<EOT
#include <stdio.h>

int main() {
  printf("Hello, world!\n");
  return 0;
}
EOT


commit refs/heads/master
committer Bob <bob@example.com> Tue, 14 Mar 2000 01:59:26 -0800
data <<EOT
Replace printf() with write().
EOT

M 100644 inline hello.c
data <<EOT
#include <unistd.h>

int main() {
  write(1, "Hello, world!\n", 14);
  return 0;
}
EOT

그리고 이 임시파일로 Git repository를 만들어보세요:

$ mkdir project; cd project; git init
$ git fast-import --date-format=rfc2822 < /tmp/history

가장 최근 버전을 가져오고 싶다면:

$ git checkout master .

git fast-export 명령어는 아무 Git repository를 결과물이 사람들이 읽을 수 있는 포맷으로 바꾸어 주는 git fast-import 포맷으로 바꾸어 줍니다. 이 명령어들은 텍스트 파일들을 텍스트 파일 전용채널을 통해서 repository로 밀어넣기 해줍니다.

어디서부터 잘못되었을까?

당신은 몇 달전에는 잘 작동되던 프로그램이 갑자기 안 된다는 것을 발견했습니다. 아놔! 이 버그는 어디서부터 생긴 것일까요? 개발을 하면서 테스팅을 종종했더라면 진작에 알아챘을텐데요.

이미 그러기엔 너무 늦었습니다. 그러나 commit이라도 자주했다는 가정하에 Git은 이러한 짐을 덜어줄 수 있습니다:

$ git bisect start
$ git bisect bad HEAD
$ git bisect good 1b6d

Git에서 한 프로젝트를 자체적으로 테스팅을 실행합니다. 그리고 버그가 발견된다면:

$ git bisect bad

버그가 더이상 없다면 위 명령어에서 "bad"를 "good"으로 바꾸세요. Git은 good 버전과 bad 버전 사이로 당신을 데려갈 겁니다. 물론 버그를 찾을 확률은 높아지지요. 몇 번이렇게 반복하다보면 결국엔 버그를 일으킨 commit을 찾아낼 수 있게 도와줄 것입니다. 버그찾기를 완료했다면 다시 처음 작업상태로 (이젠 버그가 없겠지요) 돌아가야 겠지요:

$ git bisect reset

수동으로 테스팅하는 것보단, 다음 명령어로 테스팅을 자동화할 수 있습니다:

$ git bisect run my_script
Git 은 기존 대비할 스크립트에 약간의 변화를 주어서 이것이 좋은 변화인지
안 좋은 변화인지 체크합니다: 좋은 변화는 0으로 무시해야할 변화는 125로
안 좋은 변화는 1과 127사이의 번호로 테스팅을 종료합니다. 마이너스 숫자는
이분화  (bisect)를 강제종료하지요.
당신은 이것보다 더 많은 일을 할 수 있습니다. 도움말은 이분화를 시각화해주는 방법과,
이분화 기록을 다시보는 방법, 이미 확인된 이상없는 변화들은 건너띄어 테스팅을 가속 시켜주는 기능들을 배우실 수 있습니다.

누가 망가뜨렸을까?

다른 VCS들과 같이 Git은 누군가를 탓할 수 있는 기능이 있습니다:

$ git blame bug.c

이 명령어를 사용하면 누가 언제 마지막으로 어느 부분을 작업하였는지 표시하여 줍니다. 다른 VCS들과는 달리 모든 작업은 오프라인에서 진행됩니다.

나의 개인경험담

중앙관리식 VCS에서는 파일들의 기록 변경은 어려울 뿐만아니라 관리자만이 변경할 수 있습니다. 클론, branch 만들기와 병합하기는 네트워크를 통해서만 할 수 있는 작업들입니다. 브라우징 기록보기, commit하기 역시 중앙관리식 VCS에서는 네트워크를 통해야만 합니다. 어떤 시스템에서는 네트워크 연결이 되어야지만 자기 자신이 작업한 기록을 보거나 편집할 수 있습니다.

중앙관리식 VCS는 개발자의 수가 늘어남에 비례해서 더 많은 네트워크 통신을 요구하기 때문에 오프라인에서 작업하는 것보다 비경제적일 수 밖에 없습니다. 그리고 제일 중요한 것은 모든 개발자들이 고급명령어들을 적재적소에 쓰지 않는다면 모든 작업이 어느정도는 무조건 느릴 수 밖에 없다는 것입니다. 극적인 경우에는 아주 기본적인 명령어 역시 잘못하면 느려질 수 밖에 없습니다. 무거운 명령어를 써야한다면 일의 효율성은 나쁜영향을 받을 수 밖에 없습니다.

저는 직접 이런 상황들을 겪어보았습니다. Git은 제가 사용한 가장 먼저 사용한 VCS였죠. 저는 Git의 여러기능들을 당연하다 생각하고 금방 적응하였습니다. 저는 당연히 다른 VCS들 역시 Git이 제공하는 기능들을 가지고 있을 것이라고 생각하였습니다: VCS를 선택하는 것은 텍스트 에디터나 웹 브라우저를 선택하는 것과 같은 맥락일 것이라고 생각하였습니다.

제가 나중에 강제로 중앙관리식 VCS를 사용하게 되었을땐 완전 쇼킹이였죠. 불안정한 인터넷연결은 Git을 사용할 때 중요치 않습니다. 그러나 이러한 인터넷연결은 로컬디스크에서 작업하는 것 만큼은 효율적 일수는 없죠. 그리고 저는 어떤 명령어는 연결 딜레이를 고려함에 따라 습관적으로 쓰지 않고있는 걸 시간이 지나며 깨달았습니다. 이런 습관은 제가 원하는 방식대로 작업을 할 수 없게하는 방해물들이었죠.

어쩔 수 없이 느린 명령어를 사용할 때는 저의 작업효율에 치명타를 입히기 일쑤였죠. 네트워크로 처리되야하는 작업이 완료되길 기다리면서 이메일 확인 및 다른 문서작업을 하며 시간을 때웠습니다. 그러다가 원래하던 작업에 돌아가면 다시 무엇을 했었는지 기억을 해내는데 시간이 많이 허비된 경험이 잦았습니다. 인간은 환경변화에 적응을 할 수는 있으나 그 적응이 결코 빠르진 않죠.

일을 하면서 발생하는 아이러니한 비극도 존재했죠: 네트워크 상황이 원활하지 않을 것이라는 걸 아는 개발자들은 미래에 딜레이를 줄이기위해 지금 하는 작업들이 오히려 현재 트래픽을 더 잡아먹을 수가 있다는 것입니다. 모든 개발자들이 네트워크를 원활하게하는 노력을 할수록 오히려 서로에게 해가 될 수있다는 것입니다. 이게 무슨 아이러니한 일입니까? == Git은 멀티플레이어 ==

제가 과거에 단독 개발자였던 시절부터 Git을 사용해 오고있었습니다. 그 당시엔 여태까지 소개했던 많은 명령어들 중 *pull*과 *clone*정도만 사용하여 같은 프로젝트를 여러 디렉토리에 저장하는데 사용하였습니다.

시간이 지난 후 Git에 제가 만든 코드를 올리고 싶었고 다른 개발자들이 한 작업도 반영하고 싶었습니다. 저는 전 세계의 많은 개발자들을 관리하는 방법을 배워야 했습니다. 다행히도 이런 일을 도와주는 것은 Git의 가장 큰 힘입니다. Git이 존재하는 이유이기도 하지요.

난 누굴까?

각 commit은 작성자의 이름과 작성자의 이메일주소를 저장합니다. 이 목록은 *git log*를 사용해 조회할 수 있습니다. 기본설정 상 Git은 시스템에 이미 기본으로 세팅이 되어있는 정보를 이용해 작성자의 이름과 이메일주소를 저장합니다. 그러나 수동으로 이름과 이메일주소를 설정하려면:

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

를 사용하여 지정해 주십시오.

현재 사용중인 repository에만 한정적으로 사용할 수 있는 이름이나 이메일을 설정하려면 위 명령어에서 *--global*을 사용하지마세요.

SSH, HTTP를 통한 Git 사용

웹 서버에 관한 SSH 접근권한을 보유하고 있다고 합니다. 그러나 Git은 아직 설치되어 있지않다고 가정합니다. 기존 프로토콜만큼 효율적이진 않겠지만, Git은 HTTP를 통해 데이터 교환이 가능합니다.

우선 기본적으로 컴퓨터에 Git을 다운받아서 설치합니다. 그리고 웹 디렉토리에 저장소를 만듭니다:

$ GIT_DIR=proj.git git init
$ cd proj.git
$ git --bare update-server-info
$ cp hooks/post-update.sample hooks/post-update

Git의 예전 버전에선 복사를 지시하는 명령어가 안 들을 수 있습니다. 그렇다면:

$ chmod a+x hooks/post-update

이제 아무 클론에서 SSH를 통해 당신의 작업을 업로드할 수 있습니다:

$ git push web.server:/path/to/proj.git master

다른 사람들은 당신의 작업을 다운받으려면 다음 명령어를 쓰면 될겁니다:

$ git clone http://web.server/proj.git

모든 것은 Git을 통한다

서버나 인터넷 연결없이 저장소를 동기화시키고 싶다고요? 긴급하게 수정할 것이 발견되었다고요? git fast-export 그리고 git fast-import 명령어들은 repository를 하나의 파일로 묶어주거나 그 하나의 파일을 repository로 되돌려 줄 수 있는 것을 배웠습니다. 다양한 매개체를 통해서 repository의 파일들을 옮길 수 있지만, 정말 효율적인 방법은 git bundle 명령어를 쓰는 것입니다.

보내는 이가 '묶음 (bundle)'을 만듭니다:

$ git bundle create somefile HEAD
그리고 다른 장소로 그 묶음, +somefile+을 어떻게든 옮겨야한다고 가정합시다: 이메일로 보내야할수도 있고, USB드라이브로 보내거나, *xxd* 프린트로 뽑아서 전송하던지, OCR 스캐너로 전송하던지, 전화로 직접 이야기하던지, 연기로 신호를 보내던지 등 어떻게든 보내야합니다. 파일을 받을 사람들은 이 묶음으로부터의 commit을 다음 명령어를 이용하여 간단히 받을 수 있습니다:
$ git pull somefile
파일을 받는 사람들은 빈 repository에서도 이 명령어를 사용할 수 있습니다. 파일의
사이즈가 작아 보임에도 불구하고 +somefile+ 은 저장소의 본 모습을 담고 있습니다.

큰 프로젝트에서는 묶음만들기를 좀 더 효율적으로 하기위해서 버전차이만을 묶어줍니다. 예를 들어서 "1b6d"의 hash가 달린 commit 이 가장 최근에 보내는 이와 받는 이 사이에 공유된 commit이라고 가정해 봅니다:

$ git bundle create somefile HEAD ^1b6d

너무 자주 이렇게 한다면, 어떤 commit이 가장 최근 것인지 기억하지 못할 수 있습니다. 도움말에서는 태그를 이용해 이런 문제점들을 피하라 명시합니다. 풀어서 말하자면 어떤 묶음을 보낸 후에는:

$ git tag -f lastbundle HEAD

그리고 새로운 묶음을 만들어 줍니다:

$ git bundle create newbundle HEAD ^lastbundle

패치: 세계적 통화

패치는 컴퓨터와 인간이 쉽게 알아들을 수 있는 언어로 파일의 변화를 텍스트로 표현할 수 있는 방법입니다. 전 세계 어떤 개발 공간에서나 이런 식으로 파일의 변화를 시도합니다. 어떠한 VCS를 쓰던간에 개발자들에게 패치를 이메일로 보낼 수도 있습니다. 그 개발자들이 그 이메일을 읽을 수만 있다면 그들은 당신이 편집을 한 작업기록을 손쉽게 볼 수 있을겁니다. 즉, 온라인용 Git repository를 만들 필요가 없다는 말이지요.

첫 장에서 본 명령어를 다시 한번 해봅시다:

$ git diff 1b6d > my.patch

위 명령어는 간단한 패치를 생성하여 이메일로 보낼수 있게 도와줍니다. Git 저장소에서 다음을 따라해보세요:

$ git apply < my.patch

위 명령어를 사용하여 패치를 적용시킵니다.

작성자의 이름과 싸인이 기록되어야하는 좀 더 공식적인 환경에서는 그에 상응하는 패치 (1b6d 이후의 작업)를 만들기위해 다음 명령어를 사용합니다:

$ git format-patch 1b6d

이렇게 만든 파일묶음은 *git-send-email*을 사용하여 보낼 수 있습니다. 보내고싶은 commit 묶음을 수동으로 지정해줄 수도 있습니다:

$ git format-patch 1b6d..HEAD^^

받는 쪽에서는 이메일을 받을 때는 받은 txt형태의 패치 파일을 저장한 후:

$ git am < email.txt
이 명령어는 새로받은 패치를 적용시키고 작성자의 정보가 포함된 새로운 commit을 만듭니다.

브라우저 상으로 이메일을 수신한다면, 당신의 이메일 클라이언트에서 첨부된 패치의 본 코드의 형태를 확인해야 할수도 있습니다.

mbox-를 기반으로하는 이메일 클라이언트는 약간의 문제점들이 있습니다. 그러나 이런 방식의 클라이언트를 쓸만한 사람이라면 손쉽게 튜토리얼을 읽지않고도 해결할 수 있을것입니다.

죄송합니다. 주소를 옮겼습니다

Repository를 클로닝한 후 *git push*나 *git pull*을 사용하면 원래의 URL에서 해당 명령어를 실행합니다. Git은 어떤 원리로 이렇게 하는 것일까요? 그 비밀은 클론을 만들때 생선된 config 옵션에서 찾을 수 있습니다. 한번 볼까요?:

$ git config --list

remote.origin.url 옵션은 URL 소스를 통제합니다; "origin"은 원래 repository에 붙여진 별명이라고 보면됩니다. Branch에 "master"라고 이름이 붙듯이 말이죠. 그말은 이 이름을 바꾸거나 지울 수 있는데 할 필요는 없다는 것입니다.

제일 처음 사용하던 repository가 옮겨지면, URL을 수정해 주어야 합니다:

$ git config remote.origin.url git://new.url/proj.git

brach.master.merge 옵션은 *git pull*로 당겨올 수 있는 branch를 설정하여 줍니다. 처음으로 클론을 생성하였을때, 그 클론의 branch는 그 클론을 만들어온 repository의 현재 사용중인 repository와 같게 설정이 되어있습니다. 그렇기 때문에 현재 작업 헤드가 다른 branch로 옮겨갔었다고 하더라도, 추후의 당겨오기는 본래의 branch를 따를 수 있게 해줄 것 입니다.

본 옵션은 처음에 +branch.master.remote+옵션에 기록되어 있는 클론의 대상 repository에만 적용됩니다. 다른 저장소에서 당겨오기를 실행한다면, 구체적으로 어떤 나뭇가지에서 당겨오길 원하는지 설정해주어야 합니다:

$ git pull git://example.com/other.git master

이 것은 왜 전에 보여드렸던 push*와 *pull 예제에 다른 argument가 붙지 않았었는지 설명하여 줍니다.

원격 branch들

어떠한 repository를 클론할 때에는 그 클론의 모든 branch를 클론하게 됩니다. Git은 이 사실을 은폐하기에 당신은 클론을 하면서 몰랐을지도 모릅니다: 그러니 당신은 직접 Git에게 물어보아야 합니다. 이 설정은 원격 repository에 있는 branch들은 당신의 branch들과 꼬이게하는 일을 없게 해줍니다. 그래서 초보자들이 Git을 사용할 수 있는 것이고요.

다음 명령어를 이용하여 숨겨진 branch들을 포함해서 모두 나열합니다:

$ git branch -r

당신은 다음과 비슷한 결과물들을 보게될 것입니다:

origin/HEAD
origin/master
origin/experimental

이 결과는 각 행마다 원격 repository의 HEAD와 branch를 보여주며, 다른 Git 명령어들과 함께 사용될 수 있습니다. 예를 들면, 당신은 지금 많은 commit을 하였다고 먼저 가정합니다. 그러고는 가장 최근에 가져온 버젼과 비교를 하고싶다고 생각해봅니다. SHA1 해쉬를 찾아서 확인할 수도 있지만 다음 명령어로 더 간단히 비교할 수 있습니다:

$ git diff origin/HEAD

아니면 "experimental" 나뭇가지가 지금 어떠한 상태인지 알아낼 수도 있습니다.

$ git log origin/experimental

다수의 원격 repository

당신 외의 두명의 개발자가 프로젝트를 공동으로 진행하고 있다고 가정합니다. 그리고 그 둘의 작업상황을 주시하고 싶습니다. 당신은 다음 명령어를 사용함으로써 하나 이상의 repository를 추적할 수 있습니다:

$ git remote add other git://example.com/some_repo.git
$ git pull other some_branch

이제 두번째 repository의 branch로 병합을 시도하였으며 모든 repository의 모든 branch에 대한 접근권한이 생겼습니다.

$ git diff origin/experimental^ other/some_branch~5

그러나 내 작업과 관련없이 버전의 변화를 비교해내는 방법은 무엇일까요? 풀어말하자면 그들의 branch를 보는 동시에 그들의 작업이 내 작업에 영향받지않게 하고싶다는 것입니다. 그렇다면 간단하게 당겨오기 보다는:

$ git fetch        # 태초의 repository로부터 물어옵니다. 디폴트 명령어.
$ git fetch other  # 다른 개발자의 repository를 물어옵니다.

작업기록들만을 가져오는 명령어들입니다. 현재 작업중인 디렉토리는 영향을 받지않을 것이지만, 로컬 사본을 가지고 있기에 우리는 이제 어느 repository의 어떤 branch라도 Git 명령어를 사용하여 활용할 수 있습니다.

Pull은 간단히 풀어서 설명하면 fetch(물어오기) 후 *merge(병합하기)*를 합친 하나의 고급명령어라고 말할 수 있습니다. 우리는 마지막 으로 한 commit을 현재 작업에 병합하길 원하기 때문에 주로 *pull(당겨오기)*를 사용하게 될 것입니다; 위에 설명한 상황은 특수상황이지요.

*git help remote*에는 원격 repository를 삭제하는 방법, 특정 branch를 무시하는 방법 외에 많은 것을 볼 수 있습니다.

나만의 취향

저는 작업을 할때 다른 개발자들이 제가 당겨오기를 실행할 수 있게 항시 준비해두는 것을 선호합니다. 어떠한 Git 호스팅 서비스는 클릭 한 번만으로도 쉽게 이를 행할 수 있게 도와주는 것도 있습니다.

어떤 파일꾸러미를 물어온 후에는 Git 명령어들을 사용하여 프로젝트가 잘 정리되어 있길 빌며 변화 기록을 조회합니다. 그러고는 저의 작업을 병합합니다. 그 후 제 작업이 맘에 들 경우 메인 저장소에 밀어넣기 합니다.

제가 다른 사람들로부터 많은 도움을 받는 스타일은 아니지만, 이러한 제 작업방식을 추천드리고 싶습니다. 다음 링크를 한 번보세요. Linus Torvalds의 블로그 포스팅.

Git의 세상에 거주하는 것은 패치 파일들을 만들어 배포하는 것보다 더 효율적입니다. Git은 단순한 버전관리 외에도 작업을 행한 사람의 이름, 이메일주소, 작업날짜를 같이 기록하여줍니다. == Git 마스터링 ==

지금까지 배운것만으로도 당신은 git help 페이지를 자유롭게 돌아다니며 거의 모든 것을 이해할 수 있을 것입니다. 그러나 어떠한 문제를 풀기위해 어느 한 가지의 알맞는 명령어를 찾는 것은 아직 성가실 수 있습니다. 그런 문제에 대해 제가 도와줄 수 있을 것 같습니다: 아래는 제가 Git을 사용하며 유용하게 썼던 몇가지 팁들입니다.

소스 배포

제 프로젝트에서 Git은 제가 저장 및 공개하고 싶은 파일들을 정확히 추적하여 줍니다.

$ git archive --format=tar --prefix=proj-1.2.3/ HEAD

바뀐 것은 꼭 commit

Git에게 무엇을 추가, 삭제 및 파일이름을 바꾸었는지 일일히 알려주는 것은 귀찮은 짓일지도 모릅니다. 대신에 당신은 다음 명령어를 쓸 수있습니다:

$ git add .
$ git add -u

Git은 현재 작업중인 디렉토리에 있는 파일들을 자동으로 살피며 자세한 사항들을 기록하여 줍니다. 위의 두번째 명령어 (git add -u) 대신에 'git commit -a’를 사용하여 그 명령어와 commit을 동시에 해낼 수 있습니다. *git help ignore*를 참고하여 특별히 지정된 파일을 무시하는 방법을 알아보십시오.

위의 모든 것을 한 줄의 명령어로 실행할 수 있습니다.

$ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove

-z*와 *-0 옵션은 파일이름이 이상한 문자를 포함하고 있을 때 생길 수 있는 여러가지 문제점들을 처리하여 줍니다. 이 옵션들은 무시된 파일들을 포함하여줌으로써 '-x’아니면 '-X’을 같이 써주어야 할 것입니다.

내 commit이 너무 클 경우?

Commit을 한지 시간이 좀 많이 지난 상황입니까? 코딩을 너무 열심히 한 나머지 버전컨트롤하는 것을 깜빡했나요? 프로젝트에 여러가지 연관성없는 수정을 한 상태입니까?

걱정하지말고:

$ git add -p

당신이 만든 모든 수정작업에 대하여 Git은 어떠한 것들이 바뀌였는지 코드로 보여주며 당신에게 다음에 실행할 commit에 부분적인 코드가 포함될 사항인지 물어볼 것입니다. "y"와 "n"를 이용하여 대답할 수 있습니다. 물론 이 대답을 당장하지 않아도 됩니다; "?"로 좀 더 알아보십시요.

모든 수정작업이 마음에 든다면:

$ git commit

위의 간단한 명령어를 사용하여 직접 선택한 작업만을 commit합니다. 이 상황에선 반드시 *-a*옵션을 생략하시길 바랍니다. 그렇지 않으면 Git은 모든 수정작업을 commit할 것입니다.

만약에 여러군데 다른 디렉토리에 많은 수정작업을 해놓았다면 어떻게 할까요? 수정된 사항을 하나씩 검토하는 작업은 정말 귀찮은 짓입니다. 이럴땐 *git add -i*를 사용합니다. 몇 번의 타이핑만으로도 특정 파일의 수정작업을 검토할 수 있게됩니다. 또는 *git commit --interactive*를 사용하여 작업 중 자동으로 작업 후 commit하는 방법도 있을 수 있습니다.

인덱스: Git의 스테이징 구역

여태까지 Git의 중요한 기능인 'index’를 피해왔지만 이제 한 번 살펴본 시간이 온 것 같습니다. 인덱스는 임시적인 스테이징 구역 (번역주:책갈피처럼)으로 보면 됩니다. Git은 당신의 프로젝트와 프로젝트의 기록 사이에 데이터를 직접 옮기는 경우는 드뭅니다. 대신, Git은 인덱스에 파일을 쓰며 그리고 그 파일들을 마지막 목표지점에 카피하여 줍니다.

예를 들어 commit -a*는 원래 두단계 과정을 거치는 하나의 명령어입니다. 첫번째 단계에서는 현 작업상황의 스냅샷을 찍어 모든 파일들을 인덱스하는 과정을 거칩니다. 두번째 단계에서는 방금 찍은 스냅샷을 영구적으로 보관하게 됩니다. *-a 옵션을 쓰지않고 commit을 하는 것은 이 두번째 과정만 실행하는 일입니다. 그렇기에 git add 같은 명령어를 쓴 후에 commit을 하는 것이 당연한 이야기가 되겠지요.

대체적으로 인덱스에 관한 컨셉트는 무시하고 파일기록에서 직접적으로 쓰기와 읽기가 실행된다는 개념으로 이해하면 편합니다. 이런 경우에는 우린 인덱스를 제어하는 것 처럼 좀 더 세부한 제어를 하기 원할것입니다. 부분적인 스냅샷을 찍은 후 영구적으로 이 '부분스냅샷’을 보존하는 것이죠.

머리(HEAD)를 잃어버리지 않기

HEAD 태그는 문서작업시 보이는 커서처럼 마지막 commit 포인트를 가르키는 포인터 역할을 합니다. Commit을 실행할 때마다 물론 HEAD도 같이 앞으로 움직이겠지요. 어떤 Git 명령어들은 수동으로 HEAD를 움직일 수 있게 해줍니다. 예를 들면:

$ git reset HEAD~3

위 명령어를 사용한다면 HEAD를 commit을 3번 하기 전으로 옮깁니다. 이 후 모든 Git 명령어는 가지고 있던 파일은 현재상태로 그대로 두되 그 3번의 commit을 하지 않은 것으로 이해하죠.

그러나 어떻게 해야 다시 가장 최근으로 돌아갈 수 있을까요? 예전에 했던 commit들은 미래에 대해서 아무것도 모를텐데 말이지요.

원래의 HEAD의 SHA1을 가지고 있다면:

$ git reset 1b6d

그러나 이 것을 어디에도 써두지 않았었더라도 걱정하지 마십시오: Git은 이런 경우를 대비해서 원래의 HEAD를 ORIG_HEAD로 어딘가에는 저장하여 둡니다. 그러고는 다음명령어를 사용하여 미래로 안전하게 돌아올 수 있지요:

$ git reset ORIG_HEAD

HEAD-헌팅

ORIG_HEAD로 돌아가는 것만으로는 충분하지 않을지도 모릅니다. 당신은 방금 엄청나게 큰 실수를 발견하였고 아주 오래전에 했던 commit으로 돌아가야 할지 모릅니다.

기본적으로 Git은 branch를 수동으로 삭제하였더라도 2주의 시간동안 commit을 무조건 저장하여 둡니다. 문제는 돌아가고 싶은 commit의 hash를 찾는 일입니다. '.git/objects’의 모든 hash 값을 조회하여 얻어걸릴 때까지 해보는 방법이 있긴합니다만, 여기 좀 더 쉬운 방법이 있습니다.

Git은 모든 commit의 hash를 '.git/logs’에 저장해 둡니다. 하위 디렉토리 'refs’은 모든 branch의 활동기록을 저장하여두고 동시에 'HEAD’파일은 모든 hash 값을 저장하고 있습니다. 후자는 실수로 마구 건너 뛴 commit들의 hash도 찾을 수 있게 해줍니다.

reflog 명령어는 당신이 사용하기 쉬운 유저인터페이스로 log파일들을 표현하여 줍니다. 다음 명령어를 사용하여 보십시오.

$ git reflog

hash를 reflog으로부터 자르고 붙여넣기 보다는:

$ git checkout "@{10 minutes ago}"

아니면 5번 째 전에 방문했던 commit으로 돌아갈수도 있습니다:

$ git checkout "@{5}"

좀 더 많은 정보는 *git help rev-parse*의 "재편집 구체화하기" 섹션을 참고하십시오.

아까 언급한 commit의 2주살이 생명을 수동으로 연장하여 줄 수 있습니다. 예를 들어:

$ git config gc.pruneexpire "30 days"

위 명령어는 30일이 지난 후에 지워진 commit들 역시 영구적으로 삭제된다는 의미입니다. 그러고는 *git gc*가 실행되지요.

*git gc*가 자동실행되는 것을 꺼줄 수도 있습니다:

$ git config gc.auto 0

이 경우에는 *git gc*를 수동적으로 실행시켜 commit들을 삭제할 수 있지요.

Git을 좀 더 발전시키는 방법

진정한 UNIX와 같이 Git의 디자인은 다른 프로그램들의 GUI, 웹 인터페이스와 같은 하위파트들과 호환이 됩니다. 어느 Git 명령어들은 유명인사의 어깨위에 서있는 것처럼 Git 그 자체가 스크립팅 언어로 사용될 수도 있습니다. 조금만 시간을 투자하면 Git은 당신의 기호에 더 알맞게 바꿀수 있습니다.

한 가지 트릭을 소개하자면 자주 사용할것 같은 명령어들을 짧게 만들어줄 수 있는 방법이 있습니다:

$ git config --global alias.co checkout
$ git config --global --get-regexp alias  # 현재 설정한 명령어들의 '가명'을 표기해줍니다.
alias.co checkout
$ git co foo                              # 'git checkout foo'와 같은 것입니다.

또 다른 트릭은 현재 작업중인 branch의 이름을 작업표시창에 표시하여주는 명령어도 있습니다.

$ git symbolic-ref HEAD

위 명령어는 현재 작업중인 branch 이름을 표기하여 줍니다. 실제로는 "refs/heads/"를 없애고 잠재적으로 일어날 수 있는 에러들을 무시하는걸 추천드립니다:

$ git symbolic-ref HEAD 2> /dev/null | cut -b 12-

contrib 하위 디렉토리는 유용한 Git 툴들이 저장되어있는 장소이기도 합니다. 시간이 지나면 이곳에 있는 툴들은 공식적으로 인정받아 고유명령어가 생기기도 하겠지요. Debian과 Ubuntu에서는 이 디렉토리는 +/usr/share/doc/git-core/contrib+에 위치하고 있습니다.

앞으로 +workdir/git-new-workdir+디렉토리에 방문할 일도 많을 것입니다. 시스템링크 기술을 통해서 이 스크립트는 원래의 repository와 작업기록을 공유하는 새로운 작업 디렉토리를 생성하여 줍니다:

$ git-new-workdir an/existing/repo new/directory

새롭게 생성된 디렉토리는 클론으로 봐도 무방하며 일반클론들과의 한가지 차이점은 어느 한 곳에서 작업을 하던 두 개의 디렉토리는 앞으로 계속 싱크를 진행하며 같은 기록을 가지게 된다는 것입니다. 즉, 병합, 밀어넣기, 당겨오기를 해줄 필요가 없어지는 것이지요.

용감한 스턴트

Git은 요즘 유저들이 데이터를 쉽게 지우지 못하도록 하고 있습니다. 그러나 몇가지의 상용적인 명령어를 통해서 이런 Git만의 방화벽 쯤은 쉽게 뚫어버릴 수 있지요.

Checkout: Commit하지 않은 작업들은 checkout을 할 수없습니다. 방금작업한 모든 것들을 없던 일로하고 그래도 굳이 commit을 진행하고 싶다면:

$ git checkout -f HEAD^

반면에 checkout을 할 위치를 수동으로 설정하여 준다면 Git의 방화벽은 처음부터 작동하지 않을 것입니다. 설정해준 위치는 조용히 덮어씌우게 됩니다. 그러니, 이런 방식으로 checkout을 할 때에는 주의 하십시오.

Reset: 리셋은 commit되지 않은 작업이 있으면 실행되지 않을 것입니다. 그래도 강제로 하고싶다면:

$ git reset --hard 1b6d

Branch: 방금한 작업을 잃어버릴 것같으면 Git은 branch가 지워지지 않게합니다. 그래도 하고싶다면:

$ git branch -D dead_branch  # -d 대신 -D

비슷한 방식으로, commit을 안한 작업이 있어서 move명령어를 통해서 덮어씌우기가 안될경우에는:

$ git branch -M source target  # -m 대신 -M

체크아웃과 리셋과는 다르게 위의 두 명령어는 데이터를 직접 삭제하진 않습니다. 모든 변경기록은 .git 하위 디렉토리에 남게되고 필요한 hash는 '.git/logs’에서 찾을 수 있습니다 (위의 "HEAD-헌팅" 섹션 참고). 기본설정상, 이 기록들은 2주 동안은 삭제되지 않습니다.

Clean: 몇 git 명령어들은 추적되지 않은 파일들을 망쳐놓을까봐 실행이 안되는 경우가 종종 있습니다. 만약에 그 파일들이 삭제되도 된다는 확신이 선다면, 가차없이 다음 명령어를 사용하여 삭제하십시오:

$ git clean -f -d

이 후에는 위 모든 명령어들은 다시 잘 실행되기 시작할 것입니다!

원치않는 commit들을 방지하기

바보같은 실수들은 내 repository를 망쳐놓곤 합니다. 그 중에서도 제일 무서운 것은 *git add*를 쓰지 않아서 작업해놓은 파일들을 잃어버리는 것이지요. 그나마 코드 뒤에 빈 공간을 마구 넣어놓는다던지 병합에서 일어날 수 있는 문제들을 해결해 놓지않는 것은 애교로 보입니다: 별로 문제가 되는 것들은 아니지만 남들이 볼 수 있는 repository에서는 보여주기 싫습니다.

hook 을 사용하는 것과 같이 제가 바보같은 짓을 할 때마다 경고를 해주는 기능이 있다면 얼마나 좋을까요:

$ cd .git/hooks
$ cp pre-commit.sample pre-commit  # 예전 Git 버젼에서는: chmod +x pre-commit

이제는 아까 설명했던 애교스러운 실수들이 발견될 때 Git은 commit을 도중에 그만 둘것입니다.

이 가이드에서는 pre-commit 앞에 밑에 써놓은 코드를 넣음으로써 혹시 있을지도 모르는 바보같은 짓을 방지하였습니다.

if git ls-files -o | grep '\.txt$'; then
  echo FAIL! Untracked .txt files.
  exit 1
fi

많은 git 작업들은 hook과 상호작용합니다; git help hooks*를 참조하십시오. 우리는 "HTTP를 통한 Git"을을 설명할때 *post-update hook을 활성화시켰습니다. HEAD가 옮겨질때마다 같이 실행되지요. Git over HTTP 예제에서는 post-update 스크립트가 통신에 필요한 Git을 업데이트 했었습니다.

비밀을 벗겨내다

Git의 안을 들여다보고 Git이 어떻게 작동하는지 알려드리겠습니다. 너무 디테일한 것들은 알아서 빼놓고 설명해드리겠습니다. 그래도 자세히 알고 싶은 분들은: the user manual로 가시길 바랍니다.

보이지 않는 능력

Git은 어떻게 눈에 띄지않게 강력한 툴일 수 있을까요? 습관적으로 하게되는 commit과 병합을 제외하고, VCS자체가 컴퓨터에 설치되지 않은 것 같아보일 때가 있습니다.

다른 VCS들은 사용할때 쓸때없이 많은 절차와 검열 등으로 고생할 수 있습니다. 파일의 보안상태가 '읽기전용’으로 세팅되어 작업을 하려고 할 때마다 중앙서버에 승인을 요청해야 할 경우도 빈번합니다. 그리고 VCS의 유저들이 많아질수록 업무처리 속도가 현저히 떨어질수도 있습니다. 그리고 중앙서버나 네트워크가 다운 될 경우에는 아무런 작업을 할 수 없죠.

반면에, Git은 당신의 로컬컴퓨터 디렉토리에 '.git’디렉토리를 형성하여 그곳에 작업기록을 하게됩니다. 그 기록은 온전히 당신만의 것이죠. 그렇기에 오프라인 상태에서도 작업을 끊기지 않고 진행할 수 있습니다. 그리고 작업중인 파일에 대해 모든 권한을 가지고 있게 해주죠.

진실성

대부분의 사람들은 크립토그래피를 어떤 정보를 비밀스럽게 숨기는 정도로 생각합니다. 그러나 크립토그래피의 진정한 목적은 정보를 보안하는 것이죠. 크립토그래피로 보호되는 hash는 데이터가 공격받거나 지워질 위험으로부터 보호해줍니다.

SHA1 hash는 고유의 160-비트 ID 번호로 생각하시면 됩니다. 그리고 이 번호는 당신이 평생, 또는 열번의 삶을 살 정도의 시간에, 쓸 byte에 부여되는 번호이기에 보안이 철저합니다.

SHA1 hash 자체 역시 byte로 구성되어 있기에 SHA1 hash의 hash를 만드는 것도 가능합니다. 이건 생각보다 유용한 기능입니다: 'hash chains’를 한번 살펴보세요. 우리는 나중에 Git이 이 기능을 어떻게 사용해서 효율적으로 데이터를 보호하는지 살펴보겠습니다.

짧게 말하자면, Git은 당신의 모든 데이터를 `.git/objects`섭디렉토리에 저장합니다. 그리고 보통의 파일이름대신 각 파일에 지정된 ID를 통해서 이 파일들을 찾을 수 있습니다. 그렇기에 Git은 보통의 파일시스템을 뭔가 굉장히 효율적인 데이터베이스로 변화시켜줍니다.

똑똑함

어떻게 Git이 당신이 파일의 이름을 변경할 때 Git에게 이름을 바꾼다 말한적도 없는데 알수 있을까요? *git mv*를 실행할수도 있겠지만 그것은 *git rm*을 사용하고 *git add*를 사용하는 것과 같습니다.

Git은 단순화된 지침으로 재설정된 파일명을 찾아내고 버전사이의 카피들을 만들어냅니다. Git은 코드들이 옮겨지고 카피되고 지워질 경우를 다 알아낼수 있죠. 모든 경우의 수를 다 알수는 없겠지만, Git은 대부분을 알아채고 있고 이 자동화된 기능은 날이 갈수록 발전하고 있습니다.

색인화

Git은 Git이 트랙킹하는 모든 파일들의 크기, 생성된 시간, 마지막 편집시간을 색인 (index)를 이용해서 기록하여 둡니다. 만약에 어떤 파일에 작업하여 변화가 생겼다면 Git은 현 파일상태와 인덱스에 저장되어있는 상태를 비교하여 파일의 변화를 감지합니다. 만약에 서로간의 차이가 없다면 Git은 그 파일이 가장 최신버전으로 업데이트되었다고 생각하고 더 이상 읽지 않습니다.

인덱스 정보를 읽는 작업은 파일을 읽는 것보다는 훨씬 빠르게 진행되니 당신이 한 작업들은 Git이 아주 빠르게 업데이트 해줄 것입니다.

인덱스는 마치 중간 대기 구역과 같다고 말씀드린 적이 있습니다. 왜 그렇게 얘기 했을까요? Add 명령어는 파일들을 순전히 업데이트 시켜 데이터베이스에 업데이트 하지만 *commit*은 (별도의 옵션을 사용하지 않는다는 전제하에) 자체적인 로컬데이터베이스에 있는 파일들에 대한 commit만 진행하지요.

Git의 근원

Linux Kernel Mailing List post 에서 Git의 역사를 나열하여 설명해줍니다. Git의 역사학자들에게 정말 흥미있는 웹사이트지요.

오브젝트 데이터베이스

데이터의 모든 버전은 '오브젝트 데이터베이스’에 보관되며, 이 데이터베이스는 `.git/objects`의 섭디렉토리에 상주하고 있습니다. `.git/`에 있는 다른 파일들은 더 적은 정보를 담고 있지요 (인덱스, branch 이름, 태그, 설정 정보, 로그, head commit의 위치 등). 오브젝트 데이터베이스는 Git의 기본이지만 우아하고 또 Git의 힘의 원천입니다.

`.git/objects’에 있는 파일들은 각각 오브젝트입니다. 그리고 크게 세가지 오브젝트로 나눌수 있습니다: blob 오브젝트, tree 오브젝트, and commit 오브젝트.

Blob 오브젝트

첫째, 마술을 보여드리겠습니다. 우선 아무 파일 이름을 선택하십시오. 빈 디렉토리에서 :

$ echo sweet > YOUR_FILENAME
$ git init
$ git add .
$ find .git/objects -type f

.git/objects/aa/823728ea7d592acc69b36875a482cdf3fd5c8d 을 보게 될 것입니다.

저는 파일이름을 알지도 못하는데 이걸 제가 어떻게 알까요? 왜냐하면

"blob" SP "6" NUL "sweet" LF

의 SHA1 hash 는 aa823728ea7d592acc69b36875a482cdf3fd5c8d 이고, SP 는 공간이며, NUL 는 0 바이트이고, LF는 라인피드이기 때문입니다. 직접 확인해보시려면 다음 명령어를 입력하세요:

$ printf "blob 6\000sweet\n" | sha1sum

Git은 콘텐츠 주소를 지정하는 것이 가능합니다: 파일은 파일 이름에 따라 저장되지 않습니다. 대신에 hash로 저장이 되며 이런 정보는 blob 오브젝트라고 불리우는 곳에 저장되어 있지요. 우리는 hash를 파일 내용에 대한 고유 ID로 생각할 수 있습니다. 그래서 어떤 의미에서 우리는 파일의 내용에 따라 주소를 지정하는 것으로 생각이 가능합니다. 초기`blob 6`는 오브젝트 타입과 크기를 저장해 놓은 header나 다름없습니다; 단지 내부 부기를 단순화합니다.

따라서 저는 당신이 보게 될 것을 쉽게 예측할 수 있었습니다. 파일 이름은 관련이 없습니다. 내부 데이터만을 이용해 blob 오브젝트를 구성하는게 가능합니다.

그러면 당신은 동일한 파일이 어떻게되는지 궁금 할 수 있습니다. 우선 아무런 파일 이름을 사용해 복사본을 추가해보십시오. + .git / objects +의 내용은 몇개의 복사본을 추가해도 동일합니다. Git은 데이터를 한 번만 저장합니다.

별개로, + .git / objects + 내의 파일은 zlib로 압축되어 있으므로 그들을 직접 보지 마십시오. zpipe -d 를 통해 필터링을 해서 보시던지, 아니면 다음 명령어를 실행해 보시면 됩니다.:

$ git cat-file -p aa823728ea7d592acc69b36875a482cdf3fd5c8d

주어진 오브젝트를 예쁘게 인쇄해줍니다.

Tree 오브젝트

그러나 파일 이름은 어디에 간거죠? 그들은 어떤 단계에서 어딘가에 저장되어야합니다. Git은 커밋 중에 파일 이름을 찾습니다.

$ git commit  # Type some message.
$ find .git/objects -type f

이제 3 개의 개체를 보게 될 것입니다. 이번에는 당신이 선택한 파일 이름에 부분적으로 의존하기 때문에 두 개의 새 파일이 무엇인지 제가 말씀드릴 수 없습니다. ‘`rose’'를 파일이름을 지정한 것으로 가정하여 진행하겠습니다. 그렇게 설정하시지 않은 경우 기록을 다시 작성하여 그렇게 지정한 것처럼 보이게 할 수 있습니다.

You should now see 3 objects. This time I cannot tell you what the 2 new files are, as it partly depends on the filename you picked. We’ll proceed assuming you chose “rose”. If you didn’t, you can rewrite history to make it look like you did:

$ git filter-branch --tree-filter 'mv YOUR_FILENAME rose'
$ find .git/objects -type f

그럼 이제 +.git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9+를 보실 수 있을겁니다. 왜냐하면 이것이 SHA1 hash이기 때문입니다:

"tree" SP "32" NUL "100644 rose" NUL 0xaa823728ea7d592acc69b36875a482cdf3fd5c8d

다음을 입력하여 이 파일에 실제로 위 내용이 포함되어 있는지 확인하십시오.

$ echo 05b217bb859794d08bb9e4f7f04cbda4b207fbe9 | git cat-file --batch

zpipe으로 이 hash를 확인하는 것이 아마 쉬운 방법일 겁니다:

$ zpipe -d < .git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9 | sha1sum

Hash 확인은 cat-file로는 좀 어려울 수도 있습니다. 왜냐하면 압축되지 않은 원래의 파일보다 더 많은 파일을 포함하고 있을 수 있기 때문입니다.

This file is a tree object: a list of tuples consisting of a file type, a filename, and a hash. In our example, the file type is 100644, which means ‘rose` is a normal file, and the hash is the blob object that contains the contents of `rose’. Other possible file types are executables, symlinks or directories. In the last case, the hash points to a tree object.

If you ran filter-branch, you’ll have old objects you no longer need. Although they will be jettisoned automatically once the grace period expires, we’ll delete them now to make our toy example easier to follow:

$ rm -r .git/refs/original
$ git reflog expire --expire=now --all
$ git prune

실제 프로젝트의 경우 일반적으로 이와 같은 명령을 피해야합니다. 이 명령어들은 백업들을 지울 수 있기 때문이죠. 깨끗한 repository를 원한다면 일반적으로 새로운 클론을 새롭게 만드는 것이 좋습니다. 그리고 +.git+을 조작할때는 주의하십시오: 만약에 여러가지 커맨드들이 동시에 실행 중이거나 갑작스러운 정전이 발생하면 안되잖아요. 일반적으로 refs는 *git update-ref -d*로 삭제해야합니다. 그래도 +refs / original+를 수동으로 제거하는 것이 안전하긴 하지만요.

Commit 오브젝트

우리는 3 개의 오브젝트들 중 2 개를 설명했습니다. 세 번째는 'commit’오브젝트입니다. 이 오브젝트의 내용은 commit 메시지와 날짜 및 시간에 따라 다릅니다. 여기에있는 것과 일치 시키려면 약간의 조정이 필요합니다.

$ git commit --amend -m Shakespeare  # Commit 메시지를 바꿉니다.
$ git filter-branch --env-filter 'export
    GIT_AUTHOR_DATE="Fri 13 Feb 2009 15:31:30 -0800"
    GIT_AUTHOR_NAME="Alice"
    GIT_AUTHOR_EMAIL="alice@example.com"
    GIT_COMMITTER_DATE="Fri, 13 Feb 2009 15:31:30 -0800"
    GIT_COMMITTER_NAME="Bob"
    GIT_COMMITTER_EMAIL="bob@example.com"'  # 타임스탬프와 저자를 바꿉니다.
$ find .git/objects -type f

그럼 이제 .git/objects/49/993fe130c4b3bf24857a15d7969c396b7bc187 를 보게될것 입니다.

"commit 158" NUL
"tree 05b217bb859794d08bb9e4f7f04cbda4b207fbe9" LF
"author Alice <alice@example.com> 1234567890 -0800" LF
"committer Bob <bob@example.com> 1234567890 -0800" LF
LF
"Shakespeare" LF

이전과 마찬가지로 zpipe 또는 cat-file을 실행하여 직접 확인할 수 있습니다. 이것은 첫 번째 commit이므로 부모 commit이 없지만 이제 나중에 commit을 할때마다 항상 부모 commit을 식별하는 한 줄이 포함될것 입니다.

마술과 분간이 안되는 프로그램

Git의 비밀은 너무 단순 해 보입니다. 몇 시간 안에 몇 개의 셸 스크립트를 혼합하고 C 코드를 추가하여 몇 시간 만에 만들 수있는 것 같아 보입니다. 결국에 Git은 견고성을 위해 잠금 파일과 fsync로 장식 된 기본 파일 시스템 작업과 SHA1 해싱의 혼합으로 구성된 프로그램입니다. 실제로 이것은 Git의 초기 버전을 정확하게 설명합니다. 그럼에도 불구하고 공간을 절약하기위한 독창적인 패킹 트릭과 시간을 절약하기위한 독창적 인 인덱싱 트릭을 포함해 이제 우리는 Git이 버전 컨트롤을 위한 완벽한 데이터베이스로 거듭나게 되는지 알게되었습니다.

예를 들어, 오브젝트 데이터베이스 내의 파일이 디스크에 의해 손상된 경우 오류가 발생하면 hash가 더 이상 일치하지 않아 문제를 알려줍니다. 기존 hash에 다른 hash를 지정해 주면서, 우리는 모든 수준에서 무결성을 유지합니다. Commit은 부분적으로 파일을 관리하지않고 전체적으로 프로젝트를 관리하여줍니다. Commit의 hash를 계산하고서 모든 tree, blob 과 부모 commit들을 저장한 후에 데이터베이스에 저장합니다. 오브젝트 데이터베이스는 정전과 같은 예상치 못한 방해에 영향을받지 않습니다.

우리는 Git으로 가장 사악한 적도 물리칠수 있습니다. 만약에 누군가가 아주 예전 버전의 프로젝트에서 파일 내용을 은밀하게 수정한다고 생각해봅시다. 오브젝트 데이터베이스를 정상 상태처럼 보이게 유지하려면 해당 데이터베이스의 해시도 변경해야 할겁니다. 이 말은 해당 파일을 참조하는 tree 오브젝트의 hash도 변경해야한다는 의미입니다. 그리고 차례로 그러한 tree를 포함하는 모든 commit 오브젝트의 hash도 변경해야 한다는 거지요. 이러면 해당 파일 이후의 commit모두 변경을 해야한다는 말이됩니다. 이것은 공식 헤드의 hash는 나쁜 repository의 hash와 달라질 수 밖에 없다는 것입니다. 그러니 일치하지 않는 hash의 흔적을 따라 나쁜의도로 변경된 파일을 정확히 찾아 낼 수 있습니다. 처음에 손상된 commit도 마찬가지로 찾을 수 있습니다.

짧게 말하자면, 마지막 commit을 나타내는 20 바이트가 안전하다면 Git repository를 임의로 아무에게도 들키지 않게 조작하는 것은 불가능합니다.

Git의 유명한 기능은 또 어떻습니까? branch 만들기? 병합하기? 태깅하기? 세부 사항. 현재 head는 + .git / HEAD + 파일에 보관됩니다. commit 오브젝트의 hash를 포함합니다. Commit 중에 hash가 업데이트됩니다. Branch는 거의 동일합니다: 그것들은 +.git/refs/heads+에 저장된 파일들 입니다. 태그도 +.git/refs/tags +에 있지만 다른 명령어들로 업데이트됩니다. == 부록 A: Git의 약점들 ==

Git을 소개하면서 저는 Git의 약점들을 몇 개 숨기긴 했습니다. 몇가지 약점들은 script나 hook을 통해 해결할수 있고, 몇가지는 프로젝트를 수정하면서 해결할수 있고, 그 외의 약점들은 현 시점에선 그냥 앉아서 기다리고 있을 수 밖에 없습니다. 그러기 싫으시다면 직접 도와줘보십쇼!

SHA1 약점

시간이 지나면 해커들은 SHA1의 약점들을 더 많이 발견하게 될겁니다. 이미 hash에서의 충돌을 찾아내는 건 가능한 일이지요. 몇 년 안에는 Git repository를 위해할 수 있는 연산능력을 가진 일반컴퓨터도 있을 수 있습니다.

Git이 그런 일이 일어나기전에 hash관련 기능들을 발전할 수 있었으면 좋겠어요.

Microsoft Windows

Git을 Microsoft Windows에서 사용하는 건 성가실 수 있습니다:

Git과 연관없는 파일들

만약에 당신의 프로젝트가 굉장히 크고, 쓸때없는 파일들이 많이 들어있는 상태이고, 상시로 바뀌는 상태라면, Git은 하나의 파일을 트랙킹하지 않기에 다른 VCS보다 유용하지 않을 수 있습니다. Git은 프로젝트 단위로 트랙킹을 하기 때문입니다. 이건 Git의 장점입니다.

그래도 만약 하나의 파일만을 트랙킹하기 원하다면 프로젝트를 여러개의 파트로 분리해두는 겁니다. 여러개의 파트로 분리해도 git submodule 명령어를 이용하면 하나의 repository를 유지할 수 있을겁니다.

누가 어떤 작업을 하는거지?

몇몇의 VCS는 유저들로 하여금 작업하기전에 파일들을 강제로 마킹 시킵니다. 이러한 강제성은 중앙서버와 연결하는데 귀찮은 절차이지만 두개의 장점이 있습니다:

  1. 버전의 차이 (Diff)를 체크하는데 매우 빠릅니다. 마킹 된 파일만 검사하면 되니까요.

  2. 유저는 어떤 사람이 어떤 작업을 하는지 중앙서버를 조회하면 간단히 알아낼 수 있습니다.

Git으로도 이렇게 하는게 가능합니다. 그러나 그렇게 하기위해선 코딩이 좀 필요하니 프로그래머의 도움이 좀 필요할 수 있겠군요.

파일 히스토리

Git은 프로젝트 전체를 트랙킹하기 때문에 어떤 한 파일의 히스토리를 재건설하는데 다른 (하나의 파일만 트랙킹하는) VCS들보다 느릴 수 있습니다.

그렇게 심하게 느려진다는 것은 아니고 오히려 Git의 장점들이 이 하나의 단점을 상쇄하고도 남습니다. 예를 들어 'git checkout’은 'cp -a’보다 빠르고 프로젝트 전체의 변화를 압축화하는 것이 파일 하나하나씩 압축하는 것보다 효율적입니다.

태초의 클론

만약에 어떠한 프로젝트의 히스토리가 길 경우, 클론을 만드는 것은 다른 VCS들의 'checking out’보다 컴퓨터의 용량을 더 차지할 수 있습니다.

그러나 길게보면 클론이 checking out보다 나을 것입니다. 클로닝 이후 다른 명령어들은 매우 빠르고 오프라인으로도 진행이 가능하니까요. 그러나 어떠한 경우에는 좀 더 히스토리가 얕은 클론을 --depth 명령어를 통해 만드는 것이 더 나은 선택일 수 있습니다. 이렇게 만들어진 클론은 작업실행 속도가 빠르겠지만 몇몇 기능들이 제외되어 있을 수 있습니다.

불완전한 프로젝트들

Git은 파일에 작업을 더 많이할 수록 그 작업량에 대비해 빠르게 Version Control을 할 수 있도록 하기위해 쓰여진 프로그램입니다. 인간은 하나의 버전에서 다음 버전으로 작업을 할때 소량의 작업만 진행할 수 있죠. 예를들어, 한줄짜리 코드에 있는 버그를 고친다던가, 새로운 기능을 넣는다던가, 코멘트를 코드에 단다거나 말이죠. 그런데 만약 commit과 commit 사이에 작업량이 방대하게 클 경우 그 파일의 히스토리는 비례해서 커질 수 밖에 없겠죠.

VCS는 이것에 대해 아무것도 할 수 없습니다. 일반 Git 유저들은 그 부풀어진 파일들을 곧대로 받아들일 수 밖에 없겠죠.

그러나 왜 방대한 작업량이 필요했는지에 대해 알아볼 필요는 있습니다. 파일 포맷이 바뀌어서 그랬을수도 있죠. 소량의 작업은 소량의 변화를 주기마련입니다.

아니면 데이터베이스나 백업자료실를 구축해놓는 것이 이런 방대한 프로젝트들을 진행하는 데에 있어 VCS보다 적합할수도 있습니다. 예를 들어 VCS는 어떤 웹캠에서 주기적으로 찍은 이미지를 관리하는 데에는 적합하지 않습니다.

만약에 파일들이 매번 변화하고 있고 각각의 변화에 무조건 버젼번호를 매겨야겠다 한다면 Git을 중앙서버처럼 쓰는 방법밖에 없습니다. 개발자들은 상대적으로 가벼운 클론을 만들면 되죠. 이렇게 일을 진해하면 물론 단점도 있을겁니다. 픽스들을 패치로 배포해야하고 Git tool들이 들어먹지 않을 수도 있어요. 근데 이렇게라도 일을 진행해야하는게 맞는 방법일 수 있습니다. 아무도 히스토리가 매우 긴 프로젝트들을 곧대로 받긴 싫어하거든요.

다른 예시로는 큰 바이너리 파일들을 수행하는 펌웨어들에 기반한 프로젝트를 진행할 경우입니다. 펌웨어의 히스토리는 유저들에게 별로 흥미로운 소재는 아니고, 업데이트들은 압축률이 매우 좋지 않습니다. 그래서 펌웨어들을 재구성할떄는 repository의 크기가 매우 커지는 경우가 있죠.

이럴때에는 모든 소스코드들이 Git repository에 저장되어 있는 편이 좋고, 바이너리 파일들은 따로 보관되어야 할 것 입니다. 이 일을 좀 더 쉽게 진행하기 위해서 Git 유저가 어떤 파일에 대해 클론을 만들수있고 *rsync*를 할 수 있으며, 가벼운 클론을 만들수있는 코드를 배포하는 것이 좋을 수 있습니다.

글로벌 카운터

몇몇 중앙관리식 VCS들은 새로운 commit이 받아들여질때마다 임의의 양의정수를 보존합니다. Git은 양의정수보다 나은 hash를 써서 commit을 관리합니다.

그러나 어느 사람들은 아직도 양의정수로 commit관리를 하길 추구합니다. 다행히도 Git에 추가프로그래밍을하여 Git repository에서 양의정수를 1씩 더하는 방식으로 commit을 관리할수도 있습니다.

어느 클론 파일이나 양의정수를 사용하여 commit을 관리할 수 있습니다. 그러나 이건 아무짝에도 쓸모가 없죠. 중앙 repository만 이 숫자를 쓸꺼니까요.

빈 (empty) 섭디렉토리

빈 섭디렉토리는 트랙킹되지 않습니다. 그러나 더미 파일을 만들어서 트랙킹하게 편법을 쓸 수 있죠.

현 버전의 Git으로써 이 문제점은 Git의 약점입니다. Git이 다시 수면위로 올라가고 더 많은 사람들이 사용하게 될수록 이런 약점도 메꿔나가 지겠죠.

태초의 commit

보통의 컴퓨터공학자들은 숫자를 셀 때 0부터 세지 1부터 세지 않습니다. 하지만 안타깝게도 commit의 횟수를 셀때 git은 컴퓨터공학자들처럼 숫자를 세지 않습니다. Git의 그 많은 명령어들은 commit이 태초적으로 한번 이루어지기 전까지는 실행되지 않을겁니다. Branch들을 rebasing 할때나 이럴 경우에는 예외일 수도 있습니다.

애초에 Git은 태초의 commit으로부터 많은 혜택을 받습니다: repostiory가 생성되자마자 HEAD는 20 zero bytes의 스트링으로 자동설정됩니다. 이 특별한 commit은 빈 나무로 표현합니다. 빈 나무는 부모님 commit도 없습니다. 한마디로 근본이 없는 친구를 태초의 commit으로 부릅니다.

그리고 태초의 commit후, git log를 로드했을때 Git이 오류를 내지 않고 단순히 commit이 하나도 안 되었다고 알려줄 것입니다.

태초의 commit은 한마디로 zero commit의 양자같은 컨셉트입니다.

그러나 이런 구성은 가끔 문제를 야기합니다. 여러개의 branch가 모두 태초의 commit을 하고 이제 branch를 병합시켜야 할때, rebasing은 아마 유저 본인이 수동으로 버전청소를 하라고 할수도 있습니다.

별난 인터페이스

A와 B commit이 있을때, "A..B" 와 "A…B" 표현의 차이는 명령어가 두개의 종점이나 범위가 입력되기를 기다리고 있느냐 마느냐입니다. git help diff 와 *git help rev-parse*를 참조하십시오.

부록 B: 이 가이드를 번역하기

다음 절차들을 따라하셔야 제 프로그램이 자동으로 빠르게 HTML과 PDF 버전의 번역본을 만들수 있습니다.

우선 원본이 되는 소스를 클론하시고 폴더를 만드시는데, 폴더명을 IETF에 기재되어 있는 태그를 쓰시기 바랍니다: 다음 링크에 들어가셔서 확인하세요, the W3C article on internationalization. 예를 들자면, 영어의 태그는 "en"이고, 일본어는 "ja"이며, 국어는 "ko"입니다. 그리고 새로운 디렉토리에서 "en"에 들어있는 원본 txt를 번역하시면 됩니다. (번역주: 영어를 못하시고 국어를 하실수 있다면 본 가이드를 쓰시면 되겠지만 그럴 확률이 거의 없겠죠)

For instance, to translate the guide into Klingon, you might type:

$ git clone git://repo.or.cz/gitmagic.git
$ cd gitmagic
$ mkdir tlh  # "tlh" is the IETF language code for Klingon.
$ cd tlh
$ cp ../en/intro.txt .
$ edit intro.txt  # Translate the file.

and so on for each text file.

Edit the Makefile and add the language code to the TRANSLATIONS variable. You can now review your work incrementally:

$ make tlh
$ firefox book-tlh/index.html

Commit your changes often, then let me know when they’re ready. GitHub has an interface that facilitates this: fork the "gitmagic" project, push your changes, then ask me to merge.