== Git 마스터링 ==

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

=== 소스 공개 ===

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

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

=== 바뀐 것은 꼭 commit ===

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

 $ git add .
 $ git add -u

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

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

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

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

=== 내 commit이 너무 클 경우? ===

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

걱정하지말고:

 $ git add -p

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

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

 $ git commit

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

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

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

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

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

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

=== 대가리(HEAD)를 잃어버리지 않기 ===

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

 $ git reset HEAD~3

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

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

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

 $ git reset 1b6d

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

 $ git reset ORIG_HEAD

=== HEAD-헌팅 ===

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

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

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

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

  $ git reflog

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

 $ git checkout "@{10 minutes ago}" 

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

 $ git checkout "@{5}"

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

Commit의 2주의 생명을 연장하여 줄 수 있습니다. 예를 들어:

  $ git config gc.pruneexpire "30 days"

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

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

  $ git config gc.auto 0

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

=== Git을 좀 더 발전시키는 방법 ===

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

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

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

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

  $ git symbolic-ref HEAD

위 명령어는 현재 작업중인 나뭇가지 이름을 표기하여 줍니다. 실용적으로는 "refs/heads/"를 사용하지 않으며
잠재적으로 일어날 수 있는 에러들을 무시하여 줍니다:

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

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

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

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

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

=== 용감한 스턴트 ===

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

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

  $ git checkout -f HEAD^

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

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

  $ git reset --hard 1b6d

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

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

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

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

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

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

  $ git clean -f -d

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

=== 원치않는 commit들을 방지하기 ===

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

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

 $ cd .git/hooks
 $ cp pre-commit.sample pre-commit  # Older Git versions: chmod +x pre-commit

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

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

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

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