-
1. 시작하기
-
2. Git의 기초
- 2.1 Git 저장소 만들기
- 2.2 수정하고 저장소에 저장하기
- 2.3 커밋 히스토리 조회하기
- 2.4 되돌리기
- 2.5 리모트 저장소
- 2.6 태그
- 2.7 Git Alias
- 2.8 요약
-
3. Git 브랜치
-
4. Git 서버
- 4.1 프로토콜
- 4.2 서버에 Git 설치하기
- 4.3 SSH 공개키 만들기
- 4.4 서버 설정하기
- 4.5 Git 데몬
- 4.6 스마트 HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 또 다른 선택지, 호스팅
- 4.10 요약
-
5. 분산 환경에서의 Git
- 5.1 분산 환경에서의 워크플로
- 5.2 프로젝트에 기여하기
- 5.3 프로젝트 관리하기
- 5.4 요약
-
6. GitHub
- 6.1 계정 만들고 설정하기
- 6.2 GitHub 프로젝트에 기여하기
- 6.3 GitHub 프로젝트 관리하기
- 6.4 Organization 관리하기
- 6.5 GitHub 스크립팅
- 6.6 요약
-
7. Git 도구
- 7.1 리비전 조회하기
- 7.2 대화형 명령
- 7.3 Stashing과 Cleaning
- 7.4 내 작업에 서명하기
- 7.5 검색
- 7.6 히스토리 단장하기
- 7.7 Reset 명확히 알고 가기
- 7.8 고급 Merge
- 7.9 Rerere
- 7.10 Git으로 버그 찾기
- 7.11 서브모듈
- 7.12 Bundle
- 7.13 Replace
- 7.14 Credential 저장소
- 7.15 요약
-
8. Git맞춤
- 8.1 Git 설정하기
- 8.2 Git Attributes
- 8.3 Git Hooks
- 8.4 정책 구현하기
- 8.5 요약
-
9. Git과 여타 버전 관리 시스템
- 9.1 Git: 범용 Client
- 9.2 Git으로 옮기기
- 9.3 요약
-
10. Git의 내부
- 10.1 Plumbing 명령과 Porcelain 명령
- 10.2 Git 개체
- 10.3 Git Refs
- 10.4 Packfile
- 10.5 Refspec
- 10.6 데이터 전송 프로토콜
- 10.7 운영 및 데이터 복구
- 10.8 환경변수
- 10.9 요약
-
A1. 부록 A: 다양한 환경에서 Git 사용하기
- A1.1 GUI
- A1.2 Visual Studio
- A1.3 Eclipse
- A1.4 Bash
- A1.5 Zsh
- A1.6 Git in Powershell
- A1.7 요약
-
A2. 부록 B: 애플리케이션에 Git 넣기
-
A3. 부록 C: Git 명령어
- A3.1 설치와 설정
- A3.2 프로젝트 가져오기와 생성하기
- A3.3 스냅샷 다루기
- A3.4 Branch와 Merge
- A3.5 공유하고 업데이트하기
- A3.6 보기와 비교
- A3.7 Debugging
- A3.8 Patch 하기
- A3.9 Email
- A3.10 다른 버전 관리 시스템
- A3.11 관리
- A3.12 Plumbing 명령어
A2.2 부록 B: 애플리케이션에 Git 넣기 - Libgit2
Libgit2
다른 방법으로는 Libgit2 라이브러리가 있다. Libgit2는 Git에 의존하지 않는다. 일반 프로그램에서 사용하기 좋게 API를 설계했다. http://libgit2.github.com에서 내려받을 수 있다.
먼저 API가 어떻게 생겼는지 구경해보자.
// Open a repository
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");
// Dereference HEAD to a commit
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;
// Print some of the commit's properties
printf("%s", git_commit_message(commit));
const git_signature *author = git_commit_author(commit);
printf("%s <%s>\n", author->name, author->email);
const git_oid *tree_id = git_commit_tree_id(commit);
// Cleanup
git_commit_free(commit);
git_repository_free(repo);
첫 두 라인은 Git 저장소를 여는 코드다.
git_repository
타입은 메모리에 있는 저장소 정보에 대한 핸들을 나타낸다.
git_repository_open
메소드는 워킹 디렉토리나 .git
폴더 경로를 알 때 사용한다.
저장소 경로를 정확히 모를 때는 git_repository_open_ext
메소드로 찾는다. git_clone
메소드와 관련된 메소드는 원격에 있는 저장소를 로컬에 Clone 할 때 사용한다. 그리고 git_repository_init
은 저장소를 새로 만들 때 사용한다.
rev-parse 문법을 사용하는 두 번째 코드는 HEAD가 가리키는 커밋을 가져온다. (자세한 내용은 브랜치로 가리키기 참고)
git_object
포인터는 Git 개체 데이터베이스에 있는 개체를 가리킨다.
git_object
는 몇 가지 “자식” 타입의 “부모” 타입이다. 이 “자식” 타입들은 git_object
에 해당하는 부분에 대해서는 메모리 구조가 같다. 그래서 맞는 자식이라면 이렇게 캐스팅해도 안전하다.
git_object_type(commit)
처럼 호출하면 GIT_OBJ_COMMIT
을 리턴한다. 그래서 git_commit
포인터로 캐스팅해도 된다.
그다음 블록은 커밋 정보를 읽는 코드다.
마지막 라인의 git_oid
는 Libgit2에서 SHA-1 값을 나타내는 타입이다
이 예제를 보면 몇 가지 코딩 패턴을 알 수 있다.
-
포인터를 정의하고 그 포인터와 Ref 스트링을 주고 Libgit2 메소드를 호출한다. 메소드는 정수 타입의 에러 코드를 리턴한다.
0
값이 성공이고 다른 값은 에러다. -
Libgit2가 포인터에 값을 할당해주지만, 사용자가 꼭 해제해야 한다.
-
Libgit2가 리턴하는
const
포인터는 해제하지 말아야 한다. 해당 메모리가 속한 객체가 해제될 때 문제가 된다. -
C로 코딩하는 것은 원래 좀 고통스럽다.
마지막 라인을 이유로 Libgit2를 C에서 사용할 가능성은 매우 낮다. 다양한 언어나 환경에서 사용할 수 있는 Libgit2 바인딩이 있어서 Git 저장소를 쉽게 다룰 수 있다. Rugged라는 Ruby 바인딩을 사용해서 위의 예제를 재작성해 보자. Rugged에 대한 자세한 정보는 https://github.com/libgit2/rugged에 있다.
repo = Rugged::Repository.new('path/to/repository')
commit = repo.head.target
puts commit.message
puts "#{commit.author[:name]} <#{commit.author[:email]}>"
tree = commit.tree
비교해보면 코드가 더 간결해졌다.
Rugged는 예외를 사용해서 더 간결하다. 하지만 ConfigError
나 ObjectError
같은 에러가 발생할 수 있다.
그리고 Ruby는 가비지 콜렉션을 사용하는 언어라서 리소스를 해제하지 않아도 된다.
좀 더 복잡한 예제를 살펴보자. 새로 커밋하는 예제다.
blob_id = repo.write("Blob contents", :blob) # (1)
index = repo.index
index.read_tree(repo.head.target.tree)
index.add(:path => 'newfile.txt', :oid => blob_id) # (2)
sig = {
:email => "bob@example.com",
:name => "Bob User",
:time => Time.now,
}
commit_id = Rugged::Commit.create(repo,
:tree => index.write_tree(repo), # (3)
:author => sig,
:committer => sig, # (4)
:message => "Add newfile.txt", # (5)
:parents => repo.empty? ? [] : [ repo.head.target ].compact, # (6)
:update_ref => 'HEAD', # (7)
)
commit = repo.lookup(commit_id) # (8)
-
파일 내용이 담긴 Blob을 만든다.
-
Index에 Head 커밋의 Tree를 채우고 만든 Blob을
newfile.txt
파일로 추가한다. -
ODB(Object Database)에 새 트리 개체를 만든다. 커밋할 때는 새 트리 개체가 필요하다.
-
Author와 Committer정보는 한 사람(Signature)으로 한다.
-
커밋 메시지를 입력한다.
-
커밋할 때 부모가 필요하다. 여기서는 HEAD를 부모로 사용한다.
-
Rugged (and Libgit2)는 커밋할 때 Ref 갱신 여부를 선택할 수 있다.
-
리턴한 커밋 개체의 SHA-1 해시로
Commit
객체 가져와 사용한다.
Ruby 코드는 간결하고 깔끔하다. Libgit2을 사용하는 것이기 때문에 여전히 빠르다. 루비스트가 아니라면 다른 바인딩에 있는 다른 바인딩을 사용할 수 있다.
고급 기능
Libgit2으로 Git을 확장하는 일도 가능하다. Libgit2에서는 커스텀 “Backend” 를 만들어 사용할 수 있다. 그래서 Git이 저장하는 방법 말고 다른 방법으로도 저장할 수 있다. 이것을 'Pluggability’라고 부른다. 설정, Ref 저장소, 개체 데이터 베이스를 커스텀 “Backend” 에 저장할 수 있다.
이게 무슨 소리인지 예제를 살펴보자. 아래 코드는 Libgit2 팀이 제공하는 Backend 예제에서 가져왔다. Libgit2 팀이 제공하는 전체 예제는 https://github.com/libgit2/libgit2-backends에 있다. 개체 데이터베이스의 Backend를 어떻게 사용하는지 보자.
git_odb *odb;
int error = git_odb_new(&odb); // (1)
git_odb_backend *my_backend;
error = git_odb_backend_mine(&my_backend, /*…*/); // (2)
error = git_odb_add_backend(odb, my_backend, 1); // (3)
git_repository *repo;
error = git_repository_open(&repo, "some-path");
error = git_repository_set_odb(odb); // (4)
(에러는 처리하지 않았다. 실제로 사용할 때는 완벽하리라 믿는다.)
-
“Frontend” 로 사용할 ODB(Object DataBase)를 하나 초기화한다. 실제로 저장하는 “Backend” 의 컨테이터로 사용한다.
-
ODB Backend를 초기화한다.
-
Frontend에 Backend를 추가한다.
-
저장소를 열고 우리가 만든 ODB를 사용하도록 설정한다. 그러면 개체를 우리가 만든 ODB에서 찾는다.
그런데 git_odb_backend_mine
는 뭘까?
이 함수는 우리의 ODB 생성자다. 여기서 원하는 대로 Backend를 만들어 주고 git_odb_backend
구조체만 잘 채우면 된다.
아래처럼 만든다.
typedef struct {
git_odb_backend parent;
// Some other stuff
void *custom_context;
} my_backend_struct;
int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/)
{
my_backend_struct *backend;
backend = calloc(1, sizeof (my_backend_struct));
backend->custom_context = …;
backend->parent.read = &my_backend__read;
backend->parent.read_prefix = &my_backend__read_prefix;
backend->parent.read_header = &my_backend__read_header;
// …
*backend_out = (git_odb_backend *) backend;
return GIT_SUCCESS;
}
my_backend_struct
의 첫 번째 맴버는 반드시 git_odb_backend
가 돼야 한다. Libgit2가 동작하는 메모리 구조에 맞아야 한다.
나머지 멤버는 상관없다. 구조체 크기는 커도 되고 작아도 된다.
이 초기화 함수에서 구조체를 메모리를 할당하고 커스텀 멤버에 필요한 정보를 설정한다. 그리고 Libgit2에서 필요한 parent
구조체를 채운다.
include/git2/sys/odb_backend.h
소스를 보면 git_odb_backend
구조체의 멤버가 어떤 것이 있는지 알 수 있다. 목적에 따라 어떻게 사용해야 하는지 확인해야 한다.
다른 바인딩
Libgit2 바인딩은 많은 언어로 구현돼 있다. 이 글을 쓰는 시점에서 거의 완벽하게 구현됐다고 생각되는 것은 여기서 소개한다. 그 외에도 C++, Go, Node.js, Erlang, JVM 등 많은 언어로 구현돼 있다. https://github.com/libgit2에 가서 살펴보면 어떤 바인딩이 있는지 찾아볼 수 있다. 여기서는 HEAD가 가리키는 커밋의 메시지를 가져오는 코드를 보여준다.
LibGit2Sharp
이 바인딩은 C#으로 작성했고 Libgit2를 감쌌음에도 네이티브 느낌이 나도록 꼼꼼하게 설계했다. 커밋 메시지를 가져오는 예제를 보자.
new Repository(@"C:\path\to\repo").Head.Tip.Message;
Windows 데스크톱 애플리케이션에서 쉽게 사용할 수 있도록 NuGet 패키지도 존재한다.
objective-git
Apple 플랫폼용 애플리케이션을 만들고 있다면 언어가 Objective-C일 것이다. 이 환경에서는 Objective-Git(https://github.com/libgit2/objective-git)을 사용할 수 있다. Objective-C 예제를 보자.
GTRepository *repo =
[[GTRepository alloc] initWithURL:[NSURL fileURLWithPath: @"/path/to/repo"] error:NULL];
NSString *msg = [[[repo headReferenceWithError:NULL] resolvedTarget] message];
Objective-git는 Swift에서도 사용할 수 있기 때문에 Objective-C가 아니라고 걱정하지 않아도 된다.
pygit2
Python용 바인딩은 Pygit2라고 부른다. http://www.pygit2.org/에서 찾을 수 있다. 예제를 보자.
pygit2.Repository("/path/to/repo") # open repository
.head # get the current branch
.peel(pygit2.Commit) # walk down to the commit
.message # read the message
읽을거리
Libgit2를 자세히 설명하는 것은 이 책의 목적에서 벗어난다. Libgit2 자체에 대해서 공부하고 싶다면 Libgit2 가이드(https://libgit2.github.com/docs)와 API 문서(https://libgit2.github.com/libgit2)를 참고한다. Libgit2 바인딩에 대해서 알고 싶다면 해당 프로젝트의 README 파일과 테스트를 참고해야 한다. 읽어보면 어디서부터 시작해야 하는지 알려준다.