diff --git a/book/10-git-internals/1-git-internals.asc b/book/10-git-internals/1-git-internals.asc index 3dbd6d35..e7ecc253 100644 --- a/book/10-git-internals/1-git-internals.asc +++ b/book/10-git-internals/1-git-internals.asc @@ -1,19 +1,40 @@ [[_git_internals]] +////////////////////////// == Git Internals +////////////////////////// +== Git의 내부 +////////////////////////// You may have skipped to this chapter from a previous chapter, or you may have gotten here after reading the rest of the book – in either case, this is where we'll go over the inner workings and implementation of Git. We found that learning this information was fundamentally important to understanding how useful and powerful Git is, but others have argued to us that it can be confusing and unnecessarily complex for beginners. Thus, we've made this discussion the last chapter in the book so you could read it early or later in your learning process. We leave it up to you to decide. +////////////////////////// +여기까지 다 읽고 왔든지 바로 여기부터 보기 시작했던지 간에 이제 마지막 장이다. 이번 장은 Git이 어떻게 구현돼 있고 내부적으로 어떻게 동작하는지 설명한다. +Git이 얼마나 유용하고 강력한지 이해하려면 이 장의 내용을 꼭 알아야 한다. 이 장은 초보자에게 너무 혼란스럽고 불필요한 내용이라고 이야기하는 사람들도 있다. +그래서 필자는 본 내용을 책의 가장 마지막에 두었고 +독자가 스스로 먼저 볼지 나중에 볼지 선택할 수 있도록 했다. +////////////////////////// Now that you're here, let's get started. First, if it isn't yet clear, Git is fundamentally a content-addressable filesystem with a VCS user interface written on top of it. You'll learn more about what this means in a bit. +////////////////////////// +자 이제 본격적으로 살펴보자. +우선 Git은 기본적으로 Content-addressable 파일 시스템이고 그 위에 VCS 사용자 인터페이스가 있는 구조다. +뭔가 깔끔한 정의는 아니지만, 이 말이 무슨 의미인지는 차차 알게 된다. +////////////////////////// In the early days of Git (mostly pre 1.5), the user interface was much more complex because it emphasized this filesystem rather than a polished VCS. In the last few years, the UI has been refined until it's as clean and easy to use as any system out there; but often, the stereotype lingers about the early Git UI that was complex and difficult to learn. +////////////////////////// +Git 초기에는 (1.5 이전 버전) 사용자 인터페이스가 훨씬 복잡했었다. VCS가 아니라 파일 시스템을 강조했기 때문이었다. +최근 몇 년간 Git은 다른 VCS처럼 쉽고 간결하게 사용자 인터페이스를 다듬어 왔다. 하지만, 여전히 복잡하고 배우기 어렵다는 선입견이 있다. +////////////////////////// The content-addressable filesystem layer is amazingly cool, so I'll cover that first in this chapter; then, you'll learn about the transport mechanisms and the repository maintenance tasks that you may eventually have to deal with. +////////////////////////// +우선 Content-addressable 파일 시스템은 정말 대단한 것이므로 먼저 다룬다. 그리고 나서 데이터 전송 원리를 배우고 마지막에는 저장소를 관리하는 법까지 배운다. include::sections/plumbing-porcelain.asc[] @@ -31,11 +52,23 @@ include::sections/maintenance.asc[] include::sections/environment.asc[] +////////////////////////// === Summary +////////////////////////// +=== 요약 +////////////////////////// You should have a pretty good understanding of what Git does in the background and, to some degree, how it's implemented. This chapter has covered a number of plumbing commands – commands that are lower level and simpler than the porcelain commands you've learned about in the rest of the book. Understanding how Git works at a lower level should make it easier to understand why it's doing what it's doing and also to write your own tools and helping scripts to make your specific workflow work for you. +////////////////////////// +Git이 내부적으로 어떻게 동작하는지 뿐만 아니라 어떻게 구현됐는지까지 잘 알게 됐을 것이다. +이 장에서는 저수준 명령어인 Plumbing 명령어를 설명했다. 다른 장에서 우리가 배웠던 Porcelain 명령어보다는 단순하다. +Git이 내부적으로 어떻게 동작하는지 알면 Git이 왜 그렇게 하는가를 더 쉽게 이해할 수 있을 뿐만 아니라 개인적으로 필요한 도구나 스크립트를 만들어 자신의 Workflow를 개선할 수 있다. +////////////////////////// Git as a content-addressable filesystem is a very powerful tool that you can easily use as more than just a VCS. We hope you can use your newfound knowledge of Git internals to implement your own cool application of this technology and feel more comfortable using Git in more advanced ways. +////////////////////////// +Git은 Content-addressable 파일 시스템이기 때문에 VCS 이상의 일을 할 수 있는 매우 강력한 도구다. +필자는 독자가 습득한 Git 내부 지식을 활용해서 필요한 애플리케이션을 직접 만들면 좋겠다. 그리고 진정 Git을 꼼꼼하고 디테일하게 다룰 수 있게 되길 바란다. diff --git a/book/10-git-internals/sections/environment.asc b/book/10-git-internals/sections/environment.asc index 654ee880..d6387517 100644 --- a/book/10-git-internals/sections/environment.asc +++ b/book/10-git-internals/sections/environment.asc @@ -1,138 +1,309 @@ +////////////////////////// === Environment Variables +////////////////////////// +=== 환경변수 +////////////////////////// Git always runs inside a `bash` shell, and uses a number of shell environment variables to determine how it behaves. Occasionally, it comes in handy to know what these are, and how they can be used to make Git behave the way you want it to. This isn't an exhaustive list of all the environment variables Git pays attention to, but we'll cover the most useful. +////////////////////////// +Git은 늘 `bash` 셸 환경 안에서 동작한다. 셸 환경변수에 따라 Git의 동작이 달라진다. +Git에 영향을 주는 환경변수가 어떤 것들이 있고 또 그 값에 따라 Git이 어떻게 동작하는지 알아두면 꽤 쓸모 있다. +Git과 관련된 환경변수 전체를 다루지는 못하지만 알아두면 유용한 변수들은 거의 다 다룬다. - +////////////////////////// ==== Global Behavior +////////////////////////// +==== Git에 영향을 주는 변수 +////////////////////////// Some of Git's general behavior as a computer program depends on environment variables. +////////////////////////// +Git의 여러 기능 중 일반적인 기능이 동작할 때 영향을 미치는 주요 환경변수는 아래와 같다. +////////////////////////// *`GIT_EXEC_PATH`* determines where Git looks for its sub-programs (like `git-commit`, `git-diff`, and others). You can check the current setting by running `git --exec-path`. +////////////////////////// +*`GIT_EXEC_PATH`* 변수는 Git의 여러 Subprogram(예를 들어 `git-commit`, `git-diff` 같은 것들)이 어디에 있는지를 설정한다. + 현재 설정을 확인하려면 `git --exec-path` 명령을 실행한다. +////////////////////////// *`HOME`* isn't usually considered customizable (too many other things depend on it), but it's where Git looks for the global configuration file. If you want a truly portable Git installation, complete with global configuration, you can override `HOME` in the portable Git's shell profile. +////////////////////////// +*`HOME`* 변수는 일반적으로 변경하지 않는 변수이다. 아주 많은 프로그램이 이 변수를 참조하기 때문이다. Git이 이 변수에 영향을 받는 부분은 사용자(User) 전체에 영향을 주는 Git 환경설정 파일을 찾을 때이다. + Git을 포터블로 설치하거나 해서 사용자 환경설정 파일의 위치를 강제로 지정해야 하면 Git을 실행하는 셸의 `HOME` 변수에 원하는 값을 설정한다. +////////////////////////// *`PREFIX`* is similar, but for the system-wide configuration. Git looks for this file at `$PREFIX/etc/gitconfig`. +////////////////////////// +*`PREFIX`* 변수도 비슷한 성격으로 사용자 수준이 아닌 시스템 수준의 환경설정 파일을 찾을 위치를 설정한다. + Git이 찾을 위치는 `$PREFIX/etc/gitconfig` 이다. +////////////////////////// *`GIT_CONFIG_NOSYSTEM`*, if set, disables the use of the system-wide configuration file. This is useful if your system config is interfering with your commands, but you don't have access to change or remove it. +////////////////////////// +*`GIT_CONFIG_NOSYSTEM`* 변수를 설정하면 시스템 수준의 환경설정 파일을 적용하지 않는다. + 이 변수는 시스템 수준의 환경설정 파일이 자꾸 방해되는데 고칠 권한이 없는 경우 설정하면 유용하다. +////////////////////////// *`GIT_PAGER`* controls the program used to display multi-page output on the command line. If this is unset, `PAGER` will be used as a fallback. +////////////////////////// +*`GIT_PAGER`* 변수는 Git이 화면에 출력할 내용이 한 화면이 넘어갈 때 사용할 프로그램을 설정한다. +이 변수에 값을 설정하지 않으면 `PAGER` 변수의 내용도 참고한다. +////////////////////////// *`GIT_EDITOR`* is the editor Git will launch when the user needs to edit some text (a commit message, for example). If unset, `EDITOR` will be used. +////////////////////////// +*`GIT_EDITOR`* 변수는 커밋 내용을 입력하는 상황과 같이 Git이 사용자로부터 어떤 내용을 입력받는 경우 실행시킬 편집기를 설정하는 변수이다. +이 변수에 값을 설정하지 않으면 `EDITOR` 변수의 내용도 참고한다. +////////////////////////// ==== Repository Locations +////////////////////////// +==== 저장소 위치 관련 변수 +////////////////////////// Git uses several environment variables to determine how it interfaces with the current repository. +////////////////////////// +Git은 현재 작업 중인 저장소를 참조할 때 아래와 같은 환경변수에 영향을 받는다. +////////////////////////// *`GIT_DIR`* is the location of the `.git` folder. If this isn't specified, Git walks up the directory tree until it gets to `~` or `/`, looking for a `.git` directory at every step. +////////////////////////// +*`GIT_DIR`* 변수는 `.git` 디렉토리의 위치를 설정하는 변수다. +이 변수의 값을 설정하지 않으면 현재 디렉토리에서부터 `~`나 `/` 까지 한 단계씩 위로 올라가면서 `.git` 디렉토리가 있는지 찾는다. +////////////////////////// *`GIT_CEILING_DIRECTORIES`* controls the behavior of searching for a `.git` directory. If you access directories that are slow to load (such as those on a tape drive, or across a slow network connection), you may want to have Git stop trying earlier than it might otherwise, especially if Git is invoked when building your shell prompt. +////////////////////////// +*`GIT_CEILING_DIRECTORIES`* 변수는 `.git` 디렉토리를 찾으려고 한 단계씩 위로 올라가는 작업을 제어한다. +사용하는 시스템의 저장장치를 읽고 쓰는 속도가 무지하게 느리면 이 변수를 적절하게 설정한다. 불필요하게 `.git` 디렉토리를 찾아서 계속 저장장치의 디렉토리를 돌아다니지 않아도 된다. +////////////////////////// *`GIT_WORK_TREE`* is the location of the root of the working directory for a non-bare repository. If not specified, the parent directory of `$GIT_DIR` is used. +////////////////////////// +*`GIT_WORK_TREE`* 변수는 Git 저장소가 관리하는 실제 소스코드와 같은 파일이 위치한 디렉토리를 설정한다. 물론 실제 파일을 사용하므로 Bare 저장소가 아닌 경우에만 해당한다. +이 변수를 설정하지 않으면 `$GIT_DIR`에 설정된 디렉토리의 상위 디렉토리를 사용한다. +////////////////////////// *`GIT_INDEX_FILE`* is the path to the index file (non-bare repositories only). +////////////////////////// +*`GIT_INDEX_FILE`* 변수는 인덱스 파일의 위치를 설정한다. Bare 저장소가 아닌 경우에만 해당한다. +////////////////////////// *`GIT_OBJECT_DIRECTORY`* can be used to specify the location of the directory that usually resides at `.git/objects`. +////////////////////////// +*`GIT_OBJECT_DIRECTORY`* 변수는 `.git/objects` 디렉토리 위치를 설정한다. Bare 저장소가 아닌 경우에만 해당한다. +////////////////////////// *`GIT_ALTERNATE_OBJECT_DIRECTORIES`* is a colon-separated list (formatted like `/dir/one:/dir/two:…`) which tells Git where to check for objects if they aren't in `GIT_OBJECT_DIRECTORY`. If you happen to have a lot of projects with large files that have the exact same contents, this can be used to avoid storing too many copies of them. +////////////////////////// +*`GIT_ALTERNATE_OBJECT_DIRECTORIES`* 변수는 콜론으로 구분된 디렉토리 리스트(예, `/dir/one:/dir/two:…`)로 `GIT_OBJECT_DIRECTORY`에서 찾을 수 없는 개체를 찾을 때 사용할 디렉토리를 설정한다. +크기가 무지하게 큰 파일을 여러 프로젝트에서 공유하고 있다면 이 변수를 적절히 사용한다. 중복되는 내용을 지우고 특정 위치에서 개체를 공유해서 사용하므로 저장공간 낭비를 줄일 수 있다. - +////////////////////////// ==== Pathspecs +////////////////////////// +==== Pathspec 관련 변수 +////////////////////////// A ``pathspec'' refers to how you specify paths to things in Git, including the use of wildcards. These are used in the `.gitignore` file, but also on the command-line (`git add *.c`). +////////////////////////// +``pathspec''은 Git을 쓸 때 파일이나 디렉토리의 경로(* 같은 와일드카드 문자를 사용하는 경우를 포함)를 전달할 때 어떤 방식을 사용하는가에 대한 내용이다. +`.gitignore` 파일에서도 사용하고 git 명령(예, `git add *.c`)에서도 사용한다. +////////////////////////// *`GIT_GLOB_PATHSPECS` and `GIT_NOGLOB_PATHSPECS`* control the default behavior of wildcards in pathspecs. If `GIT_GLOB_PATHSPECS` is set to 1, wildcard characters act as wildcards (which is the default); if `GIT_NOGLOB_PATHSPECS` is set to 1, wildcard characters only match themselves, meaning something like `*.c` would only match a file _named_ ``*.c'', rather than any file whose name ends with `.c`. You can override this in individual cases by starting the pathspec with `:(glob)` or `:(literal)`, as in `:(glob)*.c`. +////////////////////////// +*`GIT_GLOB_PATHSPECS`, `GIT_NOGLOB_PATHSPECS`* 변수로는 Pathspec을 사용할 때 와일드카드 문자로 어떤 동작을 하게 할 지 설정한다. +`GIT_GLOB_PATHSPECS` 변수의 값을 1로 설정하면 와일드카드 문자는 보통 사용하듯 와일드카드 문자의 역할을 한다(기본값). `GIT_NOGLOB_PATHSPECS` 변수의 값을 1로 설정하면 와일드카드 문자를 진짜 파일 이름의 와일드카드 문자로만 인식한다. `*.c` 라고 하면 진짜 파일이름이 ``*.c'' 인 파일만 해당하고 확장자가 `.c` 파일은 해당하지 않는다. +환경 변수에 독립적으로 각 명령에서 이를 선택하여 사용할 때는 `:(glob)` 또는 `:(literal)`를 명시해서 사용할 수 있다. 예를 들어 `:(glob)*.c` 같이 말이다. +////////////////////////// *`GIT_LITERAL_PATHSPECS`* disables both of the above behaviors; no wildcard characters will work, and the override prefixes are disabled as well. +////////////////////////// +*`GIT_LITERAL_PATHSPECS`* 변수를 설정하면 위 설정 둘 다 적용하지 않는다. 와일드카드 문자는 아무런 쓸모도 없게 되고, 변수에 독립적으로 사용하는 접두어도 마찬가지로 쓸 수 없게 된다. +////////////////////////// *`GIT_ICASE_PATHSPECS`* sets all pathspecs to work in a case-insensitive manner. +////////////////////////// +*`GIT_ICASE_PATHSPECS`* 변수를 설정하면 대문자와 소문자를 가리지 않게 된다. +////////////////////////// ==== Commiting +////////////////////////// +==== 커밋관련 변수 +////////////////////////// The final creation of a Git commit object is usually done by `git-commit-tree`, which uses these environment variables as its primary source of information, falling back to configuration values only if these aren't present. +////////////////////////// +Git이 커밋을 만드는 작업에서 대부분 `git-commit-tree` 명령을 실행하고 나면 커밋 개체가 만들어진다. 이 명령이 커밋을 만들 때 커밋에 채워넣을 정보를 가져오거나 참고하는 환경변수는 아래와 같다. 환경변수를 설정하지 않는 경우는 환경설정 파일의 내용을 가져와 적용한다. +////////////////////////// *`GIT_AUTHOR_NAME`* is the human-readable name in the ``author'' field. +////////////////////////// +*`GIT_AUTHOR_NAME`* 변수는 ``author'' 정보로 사용할 이름. +////////////////////////// *`GIT_AUTHOR_EMAIL`* is the email for the ``author'' field. +////////////////////////// +*`GIT_AUTHOR_EMAIL`* 변수는 ``author'' 정보로 사용할 이메일 주소. +////////////////////////// *`GIT_AUTHOR_DATE`* is the timestamp used for the ``author'' field. +////////////////////////// +*`GIT_AUTHOR_DATE`* 변수는 ``author'' 정보로 사용할 타임스탬프 값. +////////////////////////// *`GIT_COMMITTER_NAME`* sets the human name for the ``committer'' field. +////////////////////////// +*`GIT_COMMITTER_NAME`* 변수는 ``committer'' 정보로 사용할 이름. +////////////////////////// *`GIT_COMMITTER_EMAIL`* is the email address for the ``committer'' field. +////////////////////////// +*`GIT_COMMITTER_EMAIL`* 변수는 ``committer'' 정보로 사용할 이메일 주소. +////////////////////////// *`GIT_COMMITTER_DATE`* is used for the timestamp in the ``committer'' field. +////////////////////////// +*`GIT_COMMITTER_DATE`* 변수는 ``committer'' 정보로 사용할 타임스탬프 값. +////////////////////////// *`EMAIL`* is the fallback email address in case the `user.email` configuration value isn't set. If _this_ isn't set, Git falls back to the system user and host names. +////////////////////////// +*`EMAIL`* 변수는 어떤 환경설정 파일에도 `user.email` 설정을 찾을 수 없는 경우 참조하는 변수다. 이 변수마저 설정하지 않으면 Git은 시스템의 현재 사용자정보와 시스템 호스트 정보를 조합하여 사용한다. - +////////////////////////// ==== Networking +////////////////////////// +==== 네트워크 관련 변수 +////////////////////////// Git uses the `curl` library to do network operations over HTTP, so *`GIT_CURL_VERBOSE`* tells Git to emit all the messages generated by that library. This is similar to doing `curl -v` on the command line. +////////////////////////// +Git은 HTTP 프로토콜로 데이터를 전송할 때 `curl` 라이브러리를 사용한다. `GIT_CURL_VERBOSE` 변수를 설정하면 `curl` 라이브러리가 출력하는 상세한 정보를 볼 수 있다. `curl -v` 명령을 사용한 경우와 비슷하다. +////////////////////////// *`GIT_SSL_NO_VERIFY`* tells Git not to verify SSL certificates. This can sometimes be necessary if you're using a self-signed certificate to serve Git repositories over HTTPS, or you're in the middle of setting up a Git server but haven't installed a full certificate yet. +////////////////////////// +`GIT_SSL_NO_VERIFY` 변수를 설정하면 SSL 인증서를 확인하지 않는다. HTTPS 프로토콜로 저장소를 사용하는데 Self-signed 인증서를 사용할 때 이 변수를 사용한다. 혹은 아직 인증서를 정상적으로 발급하진 않았지만, 테스트를 위해 테스트용 인증서를 사용하는 경우를 예로 들 수도 있다. +////////////////////////// If the data rate of an HTTP operation is lower than *`GIT_HTTP_LOW_SPEED_LIMIT`* bytes per second for longer than *`GIT_HTTP_LOW_SPEED_TIME`* seconds, Git will abort that operation. These values override the `http.lowSpeedLimit` and `http.lowSpeedTime` configuration values. +////////////////////////// +`GIT_HTTP_LOW_SPEED_TIME` 변수에 설정한 시간 동안 `GIT_HTTP_LOW_SPEED_LIMIT` 변수에 설정한 초당 전송 바이트 수에 미치지 못하는 HTTP 전송속도가 나오면 Git은 데이터 전송을 중지한다. +이 설정은 설정파일의 `http.lowSpeedLimit`, `http.lowSpeedTime` 항목보다 우선한다. +////////////////////////// *`GIT_HTTP_USER_AGENT`* sets the user-agent string used by Git when communicating over HTTP. The default is a value like `git/2.0.0`. +////////////////////////// +`GIT_HTTP_USER_AGENT` 변수는 Git이 HTTP 데이터 전송을 할 때 헤더에 사용자 에이전트 값으로 사용할 문자열을 설정한다. +기본적으로 사용하는 값은 `git/2.0.0` 같은 모양의 값이다. +////////////////////////// ==== Diffing and Merging +////////////////////////// +==== Diff/Merge 관련 변수 +////////////////////////// *`GIT_DIFF_OPTS`* is a bit of a misnomer. The only valid values are `-u` or `--unified=`, which controls the number of context lines shown in a `git diff` command. +////////////////////////// +*`GIT_DIFF_OPTS`* 변수는 이름이 잘못 지어진 변수다. +`git diff` 명령을 실행했을 때 변경된 부분 아래위로 보여주는 라인의 개수를 조절한다. 명령의 옵션으로 사용할 때는 `-u` 이나 `--unified=`로 사용한다. +////////////////////////// *`GIT_EXTERNAL_DIFF`* is used as an override for the `diff.external` configuration value. If it's set, Git will invoke this program when `git diff` is invoked. +////////////////////////// +*`GIT_EXTERNAL_DIFF`* 변수는 `diff.external` 설정보다 우선한다. +`git diff` 명령을 실행하면 이 변수에 설정한 명령을 실행한다. +////////////////////////// *`GIT_DIFF_PATH_COUNTER`* and *`GIT_DIFF_PATH_TOTAL`* are useful from inside the program specified by `GIT_EXTERNAL_DIFF` or `diff.external`. The former represents which file in a series is being diffed (starting with 1), and the latter is the total number of files in the batch. +////////////////////////// +*`GIT_DIFF_PATH_COUNTER`* 변수나 *`GIT_DIFF_PATH_TOTAL`* 변수의 설정은 `GIT_EXTERNAL_DIFF` 또는 `diff.external`에 설정된 프로그램 안에서 유용하게 사용한다. +`GIT_DIFF_PATH_TOTAL` 변수는 diff 명령이 실행할 때 보여주는 모든 파일의 개수를 나타낸다. `GIT_DIFF_PATH_COUNTER` 변수는 그 파일 중 지금 몇 번째 파일을 보여주고 있는지를 1로 시작하는 인덱스를 담고 있다. +////////////////////////// *`GIT_MERGE_VERBOSITY`* controls the output for the recursive merge strategy. The allowed values are as follows: +////////////////////////// +*`GIT_MERGE_VERBOSITY`* 변수는 Recursive Merge 전략에 따른 메시지 출력을 제어한다. +이 변수가 사용할 수 있는 값은 아래와 같이 5개의 수준이다. +////////////////////////// * 0 outputs nothing, except possibly a single error message. * 1 shows only conflicts. * 2 also shows file changes. * 3 shows when files are skipped because they haven't changed. * 4 shows all paths as they are processed. * 5 and above show detailed debugging information. - +////////////////////////// +* 0 충돌이 발생한 경우에만 마지막 에러 메시지를 출력 +* 1 충돌이 발생한 경우에만 충돌 내용을 출력함 +* 2 충돌 내용과 변경된 내용을 출력함 +* 3 변경된 내용이 없는 파일이라도 출력함 +* 4 Merge할 때 열어본 모든 파일을 출력함 +* 5 또는 그 이상의 값을 설정하면 디버그 메시지까지 출력함 + +////////////////////////// The default value is 2. +////////////////////////// +이 중 기본 값은 2이다. +////////////////////////// ==== Debugging +////////////////////////// +==== 디버그 관련 변수 +////////////////////////// Want to _really_ know what Git is up to? Git has a fairly complete set of traces embedded, and all you need to do is turn them on. The possible values of these variables are as follows: +////////////////////////// +Git이 어디까지 실행했는지 알고 싶은가? +Git은 거의 모든 내부 동작에 대한 Trace 로그를 남길 수 있으며 환경변수를 조절해서 Trace 로그를 확인할 수 있다. +변수에 설정할 수 있는 값은 아래와 같다. +////////////////////////// * ``true'', ``1'', or ``2'' – the trace category is written to stderr. * An absolute path starting with `/` – the trace output will be written to that file. +////////////////////////// +* ``true'', ``1'', ``2'' – stderr 표준에러출력으로 Trace 로그를 출력함(1 이상 10 이하의 숫자는 해당 FD로 출력함). +* `/` 로 시작하는 절대 경로 – Trace 로그를 해당 경로의 파일에 기록함. +////////////////////////// *`GIT_TRACE`* controls general traces, which don't fit into any specific category. This includes the expansion of aliases, and delegation to other sub-programs. +////////////////////////// +*`GIT_TRACE`* 변수에 위와 같은 값을 설정하면 특정 카테고리로 지정하지 않은 모든 Trace 메시지를 대상에 기록하거나 출력한다. +Alias를 적용하거나 명령에 따라 Subprogram을 실행시킨다거나 하는 Trace를 확인할 수 있다. [source,console] ---- @@ -145,8 +316,12 @@ $ GIT_TRACE=true git lga 20:12:49.899675 run-command.c:192 trace: exec: 'less' ---- +////////////////////////// *`GIT_TRACE_PACK_ACCESS`* controls tracing of packfile access. The first field is the packfile being accessed, the second is the offset within that file: +////////////////////////// +*`GIT_TRACE_PACK_ACCESS`* 변수에 따라 Packfile 사용 내용을 출력한다. +출력 내용을 보면 첫 번째 열은 접근하는 Packfile의 이름을, 두 번째 열은 Packfile 안에서 오프셋 정보를 보여준다. [source,console] ---- @@ -162,7 +337,10 @@ Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean ---- +////////////////////////// *`GIT_TRACE_PACKET`* enables packet-level tracing for network operations. +////////////////////////// +*`GIT_TRACE_PACKET`* 변수는 네트워크 데이터 전송을 하는 경우 패킷 수준의 Trace 정보를 보여준다. [source,console] ---- @@ -175,8 +353,12 @@ $ GIT_TRACE_PACKET=true git ls-remote origin # […] ---- +////////////////////////// *`GIT_TRACE_PERFORMANCE`* controls logging of performance data. The output shows how long each particular git invocation takes. +////////////////////////// +*`GIT_TRACE_PERFORMANCE`* 변수를 설정하면 Git의 성능에 관련된 Trace를 출력한다. +출력한 내용을 살펴보면 어떤 작업이 얼마나 시간이 걸려 실행되었는지 확인할 수 있다. [source,console] ---- @@ -198,7 +380,10 @@ Checking connectivity: 170994, done. 20:18:25.233159 trace.c:414 performance: 6.112217000 s: git command: 'git' 'gc' ---- +////////////////////////// *`GIT_TRACE_SETUP`* shows information about what Git is discovering about the repository and environment it's interacting with. +////////////////////////// +*`GIT_TRACE_SETUP`* 변수를 설정하면 Git이 현재 어떤 저장소와 어떤 환경 위에서 동작하고 있는지 파악한 정보를 보여준다. [source,console] ---- @@ -212,26 +397,53 @@ Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean ---- +////////////////////////// ==== Miscellaneous +////////////////////////// +==== 잡동사니 변수 +////////////////////////// *`GIT_SSH`*, if specified, is a program that is invoked instead of `ssh` when Git tries to connect to an SSH host. It is invoked like `$GIT_SSH [username@]host [-p ] `. Note that this isn't the easiest way to customize how `ssh` is invoked; it won't support extra command-line parameters, so you'd have to write a wrapper script and set `GIT_SSH` to point to it. It's probably easier just to use the `~/.ssh/config` file for that. +////////////////////////// +*`GIT_SSH`* 변수를 설정하면 Git이 SSH 리모트로 연결할 때 `ssh` 명령 대신 설정된 명령을 사용한다. +즉 `$GIT_SSH [username@]host [-p ] ` 명령을 실행한 것과 같다. +`GIT_SSH` 변수를 설정하는 방식이 `ssh` 명령을 사용자 입맛에 맞게끔 고치는 가장 좋은 방법은 아니다. `ssh` 명령의 다양한 옵션을 사용할 수 없는 방식이기 때문이다. 따로 원하는 옵션들을 적용한 스크립트를 하나 만들고 이 스크립트를 변수에 설정하면 원하는 `ssh` 옵션을 사용할 수 있다. +`~/.ssh/config` 환경설정 파일을 편집하여 사용하는 것이 더 나을 수도 있다. +////////////////////////// *`GIT_ASKPASS`* is an override for the `core.askpass` configuration value. This is the program invoked whenever Git needs to ask the user for credentials, which can expect a text prompt as a command-line argument, and should return the answer on `stdout`. (See <<_credential_caching>> for more on this subsystem.) +////////////////////////// +*`GIT_ASKPASS`* 변수는 `core.askpass` 설정보다 우선한다. +이 변수에 설정하는 스크립트나 프로그램은 Git이 사용자에게 암호를 입력받는 상황에서 실행되어 `stdout` 표준출력으로 출력하는 메시지를 암호로 받아서 처리한다. +(<<_credential_caching>> 에서 더 자세한 내용을 확인할 수 있다.) +////////////////////////// *`GIT_NAMESPACE`* controls access to namespaced refs, and is equivalent to the `--namespace` flag. This is mostly useful on the server side, where you may want to store multiple forks of a single repository in one repository, only keeping the refs separate. +////////////////////////// +*`GIT_NAMESPACE`* 변수를 설정하면 Ref에 접근할 때 네임스페이스로 사용한다. `--namespace` 옵션과 같다. +이 변수는 서버 측에서 유용하게 사용할 수 있다. 하나의 저장소 안에 여러 Fork를 운영하는 경우 이 변수를 사용하여 Ref를 분리하여 사용할 수 있다. +////////////////////////// *`GIT_FLUSH`* can be used to force Git to use non-buffered I/O when writing incrementally to stdout. A value of 1 causes Git to flush more often, a value of 0 causes all output to be buffered. The default value (if this variable is not set) is to choose an appropriate buffering scheme depending on the activity and the output mode. +////////////////////////// +*`GIT_FLUSH`* 변수를 설정하면 Git이 메시지를 화면에 출력할 때 버퍼를 사용하지 않고 즉시즉시 출력한다. +값을 1로 설정하면 평소보다 훨씬 빈번하게 메시지 출력하고 0으로 설정하면 항상 버퍼를 사용한다. +이 변수에 값을 설정하지 않으면 기본적으로 Git은 상황에 맞게 조절하여 출력한다. +////////////////////////// *`GIT_REFLOG_ACTION`* lets you specify the descriptive text written to the reflog. Here's an example: +////////////////////////// +*`GIT_REFLOG_ACTION`* 변수는 `reflog`의 설명에 사용된다. 이 변수에 작업 내용에 대한 설명을 담아두고 Git 명령을 실행하면 된다. +예를 들어 아래와 같다. [source,console] ---- diff --git a/book/10-git-internals/sections/maintenance.asc b/book/10-git-internals/sections/maintenance.asc index 664a5f49..e70a72d4 100644 --- a/book/10-git-internals/sections/maintenance.asc +++ b/book/10-git-internals/sections/maintenance.asc @@ -1,29 +1,57 @@ +////////////////////////// === Maintenance and Data Recovery +////////////////////////// +=== 운영 및 데이터 복구 +////////////////////////// Occasionally, you may have to do some cleanup – make a repository more compact, clean up an imported repository, or recover lost work. This section will cover some of these scenarios. +////////////////////////// +언젠가는 저장소를 손수 정리해야 할 날이 올지도 모른다. 저장소를 좀 더 알차게(Compact) 만들고, 다른 VCS에서 임포트하고 나서 그 잔재를 치운다든가, 아니면 문제가 생겨서 복구해야 할 수도 있다. +이 절은 이럴 때 필요한 것을 설명한다. [[_git_gc]] +////////////////////////// ==== Maintenance +////////////////////////// +==== 운영 +////////////////////////// Occasionally, Git automatically runs a command called ``auto gc''. Most of the time, this command does nothing. However, if there are too many loose objects (objects not in a packfile) or too many packfiles, Git launches a full-fledged `git gc` command. The ``gc'' stands for garbage collect, and the command does a number of things: it gathers up all the loose objects and places them in packfiles, it consolidates packfiles into one big packfile, and it removes objects that aren't reachable from any commit and are a few months old. +////////////////////////// +Git은 때가 되면 자동으로 ``auto gc'' 명령을 실행한다. +이 명령이 실행되는 경우 대부분은 아무런 일도 일어나지 않는다. +Loose 개체가 너무 많거나, Packfile 자체가 너무 많으면 Git은 그제야 진짜로 `git gc` 명령이 일하게 한다. +`gc` 명령은 Garbage를 Collect하는 명령이다. 이 명령은 Loose 개체를 모아서 Packfile에 저장하거나 작은 Packfile을 모아서 하나의 큰 Packfile에 저장한다. 아무런 커밋도 가리키지 않는 개체가 있고 오랫동안(대략 몇 달쯤) 아무도 쓰지 않는다면 개체를 삭제한다. +////////////////////////// You can run auto gc manually as follows: +////////////////////////// +Git이 Garbage를 Collect할 지 말지 자동으로 판단해서 처리하도록 아래와 같이 gc 명령을 실행할 수 있다. [source,console] ---- $ git gc --auto ---- +////////////////////////// Again, this generally does nothing. You must have around 7,000 loose objects or more than 50 packfiles for Git to fire up a real gc command. You can modify these limits with the `gc.auto` and `gc.autopacklimit` config settings, respectively. +////////////////////////// +이 명령을 실행해도 보통은 아무 일도 일어나지 않는다. +Loose 개체가 7천 개가 넘거나 Packfile이 50개가 넘지 않으면 Git은 실제로 `gc` 작업을 실행하지 않는다. +원한다면 `gc.auto`나 `gc.autopacklimit` 설정으로 그 숫자를 조절할 수 있다. +////////////////////////// The other thing `gc` will do is pack up your references into a single file. Suppose your repository contains the following branches and tags: +////////////////////////// +`gc` 명령이 하는 일 중 하나는 Refs를 파일 하나로 압축하는 일이다. +예를 들어 저장소에 아래와 같은 브랜치와 태그가 있다고 하자. [source,console] ---- @@ -34,8 +62,12 @@ $ find .git/refs -type f .git/refs/tags/v1.1 ---- +////////////////////////// If you run `git gc`, you'll no longer have these files in the `refs` directory. Git will move them for the sake of efficiency into a file named `.git/packed-refs` that looks like this: +////////////////////////// +`git gc`를 실행하면 `refs`에 있는 파일은 사라진다. +대신 Git은 그 파일을 `.git/packed-refs` 파일로 압축해서 효율을 높인다. [source,console] ---- @@ -48,22 +80,43 @@ cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0 ^1a410efbd13591db07496601ebc7a059dd55cfe9 ---- +////////////////////////// If you update a reference, Git doesn't edit this file but instead writes a new file to `refs/heads`. To get the appropriate SHA for a given reference, Git checks for that reference in the `refs` directory and then checks the `packed-refs` file as a fallback. However, if you can't find a reference in the `refs` directory, it's probably in your `packed-refs` file. +////////////////////////// +이 상태에서 Refs를 수정하면 파일을 수정하는 게 아니라 `refs/heads` 폴더에 파일을 새로 만든다. +Git은 Refs가 가리키는 SHA 값을 찾을 때 먼저 `refs` 디렉토리에서 찾고 없으면 `packed-refs` 파일에서 찾는다. +그러니까 어떤 Refs가 있는데 `refs` 디렉토리에서 못 찾으면 `packed-refs`에 있을 것이다. +////////////////////////// Notice the last line of the file, which begins with a `^`. This means the tag directly above is an annotated tag and that line is the commit that the annotated tag points to. +////////////////////////// +마지막에 있는 `^`로 시작하는 줄을 살펴보자. +이것은 바로 윗줄의 태그가 Annotated 태그라는 것을 말해준다. 해당 커밋은 윗 태그가 가리키는 커밋이라는 뜻이다. [[_data_recovery]] +////////////////////////// ==== Data Recovery +////////////////////////// +==== 데이터 복구 +////////////////////////// At some point in your Git journey, you may accidentally lose a commit. Generally, this happens because you force-delete a branch that had work on it, and it turns out you wanted the branch after all; or you hard-reset a branch, thus abandoning commits that you wanted something from. Assuming this happens, how can you get your commits back? +////////////////////////// +Git을 사용하다 보면 커밋을 잃어 버리는 실수를 할 때도 있다. +보통 작업 중인 브랜치를 강제로 삭제하거나, 어떤 커밋을 브랜치 밖으로 끄집어 내버렸거나, 강제로(Hard) Reset하면 그렇게 될 수 있다. +어쨌든 원치 않게 커밋을 잃어 버리면 어떻게 다시 찾아야 할까? +////////////////////////// Here's an example that hard-resets the master branch in your test repository to an older commit and then recovers the lost commits. First, let's review where your repository is at this point: +////////////////////////// +`master` 브랜치에서 강제로(Hard) Reset한 경우를 예로 들어 잃어버린 커밋을 복구해보자. +먼저 연습용 저장소를 만든다. [source,console] ---- @@ -75,7 +128,10 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- +////////////////////////// Now, move the `master` branch back to the middle commit: +////////////////////////// +`master` 브랜치를 예전 커밋으로 Reset한다. [source,console] ---- @@ -87,15 +143,26 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- +////////////////////////// You've effectively lost the top two commits – you have no branch from which those commits are reachable. You need to find the latest commit SHA and then add a branch that points to it. The trick is finding that latest commit SHA – it's not like you've memorized it, right? +////////////////////////// +최근 커밋 두 개는 어떤 브랜치도 가리키지 않는다. 잃어 버렸다고 볼 수 있다. +그 두 커밋을 브랜치에 다시 포함하려면 마지막 커밋을 다시 찾아야 한다. +SHA 값을 외웠을 리도 없고 뭔가 찾아낼 방법이 필요하다. +////////////////////////// Often, the quickest way is to use a tool called `git reflog`. As you're working, Git silently records what your HEAD is every time you change it. Each time you commit or change branches, the reflog is updated. The reflog is also updated by the `git update-ref` command, which is another reason to use it instead of just writing the SHA value to your ref files, as we covered in <<_git_refs>>. You can see where you've been at any time by running `git reflog`: +////////////////////////// +보통 `git reflog` 명령을 사용하는 게 가장 쉽다. +HEAD가 가리키는 커밋이 바뀔 때마다 Git은 남몰래 자동으로 그 커밋이 무엇인지 기록한다. +새로 커밋하거나 브랜치를 바꾸면 Reflog도 늘어난다. <<_git_refs>> 절에서 배운 `git update-ref` 명령으로도 Reflog를 남길 수 있다. 이런 상황을 대비할 수 있다는 점이 `git update-ref`를 꼭 사용해야 하는 이유 중 하나다. +`git reflog` 명령만 실행하면 언제나 발자취를 돌아볼 수 있다. [source,console] ---- @@ -105,8 +172,12 @@ ab1afef HEAD@{1}: commit: modified repo.rb a bit 484a592 HEAD@{2}: commit: added repo.rb ---- +////////////////////////// Here we can see the two commits that we have had checked out, however there is not much information here. To see the same information in a much more useful way, we can run `git log -g`, which will give you a normal log output for your reflog. +////////////////////////// +`reflog` 명령으로 확인해보니 Checkout 했었던 커밋 두 개만 보여 준다. 구체적인 정보까지 보여주진 않는다. +좀 더 자세히 보려면 `git log -g` 명령을 사용해야 한다. 이 명령은 Reflog를 `log` 명령 형식으로 보여준다. [source,console] ---- @@ -117,7 +188,7 @@ Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:22:37 2009 -0700 - third commit + third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: HEAD@{1} (Scott Chacon ) @@ -128,8 +199,12 @@ Date: Fri May 22 18:15:24 2009 -0700 modified repo.rb a bit ---- +////////////////////////// It looks like the bottom commit is the one you lost, so you can recover it by creating a new branch at that commit. For example, you can start a branch named `recover-branch` at that commit (ab1afef): +////////////////////////// +두 번째 커밋을 잃어버린 것이니까 그 커밋을 가리키는 브랜치를 만들어 복구한다. +그 커밋(ab1afef)을 가리키는 브랜치 `recover-branch`를 만든다. [source,console] ---- @@ -142,9 +217,14 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- +////////////////////////// Cool – now you have a branch named `recover-branch` that is where your `master` branch used to be, making the first two commits reachable again. Next, suppose your loss was for some reason not in the reflog – you can simulate that by removing `recover-branch` and deleting the reflog. Now the first two commits aren't reachable by anything: +////////////////////////// +`master` 브랜치가 가리키던 커밋을 `recover-branch` 브랜치가 가리키게 했다. 이 커밋 두 개는 다시 도달할 수 있다. +이보다 안 좋은 상황을 가정해보자. 잃어 버린 두 커밋을 Reflog에서 못 찾았다. `recover-branch`를 다시 삭제하고 Reflog를 삭제하여 이 상황을 재연하자. +그러면 그 두 커밋은 다시 도달할 수 없게 된다. [source,console] ---- @@ -152,10 +232,15 @@ $ git branch -D recover-branch $ rm -Rf .git/logs/ ---- +////////////////////////// Because the reflog data is kept in the `.git/logs/` directory, you effectively have no reflog. How can you recover that commit at this point? One way is to use the `git fsck` utility, which checks your database for integrity. If you run it with the `--full` option, it shows you all objects that aren't pointed to by another object: +////////////////////////// +Reflog 데이터는 `.git/logs/` 디렉토리에 있기 때문에 그 디렉토리를 지우면 Reflog도 다 지워진다. +그러면 커밋을 어떻게 복구할 수 있을까? 한 가지 방법이 있는데 `git fsck` 명령으로 데이터베이스의 Integrity를 검사할 수 있다. +이 명령에 `--full` 옵션을 주고 실행하면 길 잃은 개체를 모두 보여준다. [source,console] ---- @@ -168,27 +253,54 @@ dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9 dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293 ---- +////////////////////////// In this case, you can see your missing commit after the string ``dangling commit''. You can recover it the same way, by adding a branch that points to that SHA. +////////////////////////// +이 Dangling 커밋이 잃어버린 커밋이니까 +그 SHA를 가리키는 브랜치를 만들어 복구할 수 있다. [[_removing_objects]] +////////////////////////// ==== Removing Objects +////////////////////////// +==== 개체 삭제 +////////////////////////// There are a lot of great things about Git, but one feature that can cause issues is the fact that a `git clone` downloads the entire history of the project, including every version of every file. This is fine if the whole thing is source code, because Git is highly optimized to compress that data efficiently. However, if someone at any point in the history of your project added a single huge file, every clone for all time will be forced to download that large file, even if it was removed from the project in the very next commit. Because it's reachable from the history, it will always be there. +////////////////////////// +Git은 장점이 매우 많다. 물론 단점도 있는데 Clone할 때 히스토리를 전부 내려받는 것이 문제가 될 때가 있을 수 있다. Git은 모든 파일의 모든 버전을 내려받는다. +사실 파일이 모두 소스코드라면 아무 문제 없다. Git은 최적화를 잘해서 데이터를 잘 압축한다. +하지만, 누군가 매우 큰 바이너리 파일을 넣어버리면 Clone할 때마다 그 파일을 내려받는다. +다음 커밋에서 그 파일을 삭제해도 히스토리에는 그대로 남아 있기 때문에 Clone할 때마다 포함된다. +////////////////////////// This can be a huge problem when you're converting Subversion or Perforce repositories into Git. Because you don't download the whole history in those systems, this type of addition carries few consequences. If you did an import from another system or otherwise find that your repository is much larger than it should be, here is how you can find and remove large objects. +////////////////////////// +이 문제는 Subversion이나 Perforce 저장소를 Git으로 변환할 때에 큰 문제가 된다. +Subversion이나 Perforce 시스템은 전체 히스토리를 내려받는 것이 아니므로 해당 파일이 여러 번 추가될 수 있다. +혹은 다른 VCS에서 Git 저장소로 임포트하려고 하는데 Git 저장소의 공간이 충분하지 않으면 너무 큰 개체는 찾아서 삭제해야 한다. +////////////////////////// *Be warned: this technique is destructive to your commit history.* It rewrites every commit object since the earliest tree you have to modify to remove a large file reference. If you do this immediately after an import, before anyone has started to base work on the commit, you're fine – otherwise, you have to notify all contributors that they must rebase their work onto your new commits. +////////////////////////// +*주의: 이 작업을 하다가 커밋 히스토리를 망쳐버릴 수 있다.* +삭제하거나 수정할 파일이 들어 있는 커밋 이후에 추가된 커밋은 모두 재작성된다. +프로젝트를 임포트 하자마자 하는 것은 괜찮다. 아직 아무도 새 저장소를 가지고 일하지 않기 때문이다. 그게 아니면 히스토리를 Rebase 한다고 관련된 사람 모두에게 알려야 한다. +////////////////////////// To demonstrate, you'll add a large file into your test repository, remove it in the next commit, find it, and remove it permanently from the repository. First, add a large object to your history: +////////////////////////// +시나리오 하나를 살펴보자. 먼저 저장소에 크기가 큰 파일을 넣고 다음 커밋에서는 삭제할 것이다. 그리고 나서 그 파일을 다시 찾아 저장소에서 삭제한다. +먼저 히스토리에 크기가 큰 개체를 추가한다. [source,console] ---- @@ -200,8 +312,12 @@ $ git commit -m 'add git tarball' create mode 100644 git.tgz ---- +////////////////////////// Oops – you didn't want to add a huge tarball to your project. Better get rid of it: +////////////////////////// +이런 tar 파일을 버전관리 하자고 넣을 수는 없다. +다음 커밋에서 다시 삭제한다. [source,console] ---- @@ -213,7 +329,10 @@ $ git commit -m 'oops - removed large tarball' delete mode 100644 git.tgz ---- +////////////////////////// Now, `gc` your database and see how much space you're using: +////////////////////////// +`gc` 명령으로 최적화하고 나서 저장소 크기가 얼마나 되는지 확인한다. [source,console] ---- @@ -225,7 +344,10 @@ Writing objects: 100% (17/17), done. Total 17 (delta 1), reused 10 (delta 0) ---- +////////////////////////// You can run the `count-objects` command to quickly see how much space you're using: +////////////////////////// +`count-objects` 명령은 사용하는 용량이 얼마나 되는지 알려준다. [source,console] ---- @@ -240,16 +362,29 @@ garbage: 0 size-garbage: 0 ---- +////////////////////////// The `size-pack` entry is the size of your packfiles in kilobytes, so you're using almost 5MB. Before the last commit, you were using closer to 2K – clearly, removing the file from the previous commit didn't remove it from your history. Every time anyone clones this repository, they will have to clone all 5MB just to get this tiny project, because you accidentally added a big file. Let's get rid of it. +////////////////////////// +`size-pack` 항목의 숫자가 Packfile의 크기다. 단위가 킬로바이트라서 이 Packfile의 크기는 약 5MB이다. +큰 파일을 커밋하기 전에는 약 2K였다. 필요없는 파일을 지우고 커밋했지만 히스토리에서 삭제되지 않았다. +어쨌든 큰 파일이 하나 들어 있기 때문에 너무 작은 프로젝트인데도 Clone하는 사람마다 5MB씩 필요하다. +이제 그 파일을 삭제해 보자. +////////////////////////// First you have to find it. In this case, you already know what file it is. But suppose you didn't; how would you identify what file or files were taking up so much space? If you run `git gc`, all the objects are in a packfile; you can identify the big objects by running another plumbing command called `git verify-pack` and sorting on the third field in the output, which is file size. You can also pipe it through the `tail` command because you're only interested in the last few largest files: +////////////////////////// +먼저 파일을 찾는다. +뭐, 지금은 무슨 파일인지 이미 알고 있지만 모른다고 가정한다. +어떤 파일이 용량이 큰지 어떻게 찾아낼까? +게다가 `git gc`를 실행됐으면 전부 Packfile 안에 있어서 더 찾기 어렵다. Plumbing 명령어 `git verify-pack`로 파일과 그 크기 정보를 수집하고 세 번째 필드를 기준으로 그 결과를 정렬한다. 세 번째 필드가 파일 크기다. +가장 큰 파일 몇 개만 삭제할 것이기 때문에 tail 명령으로 가장 큰 파일 3개만 골라낸다. [source,console] ---- @@ -261,10 +396,16 @@ dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12 82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438 ---- +////////////////////////// The big object is at the bottom: 5MB. To find out what file it is, you'll use the `rev-list` command, which you used briefly in <<_enforcing_commit_message_format>>. If you pass `--objects` to `rev-list`, it lists all the commit SHAs and also the blob SHAs with the file paths associated with them. You can use this to find your blob's name: +////////////////////////// +마지막에 있는 개체가 5MB로 가장 크다. +이제 그 파일이 정확히 무슨 파일인지 알아내야 한다. +<<_enforcing_commit_message_format>> 에서 소개했던 `rev-list` 명령에 `--objects` 옵션을 추가하면 커밋의 SHA 값과 Blob 개체의 파일이름, SHA 값을 보여준다. +그 결과에서 해당 Blob의 이름을 찾는다. [source,console] ---- @@ -272,8 +413,12 @@ $ git rev-list --objects --all | grep 82c99a3 82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz ---- +////////////////////////// Now, you need to remove this file from all trees in your past. You can easily see what commits modified this file: +////////////////////////// +히스토리에 있는 모든 Tree 개체에서 이 파일을 삭제한다. +먼저 이 파일을 추가한 커밋을 찾는다. [source,console] ---- @@ -282,8 +427,12 @@ dadf725 oops - removed large tarball 7b30847 add git tarball ---- +////////////////////////// You must rewrite all the commits downstream from `7b30847` to fully remove this file from your Git history. To do so, you use `filter-branch`, which you used in <<_rewriting_history>>: +////////////////////////// +이 파일을 히스토리에서 완전히 삭제하면 `6df76` 이후 커밋은 모두 재작성된다. +<<_rewriting_history>> 에서 배운 `filter-branch` 명령으로 삭제한다. [source,console] ---- @@ -294,18 +443,34 @@ Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2) Ref 'refs/heads/master' was rewritten ---- +////////////////////////// The `--index-filter` option is similar to the `--tree-filter` option used in <<_rewriting_history>>, except that instead of passing a command that modifies files checked out on disk, you're modifying your staging area or index each time. +////////////////////////// +`--index-filter` 옵션은 <<_rewriting_history>> 에서 배운 `--tree-filter`와 비슷하다. `--tree-filter`는 디스크에 Checkout해서 파일을 수정하지만 `--index-filter`는 Staging Area에서 수정한다. +////////////////////////// Rather than remove a specific file with something like `rm file`, you have to remove it with `git rm --cached` – you must remove it from the index, not from disk. The reason to do it this way is speed – because Git doesn't have to check out each revision to disk before running your filter, the process can be much, much faster. You can accomplish the same task with `--tree-filter` if you want. The `--ignore-unmatch` option to `git rm` tells it not to error out if the pattern you're trying to remove isn't there. Finally, you ask `filter-branch` to rewrite your history only from the `6df7640` commit up, because you know that is where this problem started. Otherwise, it will start from the beginning and will unnecessarily take longer. - +////////////////////////// +삭제도 `rm file` 명령이 아니라 `git rm --cached` 명령으로 삭제한다. 디스크에서 삭제하는 것이 아니라 Index에서 삭제하는 것이다. +이렇게 하는 이유는 속도가 빠르기 때문이다. Filter를 실행할 때마다 각 리비전을 디스크에 Checkout하지 않기 때문에 이것이 울트라 캡숑 더 빠르다. +`--tree-filter`로도 같은 작업을 할 수 있다. 단지 느릴 뿐이다. +그리고 `git rm` 명령에 `--ignore-unmatch` 옵션을 주면 파일이 없는 경우에 에러를 출력하지 않는다. +마지막으로 문제가 생긴 것은 `6df7640` 커밋부터라서 `filter-branch` 명령에 `6df7640` 커밋부터 재작성하라고 알려줘야 한다. +그렇지 않으면 첫 커밋부터 시작해서 불필요한 것까지 재작성해 버린다. + +////////////////////////// Your history no longer contains a reference to that file. However, your reflog and a new set of refs that Git added when you did the `filter-branch` under `.git/refs/original` still do, so you have to remove them and then repack the database. You need to get rid of anything that has a pointer to those old commits before you repack: +////////////////////////// +히스토리에서는 더는 그 파일을 가리키지 않는다. +하지만, Reflog나 filter-branch를 실행할 때 생기는 레퍼런스가 남아있다. `filter-branch`는 `.git/refs/original` 디렉토리에 실행될 때의 상태를 저장한다. 그래서 이 파일도 삭제하고 데이터베이스를 다시 압축해야 한다. +압축하기 전에 해당 개체를 가리키는 레퍼런스는 모두 없애야 한다. [source,console] ---- @@ -319,7 +484,10 @@ Writing objects: 100% (15/15), done. Total 15 (delta 1), reused 12 (delta 0) ---- +////////////////////////// Let's see how much space you saved. +////////////////////////// +공간이 얼마나 절약됐는지 확인한다. [source,console] ---- @@ -334,9 +502,14 @@ garbage: 0 size-garbage: 0 ---- +////////////////////////// The packed repository size is down to 8K, which is much better than 5MB. You can see from the size value that the big object is still in your loose objects, so it's not gone; but it won't be transferred on a push or subsequent clone, which is what is important. If you really wanted to, you could remove the object completely by running `git prune` with the `--expire` option: +////////////////////////// +압축된 저장소의 크기는 8K로 내려갔다. 5MB보다 한참 작다. +하지만, size 항목은 아직 압축되지 않는 Loose 개체의 크기를 나타내는데 그 항목이 아직 크다. 즉, 아직 완전히 제거된 것은 아니다. 하지만, 이 개체는 Push할 수도 Clone할 수도 없다. 이 점이 중요하다. +정말로 완전히 삭제하려면 `git prune --expire` 명령으로 삭제해야 한다. [source,console] ---- diff --git a/book/10-git-internals/sections/objects.asc b/book/10-git-internals/sections/objects.asc index 84bd5b5b..f7885fe5 100644 --- a/book/10-git-internals/sections/objects.asc +++ b/book/10-git-internals/sections/objects.asc @@ -1,6 +1,10 @@ [[_objects]] +////////////////////////// === Git Objects +////////////////////////// +=== Git 개체 +////////////////////////// Git is a content-addressable filesystem. Great. What does that mean? @@ -8,6 +12,14 @@ It means that at the core of Git is a simple key-value data store. You can insert any kind of content into it, and it will give you back a key that you can use to retrieve the content again at any time. To demonstrate, you can use the plumbing command `hash-object`, which takes some data, stores it in your `.git` directory, and gives you back the key the data is stored as. First, you initialize a new Git repository and verify that there is nothing in the `objects` directory: +////////////////////////// +Git은 Content-addressable +파일시스템이다. +이게 무슨 말이냐 하면 +Git의 핵심은 단순한 Key-Value(역주: 예, 파일이름과 파일데이터) 데이터 저장소라는 것이다. +어떤 형식의 데이터라도 집어넣을 수 있고 해당 Key로 언제든지 데이터를 다시 가져올 수 있다. +Plumbing 명령어를 예로 들면 `hash-object` 명령에 데이터를 주면 `.git` 디렉토리에 저장하고 그 key를 알려준다. +우선 Git 저장소를 새로 만들고 `objects` 디렉토리에 뭐가 들어 있는지 확인한다: [source,console] ---- @@ -21,8 +33,12 @@ $ find .git/objects $ find .git/objects -type f ---- +////////////////////////// Git has initialized the `objects` directory and created `pack` and `info` subdirectories in it, but there are no regular files. Now, store some text in your Git database: +////////////////////////// +아직 빈 디렉토리일 뿐 파일은 아무것도 없다. Git은 `init` 명령으로 저장소를 초기화할 때 `objects` 디렉토리를 만들고 그 밑에 `pack`과 `info` 디렉토리도 만든다. +Git 데이터베이스에 텍스트 파일을 저장해보자: [source,console] ---- @@ -30,11 +46,18 @@ $ echo 'test content' | git hash-object -w --stdin d670460b4b4aece5915caf5c68d12f560a9fe3e4 ---- +////////////////////////// The `-w` tells `hash-object` to store the object; otherwise, the command simply tells you what the key would be. `--stdin` tells the command to read the content from stdin; if you don't specify this, `hash-object` expects a file path at the end. The output from the command is a 40-character checksum hash. This is the SHA-1 hash – a checksum of the content you're storing plus a header, which you'll learn about in a bit. Now you can see how Git has stored your data: +////////////////////////// +이 명령은 표준입력으로 들어오는 데이터를 저장할 수 있다. `-w` 옵션을 줘야 실제로 저장한다. `-w`가 없으면 저장하지 않고 key만 보여준다. +그리고 `--stdin` 옵션을 주면 표준입력으로 입력되는 데이터를 읽는다. 이 옵션이 없으면 파일 경로를 알려줘야 한다. +`hash-object` 명령이 출력하는 것은 40자 길이의 체크섬 해시다. +이 해시는 헤더 정보와 데이터 모두에 대한 SHA-1 해시이다. 헤더 정보는 차차 자세히 살펴볼 것이다. +Git이 저장한 데이터를 알아보자: [source,console] ---- @@ -42,13 +65,23 @@ $ find .git/objects -type f .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 ---- +////////////////////////// You can see a file in the `objects` directory. This is how Git stores the content initially – as a single file per piece of content, named with the SHA-1 checksum of the content and its header. The subdirectory is named with the first 2 characters of the SHA, and the filename is the remaining 38 characters. +////////////////////////// +`objects` 디렉토리에 파일이 하나 새로 생겼다. +데이터는 새로 만든 파일에 저장하며 Git은 데이터를 저장할 때 데이터와 헤더로 생성한 SHA-1 체크섬으로 파일 이름을 짓는다. +해시의 처음 두 글자를 따서 디렉토리 이름에 사용하고 나머지 38글자를 파일 이름에 사용한다. +////////////////////////// You can pull the content back out of Git with the `cat-file` command. This command is sort of a Swiss army knife for inspecting Git objects. Passing `-p` to it instructs the `cat-file` command to figure out the type of content and display it nicely for you: +////////////////////////// +`cat-file` 명령으로 저장한 데이터를 불러올 수 있다. +이 명령은 Git 개체를 살펴보고 싶을 때 맥가이버칼처럼 사용할 수 있다. +`cat-file` 명령에 `-p` 옵션을 주면 파일 내용이 출력된다. [source,console] ---- @@ -56,10 +89,16 @@ $ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4 test content ---- +////////////////////////// Now, you can add content to Git and pull it back out again. You can also do this with content in files. For example, you can do some simple version control on a file. First, create a new file and save its contents in your database: +////////////////////////// +다시 한 번 데이터를 Git 저장소에 추가하고 불러와 보자. +Git이 파일 버전을 관리하는 방식을 이해할 수 있도록 +가상의 상황을 만들어 살펴본다. +우선 새 파일을 하나 만들고 Git 저장소에 저장한다: [source,console] ---- @@ -68,7 +107,10 @@ $ git hash-object -w test.txt 83baae61804e65cc73a7201a7252750c76066a30 ---- +////////////////////////// Then, write some new content to the file, and save it again: +////////////////////////// +그리고 그 파일을 수정하고 다시 저장한다. [source,console] ---- @@ -77,7 +119,10 @@ $ git hash-object -w test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a ---- +////////////////////////// Your database contains the two new versions of the file as well as the first content you stored there: +////////////////////////// +이제 데이터베이스에는 데이터가 두 가지 버전으로 저장돼 있다. [source,console] ---- @@ -87,7 +132,10 @@ $ find .git/objects -type f .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 ---- +////////////////////////// Now you can revert the file back to the first version +////////////////////////// +파일의 내용을 첫 번째 버전으로 되돌린다. [source,console] ---- @@ -96,7 +144,10 @@ $ cat test.txt version 1 ---- +////////////////////////// or the second version: +////////////////////////// +두 번째 버전을 다시 적용한다. [source,console] ---- @@ -105,9 +156,14 @@ $ cat test.txt version 2 ---- +////////////////////////// But remembering the SHA-1 key for each version of your file isn't practical; plus, you aren't storing the filename in your system – just the content. This object type is called a blob. You can have Git tell you the object type of any object in Git, given its SHA-1 key, with `cat-file -t`: +////////////////////////// +파일의 SHA-1 키를 외워서 사용하는 것은 너무 어렵다. 게다가 원래 파일의 이름은 저장하지도 않았다. 단지 파일 내용만 저장했을 뿐이다. +이런 종류의 개체를 Blob 개체라고 부른다. +`cat-file -t` 명령으로 해당 개체가 무슨 개체인지 확인할 수 있다. [source,console] ---- @@ -116,13 +172,23 @@ blob ---- [[_tree_objects]] +////////////////////////// ==== Tree Objects +////////////////////////// +==== Tree 개체 +////////////////////////// The next type we'll look at is the tree, which solves the problem of storing the filename and also allows you to store a group of files together. Git stores content in a manner similar to a UNIX filesystem, but a bit simplified. All the content is stored as tree and blob objects, with trees corresponding to UNIX directory entries and blobs corresponding more or less to inodes or file contents. A single tree object contains one or more tree entries, each of which contains a SHA-1 pointer to a blob or subtree with its associated mode, type, and filename. For example, the most recent tree in the a project may look something like this: +////////////////////////// +다음은 Tree 개체를 살펴보자. 이 Tree 개체에 파일 이름을 저장한다. 파일 여러 개를 한꺼번에 저장할 수도 있다. +Git은 유닉스 파일 시스템과 비슷한 방법으로 저장하지만 좀 더 단순하다. +모든 것을 Tree와 Blob 개체로 저장한다. Tree는 유닉스의 디렉토리에 대응되고 Blob은 Inode나 일반 파일에 대응된다. +Tree 개체 하나는 항목을 여러 개 가질 수 있다. 그리고 그 항목에는 Blob 개체나 하위 Tree 개체를 가리키는 SHA-1 포인터, 파일 모드, 개체 타입, 파일 이름이 들어 있다. +simplegit 프로젝트의 마지막 Tree 개체를 살펴보자. [source,console] ---- @@ -132,8 +198,12 @@ $ git cat-file -p master^{tree} 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib ---- +////////////////////////// The `master^{tree}` syntax specifies the tree object that is pointed to by the last commit on your `master` branch. Notice that the `lib` subdirectory isn't a blob but a pointer to another tree: +////////////////////////// +`master^{tree}` 구문은 `master` 브랜치가 가리키는 Tree 개체를 말한다. +`lib` 항목은 디렉토리인데 Blob 개체가 아니고 다른 Tree 개체다. [source,console] ---- @@ -141,11 +211,19 @@ $ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb ---- +////////////////////////// Conceptually, the data that Git is storing is something like this: +////////////////////////// +Git이 저장하는 데이터는 대강 아래 그림과 같다. +////////////////////////// .Simple version of the Git data model. image::images/data-model-1.png[Simple version of the Git data model.] +////////////////////////// +.단순화한 Git 데이터 모델. +image::images/data-model-1.png[단순화한 Git 데이터 모델.] +////////////////////////// You can fairly easily create your own tree. Git normally creates a tree by taking the state of your staging area or index and writing a series of tree objects from it. So, to create a tree object, you first have to set up an index by staging some files. @@ -153,6 +231,14 @@ To create an index with a single entry – the first version of your test.txt fi You use this command to artificially add the earlier version of the test.txt file to a new staging area. You must pass it the `--add` option because the file doesn't yet exist in your staging area (you don't even have a staging area set up yet) and `--cacheinfo` because the file you're adding isn't in your directory but is in your database. Then, you specify the mode, SHA-1, and filename: +////////////////////////// +직접 Tree 개체를 만들어 보자. +Git은 일반적으로 Staging Area(Index)의 상태대로 Tree 개체를 만들고 기록한다. +그래서 Tree 개체를 만들려면 우선 Staging Area에 파일을 추가해서 Index를 만들어야 한다. +우선 Plumbing 명령어 `update-index`로 `test.txt` 파일만 들어 있는 Index를 만든다. +이 명령어는 파일을 인위적으로 Staging Area에 추가하는 명령이다. 아직 Staging Area에 없는 파일이기 때문에 `--add` 옵션을 꼭 줘야 한다(사실 아직 Staging Area도 설정하지 않았다). +그리고 디렉토리에 있는 파일이 아니라 데이터베이스에 있는 파일을 추가하는 것이기 때문에 `--cacheinfo` 옵션이 필요하다. +파일 모드, SHA-1 해시, 파일 이름 정보도 입력한다. [source,console] ---- @@ -160,12 +246,21 @@ $ git update-index --add --cacheinfo 100644 \ 83baae61804e65cc73a7201a7252750c76066a30 test.txt ---- +////////////////////////// In this case, you're specifying a mode of `100644`, which means it's a normal file. Other options are `100755`, which means it's an executable file; and `120000`, which specifies a symbolic link. The mode is taken from normal UNIX modes but is much less flexible – these three modes are the only ones that are valid for files (blobs) in Git (although other modes are used for directories and submodules). +////////////////////////// +여기서 파일 모드는 보통의 파일을 나타내는 `100644`로 지정했다. +실행파일이라면 `100755`로 지정하고 심볼릭 링크라면 `120000`으로 지정한다. +이런 파일 모드는 유닉스에서 가져오긴 했지만, 유닉스 모드를 전부 사용하지는 않는다. Blob 파일에는 이 세 가지 모드만 사용한다. 디렉토리나 서브모듈에는 다른 모드를 사용한다. +////////////////////////// Now, you can use the `write-tree` command to write the staging area out to a tree object. No `-w` option is needed – calling `write-tree` automatically creates a tree object from the state of the index if that tree doesn't yet exist: +////////////////////////// +Staging Area를 Tree 개체로 저장할 때는 `write-tree` 명령을 사용한다. +`write-tree` 명령은 Tree 개체가 없으면 자동으로 생성하므로 `-w` 옵션이 필요 없다. [source,console] ---- @@ -175,7 +270,10 @@ $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt ---- +////////////////////////// You can also verify that this is a tree object: +////////////////////////// +아래 명령으로 이 개체가 Tree 개체라는 것을 확인한다. [source,console] ---- @@ -183,7 +281,10 @@ $ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree ---- +////////////////////////// You'll now create a new tree with the second version of test.txt and a new file as well: +////////////////////////// +파일을 새로 하나 추가하고 test.txt 파일도 두 번째 버전을 만든다. 그리고 나서 Tree 개체를 만든다. [source,console] ---- @@ -192,8 +293,12 @@ $ git update-index test.txt $ git update-index --add new.txt ---- +////////////////////////// Your staging area now has the new version of test.txt as well as the new file new.txt. Write out that tree (recording the state of the staging area or index to a tree object) and see what it looks like: +////////////////////////// +새 파일인 new.txt와 새로운 버전의 test.txt 파일까지 Staging Area에 추가했다. +현재 상태의 Staging Area를 새로운 Tree 개체로 기록하면 어떻게 보이는지 살펴보자. [source,console] ---- @@ -204,10 +309,16 @@ $ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt ---- +////////////////////////// Notice that this tree has both file entries and also that the test.txt SHA is the ``version 2'' SHA from earlier (`1f7a7a`). Just for fun, you'll add the first tree as a subdirectory into this one. You can read trees into your staging area by calling `read-tree`. In this case, you can read an existing tree into your staging area as a subtree by using the `--prefix` option to `read-tree`: +////////////////////////// +이 Tree 개체에는 파일이 두 개 있고 test.txt 파일의 SHA 값도 두 번째 버전인 `1f7a7a1`이다. +재미난 걸 해보자. 처음에 만든 Tree 개체를 하위 디렉토리로 만들 수 있다. +`read-tree` 명령으로 Tree 개체를 읽어 Staging Area에 추가한다. +`--prefix` 옵션을 주면 Tree 개체를 하위 디렉토리로 추가할 수 있다. [source,console] ---- @@ -220,21 +331,41 @@ $ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt ---- +////////////////////////// If you created a working directory from the new tree you just wrote, you would get the two files in the top level of the working directory and a subdirectory named `bak` that contained the first version of the test.txt file. You can think of the data that Git contains for these structures as being like this: +////////////////////////// +이 Tree 개체로 워킹 디렉토리를 만들면 파일 두 개와 `bak`이라는 하위 디렉토리가 생긴다. 그리고 `bak` 디렉토리 안에는 test.txt 파일의 처음 버전이 들어 있다. +아래와 그림과 같은 구조로 데이터가 저장된다. +////////////////////////// .The content structure of your current Git data. image::images/data-model-2.png[The content structure of your current Git data.] +////////////////////////// +.현재 Git 데이터 구조. +image::images/data-model-2.png[현재 Git 데이터 구조.] [[_git_commit_objects]] +////////////////////////// ==== Commit Objects +////////////////////////// +==== 커밋 개체 +////////////////////////// You have three trees that specify the different snapshots of your project that you want to track, but the earlier problem remains: you must remember all three SHA-1 values in order to recall the snapshots. You also don't have any information about who saved the snapshots, when they were saved, or why they were saved. This is the basic information that the commit object stores for you. +////////////////////////// +각기 다른 스냅샷을 나타내는 Tree 개체를 세 개 만들었다. 하지만, 여전히 이 스냅샷을 불러오려면 SHA-1 값을 기억하고 있어야 한다. +스냅샷을 누가, 언제, 왜 저장했는지에 대한 정보는 아예 없다. +이런 정보는 커밋 개체에 저장된다. +////////////////////////// To create a commit object, you call `commit-tree` and specify a single tree SHA-1 and which commit objects, if any, directly preceded it. Start with the first tree you wrote: +////////////////////////// +커밋 개체는 `commit-tree` 명령으로 만든다. 이 명령에 커밋 개체에 대한 설명과 Tree 개체의 SHA-1 값 한 개를 넘긴다. +앞서 저장한 첫 번째 Tree를 가지고 아래와 같이 만들어 본다. [source,console] ---- @@ -242,7 +373,10 @@ $ echo 'first commit' | git commit-tree d8329f fdf4fc3344e67ab068f836878b6c4951e3b15f3d ---- +////////////////////////// Now you can look at your new commit object with `cat-file`: +////////////////////////// +새로 생긴 커밋 개체를 `cat-file` 명령으로 확인해보자. [source,console] ---- @@ -254,9 +388,15 @@ committer Scott Chacon 1243040974 -0700 first commit ---- +////////////////////////// The format for a commit object is simple: it specifies the top-level tree for the snapshot of the project at that point; the author/committer information (which uses your `user.name` and `user.email` configuration settings and a timestamp); a blank line, and then the commit message. +////////////////////////// +커밋 개체의 형식은 간단하다. 해당 스냅샷에서 최상단 Tree를(역주 - 루트 디렉터리 같은) 하나 가리킨다. 그리고 `user.name`과 `user.email` 설정에서 가져온 Author/Committer 정보, 시간 정보, 그리고 한 줄 띄운 다음 커밋 메시지가 들어간다. +////////////////////////// Next, you'll write the other two commit objects, each referencing the commit that came directly before it: +////////////////////////// +이제 커밋 개체를 두 개 더 만들어 보자. 각 커밋 개체는 이전 개체를 가리키도록 한다. [source,console] ---- @@ -266,8 +406,12 @@ $ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab 1a410efbd13591db07496601ebc7a059dd55cfe9 ---- +////////////////////////// Each of the three commit objects points to one of the three snapshot trees you created. Oddly enough, you have a real Git history now that you can view with the `git log` command, if you run it on the last commit SHA-1: +////////////////////////// +세 커밋 개체는 각각 해당 스냅샷을 나타내는 Tree 개체를 하나씩 가리키고 있다. +이상해 보이겠지만 우리는 진짜 Git 히스토리를 만들었다. 마지막 커밋 개체의 SHA-1 값을 주고 `git log` 명령을 실행하면 아래와 같이 출력한다. [source,console] ---- @@ -276,7 +420,7 @@ commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Author: Scott Chacon Date: Fri May 22 18:15:24 2009 -0700 - third commit + third commit bak/test.txt | 1 + 1 file changed, 1 insertion(+) @@ -285,7 +429,7 @@ commit cac0cab538b970a37ea1e769cbbde608743bc96d Author: Scott Chacon Date: Fri May 22 18:14:29 2009 -0700 - second commit + second commit new.txt | 1 + test.txt | 2 +- @@ -301,11 +445,18 @@ Date: Fri May 22 18:09:34 2009 -0700 1 file changed, 1 insertion(+) ---- +////////////////////////// Amazing. You've just done the low-level operations to build up a Git history without using any of the front end commands. This is essentially what Git does when you run the `git add` and `git commit` commands – it stores blobs for the files that have changed, updates the index, writes out trees, and writes commit objects that reference the top-level trees and the commits that came immediately before them. These three main Git objects – the blob, the tree, and the commit – are initially stored as separate files in your `.git/objects` directory. Here are all the objects in the example directory now, commented with what they store: +////////////////////////// +놀랍지 않은가! +방금 우리는 고수준 명령어 없이 저수준의 명령으로만 Git 히스토리를 만들었다. +지금 한 일이 `git add`와 `git commit` 명령을 실행했을 때 Git 내부에서 일어나는 일이다. Git은 변경된 파일을 Blob 개체로 저장하고 현 Index에 따라서 Tree 개체를 만든다. 그리고 이전 커밋 개체와 최상위 Tree 개체를 참고해서 커밋 개체를 만든다. +즉 Blob, Tree, 커밋 개체가 Git의 주요 개체이고 이 개체는 전부 `.git/objects` 디렉토리에 저장된다. +이 예제에서 생성한 개체는 아래와 같다. [source,console] ---- @@ -322,18 +473,36 @@ $ find .git/objects -type f .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1 ---- +////////////////////////// If you follow all the internal pointers, you get an object graph something like this: +////////////////////////// +내부의 포인터를 따라가면 아래와 같은 그림이 그려진다. +////////////////////////// .All the objects in your Git directory. image::images/data-model-3.png[All the objects in your Git directory.] +////////////////////////// +.Git 저장소 내의 모든 개체. +image::images/data-model-3.png[Git 저장소 내의 모든 개체.] +////////////////////////// ==== Object Storage +////////////////////////// +==== 개체 저장소 +////////////////////////// We mentioned earlier that a header is stored with the content. Let's take a minute to look at how Git stores its objects. You'll see how to store a blob object – in this case, the string ``what is up, doc?'' – interactively in the Ruby scripting language. +////////////////////////// +내용과 함께 헤더도 저장한다고 얘기했다. +잠시 Git이 개체를 어떻게 저장하는지부터 살펴보자. +대화형 Ruby 쉘을 이용하여 ``what is up, doc?''이라는 문자열을 저장하는 방법을 흉내 내 본다. +////////////////////////// You can start up interactive Ruby mode with the `irb` command: +////////////////////////// +`irb` 명령을 실행하여 시작해보자. [source,console] ---- @@ -342,8 +511,12 @@ $ irb => "what is up, doc?" ---- +////////////////////////// Git constructs a header that starts with the type of the object, in this case a blob. Then, it adds a space followed by the size of the content and finally a null byte: +////////////////////////// +Git은 개체의 타입을 시작으로 헤더를 만든다. +그다음에 공백 문자 하나, 내용의 크기, 마지막에 널 문자를 추가한다. [source,console] ---- @@ -351,8 +524,12 @@ Then, it adds a space followed by the size of the content and finally a null byt => "blob 16\u0000" ---- +////////////////////////// Git concatenates the header and the original content and then calculates the SHA-1 checksum of that new content. You can calculate the SHA-1 value of a string in Ruby by including the SHA1 digest library with the `require` command and then calling `Digest::SHA1.hexdigest()` with the string: +////////////////////////// +Git은 헤더와 원래 내용을 합쳐서 SHA-1 체크섬을 계산한다. +Ruby에서도 `require`로 SHA1 라이브러리를 가져다가 흉내 낼 수 있다. `require`로 라이브러리를 포함하고 나서 `Digest::SHA1.hexdigest()`를 호출한다. [source,console] ---- @@ -364,8 +541,12 @@ You can calculate the SHA-1 value of a string in Ruby by including the SHA1 dige => "bd9dbf5aae1a3862dd1526723246b20206e5fc37" ---- +////////////////////////// Git compresses the new content with zlib, which you can do in Ruby with the zlib library. First, you need to require the library and then run `Zlib::Deflate.deflate()` on the content: +////////////////////////// +Git은 또 zlib으로 내용을 압축한다. Ruby에도 zlib 라이브러리가 있으니 Ruby에서도 할 수 있다. +라이브러리를 포함하고 `Zlib::Deflate.deflate()`를 호출한다. [source,console] ---- @@ -375,10 +556,16 @@ First, you need to require the library and then run `Zlib::Deflate.deflate()` on => "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D" ---- +////////////////////////// Finally, you'll write your zlib-deflated content to an object on disk. You'll determine the path of the object you want to write out (the first two characters of the SHA-1 value being the subdirectory name, and the last 38 characters being the filename within that directory). In Ruby, you can use the `FileUtils.mkdir_p()` function to create the subdirectory if it doesn't exist. Then, open the file with `File.open()` and write out the previously zlib-compressed content to the file with a `write()` call on the resulting file handle: +////////////////////////// +마지막으로 zlib으로 압축한 내용을 개체로 저장한다. +SHA-1 값 중에서 맨 앞에 있는 두 자를 가져다 하위 디렉토리 이름으로 사용하고 나머지 38자를 그 디렉토리 안에 있는 파일이름으로 사용한다. +Ruby에서는 `FileUtils.mkdir_p()`로 하위 디렉토리의 존재를 보장하고 나서 `File.open()`으로 파일을 연다. +그리고 그 파일에 zlib으로 압축한 내용을 `write()` 함수로 저장한다. [source,console] ---- @@ -392,6 +579,11 @@ Then, open the file with `File.open()` and write out the previously zlib-compres => 32 ---- +////////////////////////// That's it – you've created a valid Git blob object. All Git objects are stored the same way, just with different types – instead of the string blob, the header will begin with commit or tree. Also, although the blob content can be nearly anything, the commit and tree content are very specifically formatted. +////////////////////////// +다 됐다. 이제 Git Blob 개체를 손으로 만들었다. +Git 개체는 모두 이 방식으로 저장하며 단지 종류만 다르다. 헤더가 `blob`이 아니라 그냥 `commit`이나 `tree`로 시작하게 되는 것뿐이다. +Blob 개체는 여기서 보여준 것과 거의 같지만 커밋이 개체나 Tree 개체는 각기 다른 형식을 사용한다. diff --git a/book/10-git-internals/sections/packfiles.asc b/book/10-git-internals/sections/packfiles.asc index 621db1db..a26a5465 100644 --- a/book/10-git-internals/sections/packfiles.asc +++ b/book/10-git-internals/sections/packfiles.asc @@ -1,7 +1,14 @@ +////////////////////////// === Packfiles +////////////////////////// +=== Packfile +////////////////////////// Let's go back to the objects database for your test Git repository. At this point, you have 11 objects – 4 blobs, 3 trees, 3 commits, and 1 tag: +////////////////////////// +테스트용 Git 저장소의 개체 데이터베이스를 다시 살펴보자. +지금 개체는 모두 11개로 Blob 4개, Tree 3개, 커밋 3개, 태그 1개가 있다. [source,console] ---- @@ -19,9 +26,14 @@ $ find .git/objects -type f .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1 ---- +////////////////////////// Git compresses the contents of these files with zlib, and you're not storing much, so all these files collectively take up only 925 bytes. You'll add some larger content to the repository to demonstrate an interesting feature of Git. To demonstrate, we'll add the `repo.rb` file from the Grit library – this is about a 22K source code file: +////////////////////////// +Git은 zlib으로 파일 내용을 압축하기 때문에 저장 공간이 많이 필요하지 않다. 그래서 이 데이터베이스에 저장된 파일은 겨우 925바이트밖에 되지 않는다. +크기가 큰 파일을 추가해서 이 기능의 효과를 좀 더 살펴보자. +앞 장에서 사용했던 Grit 라이브러리에 들어 있는 `repo.rb` 파일을 추가한다. 이 파일의 크기는 약 22K이다. [source,console] ---- @@ -35,7 +47,10 @@ $ git commit -m 'added repo.rb' rewrite test.txt (100%) ---- +////////////////////////// If you look at the resulting tree, you can see the SHA-1 value your repo.rb file got for the blob object: +////////////////////////// +추가한 Tree 개체를 보면 repo.rb 파일의 SHA-1 값이 무엇인지 확인할 수 있다. [source,console] ---- @@ -45,7 +60,10 @@ $ git cat-file -p master^{tree} 100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt ---- +////////////////////////// You can then use `git cat-file` to see how big that object is: +////////////////////////// +`git cat-file` 명령으로 개체의 크기는 아래와 같이 확인한다. [source,console] ---- @@ -53,7 +71,10 @@ $ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 22044 ---- +////////////////////////// Now, modify that file a little, and see what happens: +////////////////////////// +파일을 수정하면 어떻게 되는지 살펴보자. [source,console] ---- @@ -63,7 +84,10 @@ $ git commit -am 'modified repo a bit' 1 file changed, 1 insertion(+) ---- +////////////////////////// Check the tree created by that commit, and you see something interesting: +////////////////////////// +수정한 커밋의 Tree 개체를 확인하면 흥미로운 점을 발견할 수 있다. [source,console] ---- @@ -73,7 +97,10 @@ $ git cat-file -p master^{tree} 100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt ---- +////////////////////////// The blob is now a different blob, which means that although you added only a single line to the end of a 400-line file, Git stored that new content as a completely new object: +////////////////////////// +이 Blob 개체는 다른 개체다. 새 Blob 개체는 400줄 이후에 한 줄을 더 추가한 새 개체이다. Git은 완전히 새로운 Blob 개체를 만들어 저장한다. [source,console] ---- @@ -81,14 +108,25 @@ $ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e 22054 ---- +////////////////////////// You have two nearly identical 22K objects on your disk. Wouldn't it be nice if Git could store one of them in full but then the second object only as the delta between it and the first? +////////////////////////// +그럼 약 22K짜리 파일을 두 개 가지게 된다. +거의 같은 파일을 두 개나 가지게 되는 것이 못마땅할 수도 있다. 처음 것과 두 번째 것 사이의 차이점만 저장할 수 없을까? +////////////////////////// It turns out that it can. The initial format in which Git saves objects on disk is called a ``loose'' object format. However, occasionally Git packs up several of these objects into a single binary file called a ``packfile'' in order to save space and be more efficient. Git does this if you have too many loose objects around, if you run the `git gc` command manually, or if you push to a remote server. To see what happens, you can manually ask Git to pack up the objects by calling the `git gc` command: +////////////////////////// +가능하다. +Git이 처음 개체를 저장하는 형식은 ``Loose'' 개체 포멧이라고 부른다. +나중에 이 개체를 파일 하나로 압축(Pack)할 수 있다. 이렇게 하여 공간을 절약하고 효율을 높일 수 있다. +Git이 이렇게 압축하는 때는 Loose 개체가 너무 많을 때, `git gc` 명령을 실행했을 때, 리모트 서버로 Push할 때 압축한다. +`git gc` 명령을 실행해서 어떻게 압축하는지 살펴보자. [source,console] ---- @@ -100,7 +138,10 @@ Writing objects: 100% (18/18), done. Total 18 (delta 3), reused 0 (delta 0) ---- +////////////////////////// If you look in your objects directory, you'll find that most of your objects are gone, and a new pair of files has appeared: +////////////////////////// +`objects` 디렉토리를 열어보면 개체 대부분이 사라졌고 한 쌍의 파일이 새로 생겼다. [source,console] ---- @@ -112,20 +153,38 @@ $ find .git/objects -type f .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack ---- +////////////////////////// The objects that remain are the blobs that aren't pointed to by any commit – in this case, the ``what is up, doc?'' example and the ``test content'' example blobs you created earlier. Because you never added them to any commits, they're considered dangling and aren't packed up in your new packfile. +////////////////////////// +압축되지 않은 Blob 개체는 어떤 커밋도 가리키지 않는 개체다. +즉, ``what is up, doc?''과 ``test content'' 예제에서 만들었던 개체이다. +어떤 커밋에도 추가돼 있지 않으면 이 개체는 `dangling` 개체로 취급되고 Packfile에 추가되지 않는다. +////////////////////////// The other files are your new packfile and an index. The packfile is a single file containing the contents of all the objects that were removed from your filesystem. The index is a file that contains offsets into that packfile so you can quickly seek to a specific object. What is cool is that although the objects on disk before you ran the `gc` were collectively about 22K in size, the new packfile is only 7K. You've cut your disk usage by ⅔ by packing your objects. - +////////////////////////// +새로 생긴 파일은 Packfile과 그 Index이다. +파일 시스템에서 삭제된 개체가 전부 이 Packfile에 저장된다. +Index 파일에 대해서는 빠르게 찾을 수 있도록 Packfile에 오프셋이 들어 있다. +`gc` 명령을 실행하기 전에 있던 파일 크기는 약 22K 정도였었는데 새로 만들어진 Packfile은 겨우 7K에 불과하다. 짱이다. +개체를 압축하여 디스크 사용량이 ⅔으로 줄었다. + +////////////////////////// How does Git do this? When Git packs objects, it looks for files that are named and sized similarly, and stores just the deltas from one version of the file to the next. You can look into the packfile and see what Git did to save space. The `git verify-pack` plumbing command allows you to see what was packed up: +////////////////////////// +이런 일은 어떤 식으로 처리하는 것인가? +개체를 압축시키면 Git은 먼저 이름이나 크기가 비슷한 파일을 찾는다. 그리고 두 파일을 비교해서 한 파일은 다른 부분만 저장한다. +Git이 얼마나 공간을 절약해 주는지 Packfile을 열어 확인할 수 있다. +`git verify-pack` 명령어는 압축한 내용을 보여준다. [source,console] ---- @@ -156,9 +215,18 @@ chain length = 1: 3 objects .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack: ok ---- +////////////////////////// Here, the `033b4` blob, which if you remember was the first version of your repo.rb file, is referencing the `b042a` blob, which was the second version of the file. The third column in the output is the size of the object in the pack, so you can see that `b042a` takes up 22K of the file, but that `033b4` only takes up 9 bytes. What is also interesting is that the second version of the file is the one that is stored intact, whereas the original version is stored as a delta – this is because you're most likely to need faster access to the most recent version of the file. +////////////////////////// +`033b4` Blob이 처음 추가한 `repo.rb` 파일인데, 이 Blob은 두 번째 버전인 `b042a` Blob을 가리킨다. +결과에서 세 번째 컬럼은 압축된 개체의 크기를 나타낸다. `b042a`의 크기는 22K지만 `033b4`는 9바이트밖에 안 된다. +특이한 점은 원본을 그대로 저장하는 것이 첫 번째가 아니라 두 번째 버전이라는 것이다. 첫 번째 버전은 차이점만 저장된다. 최신 버전에 접근할 때가 더 많고 최신 버전에 접근하는 속도가 더 빨라야 하기 때문에 이렇게 한다. +////////////////////////// The really nice thing about this is that it can be repacked at any time. Git will occasionally repack your database automatically, always trying to save more space, but you can also manually repack at any time by running `git gc` by hand. +////////////////////////// +언제나 다시 압축할 수 있기 때문에 이 기능은 정말 판타스틱하다. +Git은 가끔 자동으로 데이터베이스를 재압축해서 공간을 절약한다. 그리고 `git gc` 명령으로 직접 압축할 수도 있다. diff --git a/book/10-git-internals/sections/plumbing-porcelain.asc b/book/10-git-internals/sections/plumbing-porcelain.asc index 4da6fc61..6b1826cd 100644 --- a/book/10-git-internals/sections/plumbing-porcelain.asc +++ b/book/10-git-internals/sections/plumbing-porcelain.asc @@ -1,18 +1,36 @@ [[_plumbing_porcelain]] +////////////////////////// === Plumbing and Porcelain +////////////////////////// +=== Plumbing 명령과 Porcelain 명령 +////////////////////////// This book covers how to use Git with 30 or so verbs such as `checkout`, `branch`, `remote`, and so on. But because Git was initially a toolkit for a VCS rather than a full user-friendly VCS, it has a bunch of verbs that do low-level work and were designed to be chained together UNIX style or called from scripts. These commands are generally referred to as ``plumbing'' commands, and the more user-friendly commands are called ``porcelain'' commands. +////////////////////////// +이 책에서는 `checkout`, `branch`, `remote` 같은 30여 가지의 Git 명령을 사용했다. Git은 사실 사용자 친화적인 VCS이기 보다는 VCS로도 사용할 수 있는 툴킷이다. 저수준 명령어가 매우 많아서 저수준의 일도 쉽게 처리할 수 있다. 명령어 여러 개를 Unix 스타일로 함께 엮어서 실행하거나 스크립트에서 호출할 수 있도록 설계됐다. +이러한 저수준의 명령어는 ``Plumbing'' 명령어라고 부르고 좀 더 사용자에게 친숙한 사용자용 명령어는 ``Porcelain'' 명령어라고 부른다. +////////////////////////// The book's first nine chapters deal almost exclusively with porcelain commands. But in this chapter, you'll be dealing mostly with the lower-level plumbing commands, because they give you access to the inner workings of Git, and help demonstrate how and why Git does what it does. Many of these commands aren't meant to be used manually on the command line, but rather to be used as building blocks for new tools and custom scripts. +////////////////////////// +이 책의 앞 아홉 장은 Porcelain 명령어만 사용했다. +하지만, 이 장에서는 저수준 명령인 Plumbing 명령어를 주로 사용한다. 이 명령으로 Git의 내부구조에 접근할 수 있고 실제로 왜, 그렇게 작동하는지도 살펴볼 수 있다. +Plumbing 명령어는 직접 커맨드라인에서 실행하기보다 새로운 도구를 만들거나 각자 필요한 스크립트를 작성할 때 사용한다. +////////////////////////// When you run `git init` in a new or existing directory, Git creates the `.git` directory, which is where almost everything that Git stores and manipulates is located. If you want to back up or clone your repository, copying this single directory elsewhere gives you nearly everything you need. This entire chapter basically deals with the stuff in this directory. Here's what it looks like: +////////////////////////// +새로 만든 디렉토리나 이미 파일이 있는 디렉토리에서 `git init` 명령을 실행하면 Git은 데이터를 저장하고 관리하는 `.git` 디렉토리를 만든다. +이 디렉토리를 복사하기만 해도 저장소가 백업 된다. +이 장은 기본적으로 이 디렉토리에 대한 내용을 설명한다. +디렉토리 구조는 아래와 같다: [source,console] ---- @@ -26,12 +44,24 @@ objects/ refs/ ---- +////////////////////////// You may see some other files in there, but this is a fresh `git init` repository – it's what you see by default. The `description` file is only used by the GitWeb program, so don't worry about it. The `config` file contains your project-specific configuration options, and the `info` directory keeps a global exclude file (((excludes))) for ignored patterns that you don't want to track in a .gitignore file. The `hooks` directory contains your client- or server-side hook scripts, which are discussed in detail in <<_git_hooks>>. +////////////////////////// +이 외에 다른 파일들이 더 있지만, 이 상태가 `git init`을 한 직후에 보이는 새 저장소의 모습이다. +`description` 파일은 기본적으로 GitWeb 프로그램에서만 사용하기 때문에 이 둘은 무시해도 된다. +`config` 파일에는 해당 프로젝트에만 적용되는 설정 옵션이 들어 있다. `info` 디렉토리는 .gitignore 파일처럼 무시할 파일의 패턴을 적어 두는 곳이다. 하지만 .gitignore 파일과는 달리 Git으로 관리되지 않는다. +`hook` 디렉토리에는 클라이언트 훅이나 서버 훅을 넣는다. 관련 내용은 <<_git_hooks>> 에서 설명한다. +////////////////////////// This leaves four important entries: the `HEAD` and (yet to be created) `index` files, and the `objects` and `refs` directories. These are the core parts of Git. The `objects` directory stores all the content for your database, the `refs` directory stores pointers into commit objects in that data (branches), the `HEAD` file points to the branch you currently have checked out, and the `index` file is where Git stores your staging area information. You'll now look at each of these sections in detail to see how Git operates. +////////////////////////// +이제 남은 네 가지 항목은 모두 중요한 항목이다. `HEAD` 파일, `index` 파일, `objects` 디렉토리, `refs` 디렉토리가 남았다. +이 네 항목이 Git의 핵심이다. +`objects` 디렉토리는 모든 컨텐트를 저장하는 데이터베이스이고 `refs` 디렉토리에는 커밋 개체의 포인터를 저장한다. `HEAD` 파일은 현재 Checkout한 브랜치를 가리키고 `index` 파일은 Staging Area의 정보를 저장한다. +각 절마다 주제를 나눠서 Git이 어떻게 동작하는지 자세히 설명한다. diff --git a/book/10-git-internals/sections/refs.asc b/book/10-git-internals/sections/refs.asc index e909f516..d92989bb 100644 --- a/book/10-git-internals/sections/refs.asc +++ b/book/10-git-internals/sections/refs.asc @@ -1,11 +1,22 @@ [[_git_refs]] +////////////////////////// === Git References +////////////////////////// +=== Git 레퍼런스 +////////////////////////// You can run something like `git log 1a410e` to look through your whole history, but you still have to remember that `1a410e` is the last commit in order to walk that history to find all those objects. You need a file in which you can store the SHA-1 value under a simple name so you can use that pointer rather than the raw SHA-1 value. +////////////////////////// +`git log 1a410e`라고 실행하면 전체 히스토리를 볼 수 있지만, 여전히 `1a410e`를 기억해야 한다. 이 커밋은 마지막 커밋이기 때문에 히스토리를 따라 모든 개체를 조회할 수 있다. +SHA-1 값을 날로 사용하기보다 쉬운 이름으로 된 포인터가 있으면 그걸 사용하는 게 더 좋다. 외우기 쉬운 이름으로 된 파일에 SHA-1 값을 저장한다. +////////////////////////// In Git, these are called ``references'' or ``refs;'' you can find the files that contain the SHA-1 values in the `.git/refs` directory. In the current project, this directory contains no files, but it does contain a simple structure: +////////////////////////// +Git에서는 이런 것을 ''레퍼런스`` 또는 ''Refs``라고 부른다. SHA-1 값이 든 파일은 `.git/refs` 디렉토리에 있다. +이 프로젝트에는 아직 Refs가 하나도 없고 단순한 구조의 디렉토리만 준비되어 있다. [source,console] ---- @@ -16,14 +27,20 @@ $ find .git/refs $ find .git/refs -type f ---- +////////////////////////// To create a new reference that will help you remember where your latest commit is, you can technically do something as simple as this: +////////////////////////// +Refs가 있으면 커밋을 찾기 쉬워진다. 사실 내부는 아래처럼 단순하다. [source,console] ---- $ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master ---- +////////////////////////// Now, you can use the head reference you just created instead of the SHA-1 value in your Git commands: +////////////////////////// +SHA-1 값 대신에 지금 만든 Refs를 사용할 수 있다. [source,console] ---- @@ -33,23 +50,33 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- +////////////////////////// You aren't encouraged to directly edit the reference files. Git provides a safer command to do this if you want to update a reference called `update-ref`: +////////////////////////// +Refs 파일을 직접 고치는 것이 좀 못마땅하다. Git에는 좀 더 안전하게 바꿀 수 있는 `update-ref` 명령이 있다. [source,console] ---- $ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9 ---- +////////////////////////// That's basically what a branch in Git is: a simple pointer or reference to the head of a line of work. To create a branch back at the second commit, you can do this: +////////////////////////// +Git 브랜치의 역할이 바로 이거다. 브랜치는 어떤 작업 중 마지막 작업을 가리키는 포인터 또는 Refs이다. +간단히 두 번째 커밋을 가리키는 브랜치를 만들어 보자. [source,console] ---- $ git update-ref refs/heads/test cac0ca ---- +////////////////////////// Your branch will contain only work from that commit down: +////////////////////////// +브랜치는 직접 가리키는 커밋과 그 커밋으로 따라갈 수 있는 모든 커밋을 포함한다. [source,console] ---- @@ -58,22 +85,44 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- +////////////////////////// Now, your Git database conceptually looks something like this: +////////////////////////// +이제 Git 데이터베이스는 아래 그림처럼 보인다. +////////////////////////// .Git directory objects with branch head references included. image::images/data-model-4.png[Git directory objects with branch head references included.] +////////////////////////// +.브랜치 Refs가 추가된 Git 데이터베이스. +image::images/data-model-4.png[브랜치 Refs가 추가된 Git 데이터베이스.] +////////////////////////// When you run commands like `git branch (branchname)`, Git basically runs that `update-ref` command to add the SHA-1 of the last commit of the branch you're on into whatever new reference you want to create. +////////////////////////// +`git branch (branchname)` 명령을 실행하면 Git은 내부적으로 `update-ref` 명령을 실행한다. 입력받은 브랜치 이름과 현 브랜치의 마지막 커밋의 SHA-1 값을 가져다 `update-ref` 명령을 실행한다. [[_the_head]] +////////////////////////// ==== The HEAD +////////////////////////// +==== HEAD +////////////////////////// The question now is, when you run `git branch (branchname)`, how does Git know the SHA-1 of the last commit? The answer is the HEAD file. +////////////////////////// +`git branch (branchname)` 명령을 실행할 때 Git은 어떻게 마지막 커밋의 SHA-1 값을 아는 걸까? +HEAD 파일은 현 브랜치를 가리키는 간접(symbolic) Refs다. +////////////////////////// The HEAD file is a symbolic reference to the branch you're currently on. By symbolic reference, we mean that unlike a normal reference, it doesn’t generally contain a SHA-1 value but rather a pointer to another reference. If you look at the file, you'll normally see something like this: +////////////////////////// +간접 Refs라서 다른 것과 다르다. +이 Refs는 다른 Refs를 가리키는 것이라서 SHA-1 값이 없다. +파일을 열어 보면 아래와 같이 생겼다. [source,console] ---- @@ -81,7 +130,10 @@ $ cat .git/HEAD ref: refs/heads/master ---- +////////////////////////// If you run `git checkout test`, Git updates the file to look like this: +////////////////////////// +`git checkout test`를 실행하면 Git은 HEAD 파일을 아래와 같이 바꾼다. [source,console] ---- @@ -89,10 +141,17 @@ $ cat .git/HEAD ref: refs/heads/test ---- +////////////////////////// When you run `git commit`, it creates the commit object, specifying the parent of that commit object to be whatever SHA-1 value the reference in HEAD points to. +////////////////////////// +`git commit`을 실행하면 커밋 개체가 만들어지는데, 지금 HEAD가 가리키고 있던 커밋의 SHA-1 값이 그 커밋 개체의 부모로 사용된다. +////////////////////////// You can also manually edit this file, but again a safer command exists to do so: `symbolic-ref`. You can read the value of your HEAD via this command: +////////////////////////// +이 파일도 손으로 직접 편집할 수 있지만 `symbolic-ref`라는 명령어가 있어서 좀 더 안전하게 사용할 수 있다. +이 명령으로 HEAD의 값을 읽을 수 있다. [source,console] ---- @@ -100,7 +159,10 @@ $ git symbolic-ref HEAD refs/heads/master ---- +////////////////////////// You can also set the value of HEAD: +////////////////////////// +HEAD의 값을 변경할 수도 있다. [source,console] ---- @@ -109,7 +171,10 @@ $ cat .git/HEAD ref: refs/heads/test ---- +////////////////////////// You can't set a symbolic reference outside of the refs style: +////////////////////////// +refs 형식에 맞지 않으면 수정할 수 없다. [source,console] ---- @@ -117,32 +182,54 @@ $ git symbolic-ref HEAD test fatal: Refusing to point HEAD outside of refs/ ---- +////////////////////////// ==== Tags +////////////////////////// +==== 태그 +////////////////////////// We just finished discussing Git's three main object types, but there is a fourth. The tag object is very much like a commit object – it contains a tagger, a date, a message, and a pointer. The main difference is that a tag object generally points to a commit rather than a tree. It's like a branch reference, but it never moves – it always points to the same commit but gives it a friendlier name. +////////////////////////// +중요한 개체는 모두 살펴봤고 살펴볼 개체가 하나가 남았다. +태그 개체는 커밋 개체랑 매우 비슷하다. 커밋 개체처럼 누가, 언제 태그를 달았는지 태그 메시지는 무엇이고 어떤 커밋을 가리키는지에 대한 정보가 포함된다. +태그 개체는 Tree 개체가 아니라 커밋 개체를 가리키는 것이 그 둘의 차이다. +브랜치처럼 커밋 개체를 가리키지만 옮길 수는 없다. 태그 개체는 늘 그 이름이 뜻하는 커밋만 가리킨다. +////////////////////////// As discussed in <<_git_basics_chapter>>, there are two types of tags: annotated and lightweight. You can make a lightweight tag by running something like this: +////////////////////////// +<<_git_basics_chapter>> 에서 배웠듯 태그는 Annotated 태그와 Lightweight 태그 두 종류로 나뉜다. +먼저 아래와 같이 Lightweight 태그를 만들어 보자. [source,console] ---- $ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d ---- +////////////////////////// That is all a lightweight tag is – a reference that never moves. An annotated tag is more complex, however. If you create an annotated tag, Git creates a tag object and then writes a reference to point to it rather than directly to the commit. You can see this by creating an annotated tag (`-a` specifies that it's an annotated tag): +////////////////////////// +Lightwieght 태그는 만들기 쉽다. 브랜치랑 비슷하지만 브랜치처럼 옮길 수는 없다. +이에 비해 Annotated 태그는 좀 더 복잡하다. Annotated 태그를 만들면 Git은 태그 개체를 만들고 거기에 커밋을 가리키는 레퍼런스를 저장한다. +Annotated 태그는 커밋을 직접 가리키지 않고 태그 개체를 가리킨다. +`-a` 옵션을 주고 Annotated 태그를 만들어 확인해보자. [source,console] ---- $ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag' ---- +////////////////////////// Here's the object SHA-1 value it created: +////////////////////////// +태그 개체의 SHA-1 값을 확인한다. [source,console] ---- @@ -150,7 +237,10 @@ $ cat .git/refs/tags/v1.1 9585191f37f7b0fb9444f35a9bf50de191beadc2 ---- +////////////////////////// Now, run the `cat-file` command on that SHA-1 value: +////////////////////////// +`cat-file` 명령으로 해당 SHA-1 값의 내용을 조회한다. [source,console] ---- @@ -163,23 +253,41 @@ tagger Scott Chacon Sat May 23 16:48:58 2009 -0700 test tag ---- +////////////////////////// Notice that the object entry points to the commit SHA-1 value that you tagged. Also notice that it doesn't need to point to a commit; you can tag any Git object. In the Git source code, for example, the maintainer has added their GPG public key as a blob object and then tagged it. You can view the public key by running this in a clone of the Git repository: +////////////////////////// +`object` 부분에 있는 SHA-1 값이 실제로 태그가 가리키는 커밋이다. +커밋 개체뿐만 아니라 모든 Git 개체에 태그를 달 수 있다. +커밋 개체에 태그를 다는 것이 아니라 Git 개체에 태그를 다는 것이다. +Git을 개발하는 프로젝트에서는 관리자가 자신의 GPG 공개키를 Blob 개체로 추가하고 그 파일에 태그를 달았다. +다음 명령으로 그 공개키를 확인할 수 있다. [source,console] ---- $ git cat-file blob junio-gpg-pub ---- +////////////////////////// The Linux kernel repository also has a non-commit-pointing tag object – the first tag created points to the initial tree of the import of the source code. +////////////////////////// +Linux Kernel 저장소에도 커밋이 아닌 다른 개체를 가리키는 태그 개체가 있다. 그 태그는 저장소에 처음으로 소스 코드를 임포트했을 때 그 첫 Tree 개체를 가리킨다. +////////////////////////// ==== Remotes +////////////////////////// +==== 리모트 +////////////////////////// The third type of reference that you'll see is a remote reference. If you add a remote and push to it, Git stores the value you last pushed to that remote for each branch in the `refs/remotes` directory. For instance, you can add a remote called `origin` and push your `master` branch to it: +////////////////////////// +리모트 Refs라는 것도 있다. +리모트를 추가하고 Push하면 Git은 각 브랜치마다 Push한 마지막 커밋이 무엇인지 `refs/remotes` 디렉토리에 저장한다. +예를 들어, `origin`이라는 리모트를 추가하고 `master` 브랜치를 Push 한다. [source,console] ---- @@ -193,7 +301,10 @@ To git@github.com:schacon/simplegit-progit.git a11bef0..ca82a6d master -> master ---- +////////////////////////// Then, you can see what the `master` branch on the `origin` remote was the last time you communicated with the server, by checking the `refs/remotes/origin/master` file: +////////////////////////// +`origin`의 `master` 브랜치에서 서버와 마지막으로 교환한 커밋이 어떤 것인지 `refs/remotes/origin/master` 파일에서 확인할 수 있다. [source,console] ---- @@ -201,6 +312,10 @@ $ cat .git/refs/remotes/origin/master ca82a6dff817ec66f44342007202690a93763949 ---- +////////////////////////// Remote references differ from branches (`refs/heads` references) mainly in that they're considered read-only. You can `git checkout` to one, but Git won't point HEAD at one, so you'll never update it with a `commit` command. Git manages them as bookmarks to the last known state of where those branches were on those servers. +////////////////////////// +`refs/heads`에 있는 Refs인 브랜치와 달리 리모트 Refs는 Checkout할 수 없고 읽기 용도로만 쓸 수 있는 브랜치인 것이다. +이 리모트 Refs는 서버의 브랜치가 가리키는 커밋이 무엇인지 적어둔 일종의 북마크이다. diff --git a/book/10-git-internals/sections/refspec.asc b/book/10-git-internals/sections/refspec.asc index 1abf5379..355b12eb 100644 --- a/book/10-git-internals/sections/refspec.asc +++ b/book/10-git-internals/sections/refspec.asc @@ -1,28 +1,46 @@ [[_refspec]] +////////////////////////// === The Refspec +////////////////////////// +=== Refspec +////////////////////////// Throughout this book, we've used simple mappings from remote branches to local references, but they can be more complex. Suppose you add a remote like this: +////////////////////////// +원격의 브랜치와 로컬 Refs를 간단히 매핑하는 것은 많이 봤다. 이 매핑은 실은 좀 더 복잡하다. +아래처럼 리모트 저장소를 추가해보자. [source,console] ---- $ git remote add origin https://github.com/schacon/simplegit-progit ---- +////////////////////////// It adds a section to your `.git/config` file, specifying the name of the remote (`origin`), the URL of the remote repository, and the refspec for fetching: +////////////////////////// +이 명령은 `origin` 이라는 저장소 이름, URL, Fetch할 Refspec를 `.git/config` 파일에 추가한다. [source,ini] ---- [remote "origin"] - url = https://github.com/schacon/simplegit-progit - fetch = +refs/heads/*:refs/remotes/origin/* + url = https://github.com/schacon/simplegit-progit + fetch = +refs/heads/*:refs/remotes/origin/* ---- +////////////////////////// The format of the refspec is an optional `+`, followed by `:`, where `` is the pattern for references on the remote side and `` is where those references will be written locally. The `+` tells Git to update the reference even if it isn't a fast-forward. +////////////////////////// +Refspec 형식은 `+`와 `:`로 돼 있다. `+`는 생략 가능하고, ``는 리모트 저장소의 Refs 패턴이고 ``는 매핑되는 로컬 저장소의 Refs 패턴이다. +`+`는 Fast-forward가 아닌 업데이트를 허용하는 것이다. +////////////////////////// In the default case that is automatically written by a `git remote add` command, Git fetches all the references under `refs/heads/` on the server and writes them to `refs/remotes/origin/` locally. So, if there is a `master` branch on the server, you can access the log of that branch locally via +////////////////////////// +기본적으로 Git은 `git remote add` 명령으로 생성한 설정을 참고하여 리모트 서버에서 `refs/heads/`에 있는 Refs를 가져다 로컬의 `refs/remotes/origin/`에 기록한다. +로컬에서 서버에 있는 `master` 브랜치에 접근할 때는 아래와 같이 한다. [source,console] ---- @@ -31,102 +49,161 @@ $ git log remotes/origin/master $ git log refs/remotes/origin/master ---- +////////////////////////// They're all equivalent, because Git expands each of them to `refs/remotes/origin/master`. +////////////////////////// +이 세 명령의 결과가 모두 같다. +Git은 모두 `refs/remotes/origin/master`라고 해석한다. +////////////////////////// If you want Git instead to pull down only the `master` branch each time, and not every other branch on the remote server, you can change the fetch line to +////////////////////////// +`master` 브랜치만 가져올 수 있게 하려면 `fetch` 부분을 아래와 같이 바꿔준다. 그러면 다른 브랜치는 가져올 수 없다. [source] ---- fetch = +refs/heads/master:refs/remotes/origin/master ---- +////////////////////////// This is just the default refspec for `git fetch` for that remote. If you want to do something one time, you can specify the refspec on the command line, too. To pull the `master` branch on the remote down to `origin/mymaster` locally, you can run +////////////////////////// +이는 해당 리모트 저장소에서 `git fetch` 명령을 실행할 때 자동으로 사용되는 Refspec이다. +다른 Refspec을 가져오려면 명령의 아규먼트로 넘긴다. +리모트 브랜치 `master`를 로컬 브랜치 `origin/mymaster`로 가져오려면 아래와 같이 실행한다. [source,console] ---- $ git fetch origin master:refs/remotes/origin/mymaster ---- +////////////////////////// You can also specify multiple refspecs. On the command line, you can pull down several branches like so: +////////////////////////// +Refspec을 여러 개 넘겨도 된다. +한꺼번에 브랜치를 여러 개 가져온다. [source,console] ---- $ git fetch origin master:refs/remotes/origin/mymaster \ - topic:refs/remotes/origin/topic + topic:refs/remotes/origin/topic From git@github.com:schacon/simplegit ! [rejected] master -> origin/mymaster (non fast forward) * [new branch] topic -> origin/topic ---- +////////////////////////// In this case, the master branch pull was rejected because it wasn't a fast-forward reference. You can override that by specifying the `+` in front of the refspec. +////////////////////////// +여기서 `master` 브랜치는 Fast-forward가 아니라서 거절된다. +하지만, Refspec 앞에 `+`를 추가하면 강제로 덮어쓴다. +////////////////////////// You can also specify multiple refspecs for fetching in your configuration file. If you want to always fetch the master and experiment branches, add two lines: +////////////////////////// +설정 파일에도 Refspec을 여러 개 적을 수 있다. +`master`와 `experiment` 브랜치를 둘 다 적으면 항상 함께 가져온다. [source,ini] ---- [remote "origin"] - url = https://github.com/schacon/simplegit-progit - fetch = +refs/heads/master:refs/remotes/origin/master - fetch = +refs/heads/experiment:refs/remotes/origin/experiment + url = https://github.com/schacon/simplegit-progit + fetch = +refs/heads/master:refs/remotes/origin/master + fetch = +refs/heads/experiment:refs/remotes/origin/experiment ---- +////////////////////////// You can't use partial globs in the pattern, so this would be invalid: +////////////////////////// +하지만, Glob 패턴은 사용할 수 없다. [source] ---- fetch = +refs/heads/qa*:refs/remotes/origin/qa* ---- +////////////////////////// However, you can use namespaces (or directories) to accomplish something like that. If you have a QA team that pushes a series of branches, and you want to get the master branch and any of the QA team's branches but nothing else, you can use a config section like this: +////////////////////////// +그 대신 네임스페이스 형식(디렉토리 형식)으로는 사용할 수 있다. +만약 QA 팀이 Push하는 브랜치가 있고 이 브랜치를 가져오고 싶으면 아래와 같이 설정한다. 다음은 `master` 브랜치와 QA 팀의 브랜치만 가져오는 설정이다. [source,ini] ---- [remote "origin"] - url = https://github.com/schacon/simplegit-progit - fetch = +refs/heads/master:refs/remotes/origin/master - fetch = +refs/heads/qa/*:refs/remotes/origin/qa/* + url = https://github.com/schacon/simplegit-progit + fetch = +refs/heads/master:refs/remotes/origin/master + fetch = +refs/heads/qa/*:refs/remotes/origin/qa/* ---- +////////////////////////// If you have a complex workflow process that has a QA team pushing branches, developers pushing branches, and integration teams pushing and collaborating on remote branches, you can namespace them easily this way. +////////////////////////// +좀 더 복잡한 것도 가능하다. QA 팀뿐만 아니라, 일반 개발자, 통합 팀 등이 사용하는 브랜치를 네임스페이스 별로 구분해 놓으면 좀 더 Git을 편리하게 사용할 수 있다. [[_pushing_refspecs]] +////////////////////////// ==== Pushing Refspecs +////////////////////////// +==== Refspec Push하기 +////////////////////////// It's nice that you can fetch namespaced references that way, but how does the QA team get their branches into a `qa/` namespace in the first place? You accomplish that by using refspecs to push. +////////////////////////// +위와 같은 방식으로 네임스페이스를 사용하여 리모트 브랜치를 구별하여 사용하는 것은 꽤 괜찮은 방법이다. 만약 QA팀이 네임스페이스를 사용하지 않는 브랜치를 리모트에 네임스페이스를 써서 Push하려면 어떻게 해야 할까? +이럴 땐 Refspec으로 가능하다. +////////////////////////// If the QA team wants to push their `master` branch to `qa/master` on the remote server, they can run +////////////////////////// +QA 팀이 `master` 브랜치를 리모트 저장소에 `qa/master`로 Push 하려면 다음과 같이 한다. [source,console] ---- $ git push origin master:refs/heads/qa/master ---- +////////////////////////// If they want Git to do that automatically each time they run `git push origin`, they can add a `push` value to their config file: +////////////////////////// +`git push origin`을 실행할 때마다 Git이 자동으로 Push하게 하려면 아래와 같이 설정 파일에 `push` 항목을 추가한다. [source,ini] ---- [remote "origin"] - url = https://github.com/schacon/simplegit-progit - fetch = +refs/heads/*:refs/remotes/origin/* - push = refs/heads/master:refs/heads/qa/master + url = https://github.com/schacon/simplegit-progit + fetch = +refs/heads/*:refs/remotes/origin/* + push = refs/heads/master:refs/heads/qa/master ---- +////////////////////////// Again, this will cause a `git push origin` to push the local `master` branch to the remote `qa/master` branch by default. +////////////////////////// +다시 말하자면 위와 같이 설정은 `git push origin`을 실행할 때 로컬 브랜치 `master`를 리모트 브랜치 `qa/master`로 Push하도록 하는 설정이다. +////////////////////////// ==== Deleting References +////////////////////////// +==== Refs 삭제하기 +////////////////////////// You can also use the refspec to delete references from the remote server by running something like this: +////////////////////////// +Refspec으로 서버에 있는 Refs를 삭제할 수 있다. [source,console] ---- $ git push origin :topic ---- +////////////////////////// Because the refspec is `:`, by leaving off the `` part, this basically says to make the topic branch on the remote nothing, which deletes it. +////////////////////////// +Refspec의 형식은 `:`이니까 ``를 비우고 실행하면 ``를 비우라는 명령이 된다. 이 결과 ``는 삭제된다. diff --git a/book/10-git-internals/sections/transfer-protocols.asc b/book/10-git-internals/sections/transfer-protocols.asc index 78dc8340..47717f83 100644 --- a/book/10-git-internals/sections/transfer-protocols.asc +++ b/book/10-git-internals/sections/transfer-protocols.asc @@ -1,29 +1,55 @@ +////////////////////////// === Transfer Protocols +////////////////////////// +=== 데이터 전송 프로토콜 +////////////////////////// Git can transfer data between two repositories in two major ways: the ``dumb'' protocol and the ``smart'' protocol. This section will quickly cover how these two main protocols operate. +////////////////////////// +Git에서 데이터를 전송할 때 보통 두 가지 종류의 프로토콜을 사용한다. 하나는 ``dumb'' 프로토콜이고 다른 종류는 ``스마트'' 프로토콜이다. +두 종류 프로토콜을 통해 Git이 어떻게 데이터를 전송하는지 살펴본다. +////////////////////////// ==== The Dumb Protocol +////////////////////////// +==== Dumb 프로토콜 +////////////////////////// If you're setting up a repository to be served read-only over HTTP, the dumb protocol is likely what will be used. This protocol is called ``dumb'' because it requires no Git-specific code on the server side during the transport process; the fetch process is a series of HTTP `GET` requests, where the client can assume the layout of the Git repository on the server. +////////////////////////// +읽기전용으로만 사용하는 HTTP 저장소를 Clone하거나 Fetch할 때가 Dumb 프로토콜을 사용하는 때이다. +Dumb 프로토콜이라 부르는 이유는 서버가 데이터를 전송할 때 Git에 최적화된 어떤 작업도 전혀 사용하지 않기 때문이다. 단지 Fetch 과정은 HTTP `GET` 요청을 여러 번 보낼 뿐이다. 이때 클라이언트는 서버의 Git 저장소 레이아웃이 특별하지 않다고 가정한다. [NOTE] ==== +////////////////////////// The dumb protocol is fairly rarely used these days. It's difficult to secure or make private, so most Git hosts (both cloud-based and on-premises) will refuse to use it. It's generally advised to use the smart protocol, which we describe a bit further on. +////////////////////////// +요즘은 Dumb 프로토콜을 사용하는 경우가 드물다. +Dumb 프로토콜을 사용하면 데이터 전송을 비밀스럽게 하기 어려워서 비공개용 저장소의 데이터를 전송하기에 적합하지 않다. +이후에 설명할 스마트 프로토콜을 사용하도록 조언하는 바이다. ==== +////////////////////////// Let's follow the `http-fetch` process for the simplegit library: +////////////////////////// +`simplegit` 라이브러리에 대한 `http-fetch` 과정을 살펴보자. [source,console] ---- $ git clone http://server/simplegit-progit.git ---- +////////////////////////// The first thing this command does is pull down the `info/refs` file. This file is written by the `update-server-info` command, which is why you need to enable that as a `post-receive` hook in order for the HTTP transport to work properly: +////////////////////////// +우선 `info/refs` 파일을 내려받는다. +이 파일은 `update-server-info` 명령으로 작성되기 때문에 `post-receive` 훅에서 `update-server-info` 명령을 호출해줘야만 HTTP를 사용할 수 있다. [source] ---- @@ -31,8 +57,12 @@ This file is written by the `update-server-info` command, which is why you need ca82a6dff817ec66f44342007202690a93763949 refs/heads/master ---- +////////////////////////// Now you have a list of the remote references and SHAs. Next, you look for what the HEAD reference is so you know what to check out when you're finished: +////////////////////////// +리모트 Refs와 SHA 값이 든 목록을 가져왔고 다음은 HEAD Refs를 찾는다. +이 HEAD Refs 덕택에 데이터를 내려받고 나서 어떤 Refs를 Checkout할 지 알게 된다. [source] ---- @@ -40,9 +70,14 @@ Next, you look for what the HEAD reference is so you know what to check out when ref: refs/heads/master ---- +////////////////////////// You need to check out the `master` branch when you've completed the process. At this point, you're ready to start the walking process. Because your starting point is the `ca82a6` commit object you saw in the `info/refs` file, you start by fetching that: +////////////////////////// +데이터 전송을 마치면 `master` 브랜치를 Checkout해야 한다. +지금은 아직 전송을 시작하는 시점이다. +`info/refs`에 `ca82a6` 커밋에서 시작해야 한다고 나와 있다. 그래서 그 커밋을 기점으로 Fetch한다. [source] ---- @@ -50,8 +85,12 @@ Because your starting point is the `ca82a6` commit object you saw in the `info/r (179 bytes of binary data) ---- +////////////////////////// You get an object back – that object is in loose format on the server, and you fetched it over a static HTTP GET request. You can zlib-uncompress it, strip off the header, and look at the commit content: +////////////////////////// +서버에 Loose 포맷으로 돼 있기 때문에 HTTP 서버에서 정적 파일을 가져오듯이 개체를 가져오면 된다. +이렇게 서버로부터 얻어온 개체를 zlib로 압축을 풀고 Header를 떼어 내면 아래와 같은 모습이 된다. [source,console] ---- @@ -64,7 +103,11 @@ committer Scott Chacon 1240030591 -0700 changed the version number ---- +////////////////////////// Next, you have two more objects to retrieve – `cfda3b`, which is the tree of content that the commit we just retrieved points to; and `085bb3`, which is the parent commit: +////////////////////////// +아직 개체를 두 개 더 내려받아야 한다. +`cfda3b` 개체는 방금 내려받은 커밋의 Tree 개체이고, `085bb3` 개체는 부모 커밋 개체이다. [source] ---- @@ -72,8 +115,12 @@ Next, you have two more objects to retrieve – `cfda3b`, which is the tree of c (179 bytes of data) ---- +////////////////////////// That gives you your next commit object. Grab the tree object: +////////////////////////// +커밋 개체는 내려받았다. +하지만, Tree 개체를 내려받으려고 하니 다음과 같은 오류가 발생한다. [source] ---- @@ -81,9 +128,14 @@ Grab the tree object: (404 - Not Found) ---- +////////////////////////// Oops – it looks like that tree object isn't in loose format on the server, so you get a 404 response back. There are a couple of reasons for this – the object could be in an alternate repository, or it could be in a packfile in this repository. Git checks for any listed alternates first: +////////////////////////// +이런! 존재하지 않는다는 404 메시지가 뜬다. +해당 Tree 개체가 서버에 Loose 포맷으로 저장돼 있지 않을 수 있다. 해당 개체가 다른 저장소에 있거나 저장소의 Packfile 속에 들어 있을 때 그렇다. +우선 Git은 다른 저장소 목록에서 찾는다. [source] ---- @@ -91,9 +143,14 @@ Git checks for any listed alternates first: (empty file) ---- +////////////////////////// If this comes back with a list of alternate URLs, Git checks for loose files and packfiles there – this is a nice mechanism for projects that are forks of one another to share objects on disk. However, because no alternates are listed in this case, your object must be in a packfile. To see what packfiles are available on this server, you need to get the `objects/info/packs` file, which contains a listing of them (also generated by `update-server-info`): +////////////////////////// +다른 저장소 목록에 없으면 Git은 Packfile에서 해당 개체를 찾는다. 이렇게 하면 프로젝트를 Fork해도 디스크 공간을 효율적으로 사용할 수 있다. +우선 서버에서 받은 다른 저장소 목록에는 없어서 개체는 확실히 Packfile 속에 있다. +어떤 Packfile이 있는지는 `objects/info/packs` 파일에 들어 있다. 이 파일도 `update-server-info` 명령이 생성한다. [source] ---- @@ -101,8 +158,12 @@ To see what packfiles are available on this server, you need to get the `objects P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack ---- +////////////////////////// There is only one packfile on the server, so your object is obviously in there, but you'll check the index file to make sure. This is also useful if you have multiple packfiles on the server, so you can see which packfile contains the object you need: +////////////////////////// +서버에는 Packfile이 하나 있다. 개체는 이 파일 속에 있다. 이 개체가 있는지 Packfile의 Index(Packfile이 포함하는 파일의 목록)에서 찾는다. +서버에 Packfile이 여러 개 있으면 이런 식으로 개체가 어떤 Packfile에 있는지 찾는다. [source] ---- @@ -110,8 +171,12 @@ This is also useful if you have multiple packfiles on the server, so you can see (4k of binary data) ---- +////////////////////////// Now that you have the packfile index, you can see if your object is in it – because the index lists the SHAs of the objects contained in the packfile and the offsets to those objects. Your object is there, so go ahead and get the whole packfile: +////////////////////////// +이제 Packfile의 Index를 가져와서 개체가 있는지 확인한다. Packfile Index에서 해당 개체의 SHA 값과 오프셋을 파악한다. +개체를 찾았으면 해당 Packfile을 내려받는다. [source] ---- @@ -119,66 +184,117 @@ Your object is there, so go ahead and get the whole packfile: (13k of binary data) ---- +////////////////////////// You have your tree object, so you continue walking your commits. They're all also within the packfile you just downloaded, so you don't have to do any more requests to your server. Git checks out a working copy of the `master` branch that was pointed to by the HEAD reference you downloaded at the beginning. +////////////////////////// +Tree 개체를 얻어 오고 나면 커밋 데이터를 가져 온다. +아마도 방금 내려받은 Packfile 속에 모든 커밋 데이터가 들어 있을 것이다. 서버에 더는 전송 요청을 보내지 않는다. +다 끝나면 Git은 HEAD가 가리키는 `master` 브랜치의 소스코드를 복원해놓는다. +////////////////////////// ==== The Smart Protocol +////////////////////////// +==== 스마트 프로토콜 +////////////////////////// The dumb protocol is simple but a bit inefficient, and it can't handle writing of data from the client to the server. The smart protocol is a more common method of transferring data, but it requires a process on the remote end that is intelligent about Git – it can read local data, figure out what the client has and needs, and generate a custom packfile for it. There are two sets of processes for transferring data: a pair for uploading data and a pair for downloading data. +////////////////////////// +Dumb 프로토콜은 매우 단순하다는 장점이 있으나 데이터를 효율적으로 전송할 수 없다. +스마트 프로토콜로 데이터를 전송하는 것이 더 일반적이다. 이 프로토콜은 리모트 서버에서 처리해야 할 작업이 있다. 서버는 클라이언트가 어떤 데이터를 갖고 있고 어떤 데이터가 필요한지 분석하여 실제로 전송할 데이터를 추려낸다. +서버가 할 일을 두 가지 일로 구분할 수 있는데 데이터를 업로드할 때 하는 일과 다운로드할 때 하는 일이 다르다. +////////////////////////// ===== Uploading Data +////////////////////////// +===== 데이터 업로드 (((git commands, send-pack)))(((git commands, receive-pack))) +////////////////////////// To upload data to a remote process, Git uses the `send-pack` and `receive-pack` processes. The `send-pack` process runs on the client and connects to a `receive-pack` process on the remote side. +////////////////////////// +리모트 서버로 데이터를 업로드하는 과정은 `send-pack` 과 `receive-pack` 과정으로 나눌 수 있다. +클라이언트에서 실행되는 `send-pack`과 서버의 `receive-pack`은 서로 연결된다. ====== SSH +////////////////////////// For example, say you run `git push origin master` in your project, and `origin` is defined as a URL that uses the SSH protocol. Git fires up the `send-pack` process, which initiates a connection over SSH to your server. It tries to run a command on the remote server via an SSH call that looks something like this: +////////////////////////// +`origin` URL이 SSH URL인 상태에서 `git push origin master` 명령을 실행하면 +Git은 `send-pack`을 시작한다. +이 과정에서는 SSH 연결을 만들고 이 SSH 연결을 통해서 다음과 같은 명령어를 실행한다. [source,console] ---- $ ssh -x git@server "git-receive-pack 'simplegit-progit.git'" 005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status \ - delete-refs side-band-64k quiet ofs-delta \ - agent=git/2:2.1.1+github-607-gfba4028 delete-refs + delete-refs side-band-64k quiet ofs-delta \ + agent=git/2:2.1.1+github-607-gfba4028 delete-refs 003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic 0000 ---- +////////////////////////// The `git-receive-pack` command immediately responds with one line for each reference it currently has – in this case, just the `master` branch and its SHA. The first line also has a list of the server's capabilities (here, `report-status`, `delete-refs`, and some others, including the client identifier). +////////////////////////// +`git-receive-pack` 명령은 Refs 정보를 한 줄에 하나씩 보여준다. +첫 번째 줄에는 `master` 브랜치의 이름과 SHA 체크섬을 보여주는데 여기에 서버의 Capability도 함께 보여준다(여기서는 `report-status`, `delete-refs`, 기타 등등과 클라이언트 Identifier를 표시한다). +////////////////////////// Each line starts with a 4-character hex value specifying how long the rest of the line is. Your first line starts with 005b, which is 91 in hex, meaning that 91 bytes remain on that line. The next line starts with 003e, which is 62, so you read the remaining 62 bytes. The next line is 0000, meaning the server is done with its references listing. +////////////////////////// +각 줄의 처음은 4 바이트는 뒤에 이어지는 나머지 데이터의 길이를 나타낸다. +첫 줄을 보자. 005b로 시작하는데 10진수로 91을 나타낸다. 첫 줄의 처음 4 바이트를 제외한 나머지 길이가 91바이트라는 뜻이다. +다음 줄의 값은 003b이고 이는 62바이트를 나타낸다. +마지막 줄은 값은 0000이다. 이는 서버가 Refs 목록의 출력을 끝냈다는 것을 의미한다. +////////////////////////// Now that it knows the server's state, your `send-pack` process determines what commits it has that the server doesn't. For each reference that this push will update, the `send-pack` process tells the `receive-pack` process that information. For instance, if you're updating the `master` branch and adding an `experiment` branch, the `send-pack` response may look something like this: +////////////////////////// +서버에 뭐가 있는지 알기 때문에 이제 서버에 없는 커밋이 무엇인지 알 수 있다. +Push할 Refs에 대한 정보는 `send-pack` 과정에서 서버의 `receive-pack` 과정으로 전달된다. +예를 들어 `master` 브랜치를 업데이트하고 `experiment` 브랜치를 추가할 때는 아래와 같은 정보를 서버에 보낸다. [source] ---- 0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \ - refs/heads/master report-status + refs/heads/master report-status 00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \ - refs/heads/experiment + refs/heads/experiment 0000 ---- +////////////////////////// Git sends a line for each reference you're updating with the line's length, the old SHA, the new SHA, and the reference that is being updated. The first line also has the client's capabilities. The SHA-1 value of all '0's means that nothing was there before – because you're adding the experiment reference. If you were deleting a reference, you would see the opposite: all '0's on the right side. - +////////////////////////// +Git은 예전 SHA, 새 SHA, Refs 이름을 한 줄 한 줄에 담아 전송한다. +첫 줄에는 클라이언트 Capability도 포함된다. +SHA-1 값이 모두 '0'인 것은 없음(無)을 의미한다. +`experiment` Refs는 새로 추가하는 것이라서 왼쪽 SHA-1값이 모두 0이다. +반대로 오른쪽 SHA-1 값이 모두 '0'이면 Refs를 삭제한다는 의미다. + +////////////////////////// Next, the client sends a packfile of all the objects the server doesn't have yet. Finally, the server responds with a success (or failure) indication: +////////////////////////// +그다음에 서버에 없는 객체를 전부 하나의 Packfile에 담아 전송한다. +마지막에 서버는 성공했거나 실패했다고 응답한다. [source] ---- @@ -187,63 +303,96 @@ Finally, the server responds with a success (or failure) indication: ====== HTTP(S) +////////////////////////// This process is mostly the same over HTTP, though the handshaking is a bit different. The connection is initiated with this request: +////////////////////////// +HTTP를 통해 데이터를 업로드하는 과정도 크게 다르지 않지만 처음 핸드쉐이킹 과정만 약간 다르다. +우선 다음과 같은 요청으로 시작한다. [source] ---- => GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack 001f# service=git-receive-pack 000000ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master \ - report-status delete-refs side-band-64k quiet ofs-delta \ - agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e + report-status delete-refs side-band-64k quiet ofs-delta \ + agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e 0000 ---- +////////////////////////// That's the end of the first client-server exchange. The client then makes another request, this time a `POST`, with the data that `git-upload-pack` provides. +////////////////////////// +첫 번째 클라이언트 요청과 서버의 응답이다. +이어지는 클라이언트 요청은 `POST` 메소드를 써서 `git-upload-pack`이 제공하는 데이터를 서버로 전송하는 요청이다. [source] ---- => POST http://server/simplegit-progit.git/git-receive/pack ---- +////////////////////////// The `POST` request includes the `send-pack` output and the packfile as its payload. The server then indicates success or failure with its HTTP response. +////////////////////////// +`POST` 요청은 `send-pack`의 결과와 Packfile을 데이터로 전송한다. +전송한 데이터가 서버에서 처리된 결과가 HTTP 응답으로 전달된다. +////////////////////////// ===== Downloading Data +////////////////////////// +===== 데이터 다운로드 (((git commands, fetch-pack)))(((git commands, upload-pack))) +////////////////////////// When you download data, the `fetch-pack` and `upload-pack` processes are involved. The client initiates a `fetch-pack` process that connects to an `upload-pack` process on the remote side to negotiate what data will be transferred down. +////////////////////////// +데이터를 다운로드하는 것는 `fetch-pack`과 `upload-pack` 과정으로 나뉜다. +클라이언트가 `fetch-pack`을 시작하면 서버의 `upload-pack`에 연결되고 서로 어떤 데이터를 내려받을지 결정한다. ====== SSH +////////////////////////// If you're doing the fetch over SSH, `fetch-pack` instead runs something like this: +////////////////////////// +SSH 프로토콜을 사용하면 `fetch-pack`은 아래와 같이 실행한다. [source,console] ---- $ ssh -x git@server "git-upload-pack 'simplegit-progit.git'" ---- +////////////////////////// After `fetch-pack` connects, `upload-pack` sends back something like this: +////////////////////////// +`fetch-pack`과 연결된 `upload-pack`은 아래와 같은 데이터를 전송한다. [source] ---- 00dfca82a6dff817ec66f44342007202690a93763949 HEADmulti_ack thin-pack \ - side-band side-band-64k ofs-delta shallow no-progress include-tag \ - multi_ack_detailed symref=HEAD:refs/heads/master \ - agent=git/2:2.1.1+github-607-gfba4028 + side-band side-band-64k ofs-delta shallow no-progress include-tag \ + multi_ack_detailed symref=HEAD:refs/heads/master \ + agent=git/2:2.1.1+github-607-gfba4028 003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master 0000 ---- +////////////////////////// This is very similar to what `receive-pack` responds with, but the capabilities are different. In addition, it sends back what HEAD points to (`symref=HEAD:refs/heads/master`) so the client knows what to check out if this is a clone. +////////////////////////// +위 `receive-pack`의 응답과 매우 비슷하지만, Capability 부분은 다르다. +HEAD Refs(`symref=HEAD:refs/heads/master`)도 알려주기 때문에 저장소를 Clone하면 무엇을 Checkout해야 할지 안다. +////////////////////////// At this point, the `fetch-pack` process looks at what objects it has and responds with the objects that it needs by sending ``want'' and then the SHA it wants. It sends all the objects it already has with ``have'' and then the SHA. At the end of this list, it writes ``done'' to initiate the `upload-pack` process to begin sending the packfile of the data it needs: +////////////////////////// +`fetch-pack`은 이 정보를 살펴보고 이미 가지는 개체에는 ``have''를 붙이고 내려받아야 하는 개체는 ``want''를 붙인 정보를 만든다. +마지막 줄에 ``done''이라고 적어서 보내면 서버의 `upload-pack`은 해당 데이터를 Packfile로 만들어 전송한다. [source] ---- @@ -255,22 +404,29 @@ At the end of this list, it writes ``done'' to initiate the `upload-pack` proces ====== HTTP(S) +////////////////////////// The handshake for a fetch operation takes two HTTP requests. The first is a `GET` to the same endpoint used in the dumb protocol: +////////////////////////// +HTTP로 Fetch하는 과정은 두 개의 HTTP 요청으로 이루어진다. +첫 번째 요청은 `GET` 요청으로 응답 결과는 SSH에서 본 내용과 같다. [source] ---- => GET $GIT_URL/info/refs?service=git-upload-pack 001e# service=git-upload-pack 000000e7ca82a6dff817ec66f44342007202690a93763949 HEADmulti_ack thin-pack \ - side-band side-band-64k ofs-delta shallow no-progress include-tag \ - multi_ack_detailed no-done symref=HEAD:refs/heads/master \ - agent=git/2:2.1.1+github-607-gfba4028 + side-band side-band-64k ofs-delta shallow no-progress include-tag \ + multi_ack_detailed no-done symref=HEAD:refs/heads/master \ + agent=git/2:2.1.1+github-607-gfba4028 003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master 0000 ---- +////////////////////////// This is very similar to invoking `git-upload-pack` over an SSH connection, but the second exchange is performed as a separate request: +////////////////////////// +이 결과는 SSH 연결을 사용할 때 `git-upload-pack` 명령을 실행한 것과 비슷하지만 이어지는 두 번째 요청이 다르다. [source] ---- @@ -280,11 +436,23 @@ This is very similar to invoking `git-upload-pack` over an SSH connection, but t 0000 ---- +////////////////////////// Again, this is the same format as above. The response to this request indicates success or failure, and includes the packfile. +////////////////////////// +전송할 내용은 앞에서 살펴본 것과 같다. +전송한 데이터를 서버에서 처리된 결과가 HTTP 응답으로 전달되고 결과에 따라 Packfile이 포함되어 있을 수 있다. +////////////////////////// ==== Protocols Summary +////////////////////////// +==== 프로토콜 요약 +////////////////////////// This section contains a very basic overview of the transfer protocols. The protocol includes many other features, such as `multi_ack` or `side-band` capabilities, but covering them is outside the scope of this book. We've tried to give you a sense of the general back-and-forth between client and server; if you need more knowledge than this, you'll probably want to take a look at the Git source code. +////////////////////////// +이번 절을 통해 Git이 사용하는 데이터 전송 프로토콜을 간단하게 살펴보았다. +Git이 사용하는 데이터 전송 프로토콜에는 `multi_ack`나 `side-band` 같은 추가적인 많은 기능도 포함하고 있지만, 이 책에서 다룰 수 없어 설명하지는 않는다. +이 책의 내용은 Git이 어떻게 클라이언트와 서버 간에 데이터를 주고받는지 기본적인 느낌을 전달하기 위해 노력한다. 데이터 전송 프로토콜의 많은 기능을 활용해보고 싶다면 Git 소스코드를 살펴보는 것이 좋다.