== 기록공부 ==

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

어떤 개발자들은 파일의 기록은 절대로 바뀌면 안되는 것이라고 믿고 있습니다. 
또 어떤 개발자들은 수정 기록들이 깨끗하게 정리되어야 한다고 합니다.
Git은 이렇게 다른 성향의 개발자들을 모두 포용할 수 있습니다. 클로닝, 나뭇가지, 병합과 같은
기능들과 같이 파일의 기록들을 바꾸는 것은 Git이 할 수있는 많은 기능들 중에 하나일 뿐입니다.
어떻게 영리하게 사용하는지는 당신에게 달려있죠.

=== 오류 수정합니다 ===

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

 $ git commit --amend

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

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

 $ git commit --amend -a

=== ... 더 있습니다 ===

전에 보았던 문제가 10배 더 힘들었다고 생각합니다. 긴 시간동안 작업해서 많은
commit을 하였다고 가정합니다. 그러나 당신은 그 commit들의 난잡한 구성이 마음에 들지
않습니다. 그리고 어떤 commit 메세지들은 다시쓰고 싶습니다. 그렇다면:

 $ git rebase -i HEAD~10

위 명령어를 사용한다면 당신이 좋아하는 작업용 에디터에 지난 열 개의 commit이 출력될 것입니다. 샘플을 보자면:
 
    pick 5c6eb73 Added repo.or.cz link
    pick a311a64 Reordered analogies in "Work How You Want"
    pick 100834f Added push target to Makefile

여기서는 오래된 commit이  'log' 명령어와 달리 새로운 commit보다 먼저 출력되어 나옵니다.
여기서는 5c6eb73 가 가장 오래된 commit이고 100834f이 가장 최근 commit 이죠. 그리고:
Here, 5c6eb73 is the oldest commit, and 100834f is the newest. Then:

- 한 줄을 지움으로써 commit을 삭제합니다. 'revert' 명령어와 같으나 기록에는 남지 않게 지웁니다.
	이 전략은 마치 commit이 처음부터 존재하지 않던 것처럼 보여지게 해줍니다.
- 행들을 재정렬하며 commit의 순서를 바꾸어 줍니다.
- 'pick' 명령어 대신에
   * `edit` 을 사용하여 개정시킬 commit을 마킹합니다.
   * `reword`를 사용하여 로그메세지를 바꿉니다.
   * `squash` 를 사용하여 전에 했던 commit과 합병합니다.
   * `fixup` 를 사용하여 전에 했던 commit과 합병 후 log 메세지를 삭제합니다.

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

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

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

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

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

 $ git rebase --continue

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

새로운 평가기준 (rebase)을 포기할 수도 있습니다:

 $ git rebase --abort

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

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

그러나 당신의 로컬 Git클론은 공식적인 파일기록와 개인으로 만든 파일기록이 뒤죽박죽 섞여있을 것입니다. 아무래도 공식적인 기록과 개인적인 기록이 분류되어 출력되면 기록을 확인하기가 쉽겠지요.

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

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

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

=== 기록 다시쓰기 ===

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

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

*git help filter-branch*를 보세요. 여기서는 본 예시에 대해 설명하고 있고 더 빠른 방법을
제시하여 줄 것입니다. 대체적으로 *filter-branch*은 하나의 명령어만으로도 
대량의 파일기록을 변화시킬 수 있을 것입니다.
 
 그리고 +.git/refs/original+ 디렉토리는 이렇게 만든 변화가 오기 전의 기록을 보여줄 것입니다. *filter-branch* 명령어가 어떻게 작용했는지 확인할수도 있고, 이 디렉토리를 지운다면 더 많은 *filter-branch*명령어를 실행시킬 수 있습니다.

마지막으로, 나중에 새로운 버전에서 작업을 진행하고 싶다면 당신의 클론을 새로운 버전의 클론으로 바꾸시면 됩니다.

=== 기록 만들기 ===

[[makinghistory]]
프로젝트를 Git으로 옮겨오고 싶다고요? 다른 버전 관리 시스템에서 옮겨오는 것이라면, 어떤 개발자가 이미 프로젝트의 기록을 Git으로 옮겨오는 스크립트를 써두었을지도 모릅니다.

아니라면,  특정 포맷으로 텍스트를 읽어 Git 기록을 처음부터 작성하여 주는 
*git fast-import* 명령어를 사용해서 확인해 보세요. 대체적으로 기록 스크립트는 
한번에 간편하게 이 명령어를 사용해서 만들어졌을 것입니다.

예로, '/tmp/history' 같은 임시파일에 다음 텍스트를 붙여넣기 해보세요:
----------------------------------
commit refs/heads/master
committer Alice <alice@example.com> Thu, 01 Jan 1970 00:00:00 +0000
data <<EOT
Initial commit.
EOT

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

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


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

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

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

----------------------------------

그리고 Git 저장소를 만들어보세요:

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

가장 최근 버전을 가져오고 싶다면:
 
 $ git checkout master .

*git fast-export* 명령어는 아무 Git 저장소를 결과물이 사람들이 읽을 수
있는 포맷으로 바꾸어 주는 *git fast-import* 포맷으로 바꾸어 줍니다.
이 명령어들은 텍스트 파일들을 텍스트 파일 전용채널을 통해서
저장소로 밀어넣기 해줍니다.
 
=== 어디서부터 잘못되었을까? ===

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

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

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

Git에서 한 프로젝트를 반 (good과 bad) 으로 나누어서 테스팅을 실행합니다. 그리고 아직도 버그가 발견된다면:
 
 $ git bisect bad

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

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

 Git 은 기존 대비할 스크립트에 약간의 변화를 주어서 이것이 좋은 변화인지
 안 좋은 변화인지 체크합니다: 좋은 변화는 0으로 무시해야할 변화는 125로 
 안 좋은 변화는 1과 127사이의 번호로 테스팅을 종료합니다. 마이너스 숫자는
 이분화  (bisect)를 강제종료하지요.

 당신은 이것보다 더 많은 일을 할 수 있습니다. 도움말은 이분화를 시각화해주는 방법과, 
 이분화 기록을 다시보는 방법, 확인된 이상없는 변화들은 건너띄어 테스팅을 가속 시켜주는 기능들을
 배우실 수 있습니다.

=== 누가 망가뜨렸을까? ===

다른 버전 관리 시스템들과 같이 Git은 누군가를 탓할 수 있는 기능이 있습니다:

 $ git blame bug.c

이 명령어를 사용하면 누가 언제 마지막으로 코딩작업을 했는지 표시하여 줍니다. 다른 버전 관리 시스템들과는 달리 모든 작업은 오프라인에서 진행됩니다.

=== 나의 개인경험담 ===

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

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

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

제가 중앙 버전 관리 시스템을 처음 사용하게 되었을땐 완전 쇼킹이였죠. 불안정한 인터 연결은
Git을 사용할 때 중요치 않습니다. 그러나 이러한 인터넷연결은 로컬디스크에서 작업하는 것
만큼은 절대 될 수 없죠. 그리고 저는 어떤 명령어는 연결 딜레이를 고려함에 따라
쓰지 않게되는 걸 시간이 지나며 깨달았습니다. 이런 행동은 제가 정말 하고싶었던
작업을 완벽히 이행하지 못하게 하는 방해물이 되었죠.

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

공유된는 비극도 존재했죠: 네트워크 상황이 원활하지 않을 것이라는 기대와 미래에 
네트워크 딜레이를 줄이기위해 기울인 노력들은 오히려 트래픽을 더 잡아먹을 수가
있다는 것입니다. 모든 개발자들이 네트워크를 원활하게하는 노력을 할수록 오히려
해가 될 수있다는 것입니다. 이게 무슨 아이러니한 일입니까?