[도서 정리] 7. Git 도구 #1 - ProGit |
7.1. 리비전 조회하기
* 리비전 하나 가리키기
* SHA-1 줄여 쓰기
-
Git 은 해시값의 앞 몇 글자만으로도 어떤 커밋인지 충분히 식별할 수 있다.
중복되지 않으면 해시값의 앞 4자만으로도 나타낼 수 있다.
-
git show [part_of_hash] 로 특정 커밋을 조회할 수 있다.
$ git show <part_of_hash>
-
git log 명령에 —abbrev-commit 이라는 옵션을 추가하면 짧고 중복되지 않는 해시값을 보여준다.
기본으로 7자를 보여주고 해시값이 중복되는 경우 더 긴 해시값을 보여준다.
보통은 8자에서 10자 내외로도 충분히 유일하게 커밋을 나타낼 수 있다.
-
참고로 리눅스 커널은 45만 개 이상의 커밋, 360만 개 이상의 오브젝트가 있지만, 해시값 11개만 사용해도 충돌이 없다.
-
언젠가 SHA-1 값이 중복될까 걱정하는데, 실제 발생한다면..
이미 있는 SHA-1 값이 git db 에 커밋되면 새로운 개체라고 해도 이미 커밋된 것으로 생각한다.
그래서 해당 SHA-1 값의 커밋을 checkout 하면 항상 처음 저장한 커밋만 checkout 된다.
그러나 해시값이 중복되는 일은 일어나기 어렵다.
SHA-1 값의 크기는 20바이트(160bit)이다.
해시값이 중복될 확률이 50% 되는데 필요한 개체의 수는 2^80 이며, 이 수는 1자 2000해이다.
지구에서 약 6억 5천만 명의 인구가 개발을 하고 각자 매초 리눅스 커널 히스토리 전체(360만 개)와 맞먹는 개체를 쏟아 내고 바로 push 한다고 가정해도, 이런 상황에서 해시값의 충돌 날 확률이 50% 가 되기까지는 약 2년이 걸린다.
* 브랜치로 가리키기
-
브랜치를 사용하는 것이 커밋을 나타내는 가장 쉬운 방법이다.
git show 로 SHA 값을 넣어도 되지만, branch name 을 넣을 수도 있다.
-
브랜치가 가리키는 개체의 SHA-1 이 궁금하다면 rev-parse 라는 plumbing 도구가 해결해준다.
$ git rev-parse <branch_name>
* RefLog 로 가리키기
-
Git 은 자동으로 브랜치와 HEAD 가 지난 몇 달 동안에 가리켰었던 커밋을 모두 기록하는데 이 로그를 Reflog 라고 부른다.
git reflog 를 실행하면 된다.
$ git reflog
-
Git 은 브랜치가 가리키는 것이 달라질 때마다 그 정보를 임시 영역에 저장한다.
그래서 예전에 가리키던 것이 무엇인지 확인해 볼 수 있다.
@{n} 규칙을 사용하면 아래와 같이 HEAD 가 5번 전에 가리켰던 것을 알 수 있다.
ex)
$ git show HEAD@{5}
순서뿐 아니라 시간도 사용할 수 있다.
ex)
$ git show master@{yesterday}
Reflog 에 남아있을 경우에만 조회할 수 있기 때문에 너무 오래된 커밋은 조회할 수 없다.
-
git log -g 명령을 사용하면 git reflog 결과를 git log 명령과 같은 형태로 볼 수 있다.
ex)
$ git log -g master
-
reflog 의 일은 모두 로컬의 일이기 때문에 내 relog 가 동료의 저장소에는 있을 수 없다.
* 계통 관계로 가리키기
-
이름 끝에 ^ 를 붙이면 git 은 해당 커밋의 부모를 찾는다.
HEAD^ 는 HEAD 의 부모를 이야기하므로 이전 커밋을 보여준다.
^ 뒤에 숫자도 사용할 수 있다.
예를 들어 d921970^2 는 d921970 의 두 번째 부모를 의미한다.
-
계통을 표현하는 방법으로 ~라는 것도 있다.
HEAD~ 와 HEAD^ 는 똑같이 첫 번째 부모를 가리킨다.
그러나 그 뒤에 숫자를 사용하면 달라진다.
HEAD~2 는 명령을 실행할 시점의 첫번째 부모의 첫번째 부모 즉 조부모를 가리킨다.
-
HEAD^^^ 는 HEAD~3 와 같다.
둘을 섞어 쓸 수도 있다.
HEAD~3^2
* 범위로 커밋 가리키기
-
범위를 주고 여러 커밋을 한꺼번에 조회할 수도 있다.
범위를 사용하여 조회할 수 있으면 브랜치를 관리할 때 유용하다.
-
범위를 표현하는 문법으로 double dot (..) 을 많이 쓴다.
double dot 은 어떤 커밋들이 한쪽에는 관련됐고 다른 쪽에는 관련되지 않았는지 git 에게 물어보는 것이다.
a branch 커밋들 중에서 아직 master 브랜치에 머지하지 않은 것을 보고싶으면,
master..a 라고 사용한다. 이것은 master 에는 없지만 a 에는 있는 커밋을 의미한다.
반대로 a 에는 없지만, master 에는 있는 것이 궁금하면 a..master 로 하면 된다.
-
토픽 브랜치를 머지하기 전에 무엇이 변경되었는지를 확인해 보고 싶을 때 유용하다.
리모트 저장소에 push 하고 싶을 때도 마찬가지다.
ex)
$ git log origin/master..HEAD
-
double dot 은 두 개 이상의 브랜치에는 사용할 수 없다.
현재 작업 중인 브랜치에는 있지만 다른 여러 브랜치에는 없는 커밋을 보고 싶으면 .. 으로는 확인할 수 없다.
git 은 ^이나 —not 옵션 뒤에 브랜치 이름을 넣으면 그 브랜치에 없는 커밋을 찾아준다.
아래 3개의 명령은 동일하다.
$ git log refA..refB
$ git log ^refA refB
$ git log refB —not refA
이 옵션은 세 개 이상의 Refs 에 사용할 수 있는 장점이 있다.
$ git log refA refB ^refC
$ git log refA refB —not refC
-
triple dot 은 양쪽에 있는 두 Refs 사이에서 공통으로 가지는 것을 제외하고 서로 다른 커밋만 보여준다.
만약 master 와 a 의 공통부분은 빼고 다른 커밋만 보고 싶다면 아래와 같이 한다.
$ git log master…a FEDC # FDEC 는 최근 날짜순
log 명령에 —left-right 옵션을 추가하면 각 커밋이 어느 브랜치에 속하는지도 보여준다.
7.2. 대화형 명령
-
git 은 대화형 스크립트도 제공하여 명령을 좀 더 쉽게 사용할 수 있다.
스크립트를 통해 커밋할 파일을 고르고 수정된 파일 일부분만 커밋할 수도 있다.
스크립트는 수정하는 파일이 매우 많아서 통째로 커밋하기 어려울 때 이슈별로 나눠서 커밋하기에 좋다.
git add 명령에 -i 나 —interactive 옵션을 주고 실행하면 된다.
$ git add -i
$ git add -interactive
* Staging Area 에 파일 추가하고 추가 취소하기
-
operation 을 한 후 프롬프트에 아무것도 입력하지 않고 엔터를 치면 작업을 수행한다.
-
stage 는 “update” 명령을 사용하면 되고,
unstage 는 “revert” 를 하면 된다.
“diff” 는 git diff —cached 와 같다. (—cached 는 push 된 것과 비교한다. 없으면 unstage 와 stage 된것(없으면 push된것)과 비교한다.)
* 파일 일부분만 Staging Area 에 추가하기
-
“patch” 명령을 이용하면 된다.
hunk 단위로 나온다.
? 를 입력하면 선택할 수 있는 명령을 선택해준다.
-
대화형 스크립트로만 파일 일부분을 stage 할 수 있는 것은 아니다.
git add -p 나 git add —patch 로도 같은 일을 할 수 있다.
reset —patch 명령을 사용해서 파일 일부만 stage area 에서 내릴 수도 있다.
또 checkout —patch 를 사용해서 파일 일부를 다시 checkout 받을 수 있다.
stash save —patch 명령으로는 파일 일부만 stash 할 수 있다.
7.3. Stashing 과 Cleaning
-
커밋하지 않고 나중에 작업분을 돌리고 싶다면 git stash 명령을 사용하면 된다.
stash 명령을 사용하면 워킹 디렉터리에서 수정한 파일들만 저장한다.
stash 는 modified 이면서 tracked 상태인 파일과 staging area 에 있는 파일들을 보관해두는 장소다.
* 하던 일을 Stash 하기
-
git stash 나 git stash save 를 실행한다.
$ git stash
$ git stash save
-
git stash list 를 통해 저장한 stash 를 확인할 수 있다.
$ git stash list
-
git stash apply 를 사용하여 stash 를 다시 적용 할 수 있다.
git stash apply stash_name 으로 stash 를 골라 적용할 수 있다.
이름이 없으면 git 은 가장 최근 stash 를 적용한다.
$ git stash apply
$ git stash apply <stash_name>
-
stash 를 적용할 때 충돌이 발생할 수 있다.
-
git 은 stash 를 적용할 때 staged 상태였던 파일을 자동으로 다시 staged 상태로 만들어 주지 않는다.
그래서 git stash apply 명령을 실행할 때 —index 옵션을 주어야 원래 작업하던 상태로 돌아올 수 있다.
$ git stash apply --index
-
git stash drop 명령을 사용하여 stash 를 제거할 수 있다.
git stash pop 이라는 명령은 stash 를 적용하고 나서 바로 스택에서 제거해준다.
$ git stash drop
$ git stash pop
* Stash 를 만드는 새로운 방법
-
stash 를 만들 때 주로 사용하는 옵션은 stash save 명령과 함께 쓰는 --keep-index 이다.
이 옵션을 이용하면 이미 staging area 에 들어있는 파일을 stash 하지 않는다.
$ git stash save --keep-index
-
추적하지 않는 파일과 추적 중인 파일을 같이 stash 하는 일도 빈번하다.
기본적으로 git stash 는 추적 중인 파일만 저장한다.
추적 중이지 않은 파일을 같이 저장하려면 --include-untracked 나 -u 옵션을 붙여준다.
$ git stash save --include-untracked
$ git stash save -u
-
—patch 옵션을 붙이면 git 은 수정된 모든 사항을 저장하지 않는다.
대신 대화형 프롬프트가 뜨며 변경된 데이터 중 저장할 것과 저장하지 않을 것을 지정할 수 있다.
$ git stash save --patch
* Stash 를 적용한 브랜치 만들기
-
git stash branch 명령을 실행하면 stash 할 당시의 커밋을 checkout 한 후 새로운 브랜치를 만들고 여기에 적용한다.
이 모든 것이 성공하면 stash 를 삭제한다.
ex)
$ git stash branch <new_branch_name>
* 워킹 디렉터리 청소하기
-
작업하고 있던 파일을 stash 하지 않고 단순히 그 파일들을 치워버리고 싶을 때는 git clean 명령을 사용한다.
$ git clean
이 명령을 사용할 때는 신중해야 한다.
이 명령을 사용하면 워킹 디렉터리 안의 추적하고 있지 않은 모든 파일이 지워지기 때문이다.
git stash -all 명령을 이용하면 지우는 건 똑같지만, 먼저 모든 파일을 stash 하므로 좀 더 안전하다.
$ git stash -all
-
워킹 디렉터리의 불필요한 파일들을 전부 지우려면 git clean 을 사용하는데, 추적 중이지 않은 모든 정보를 워킹 디렉터리에서 지우고 싶다면 git clean -f -d 명령을 사용한다.
$ git clean -f -d
이 명령은 하위 디렉터리까지 모두 지워버린다.
-f 옵션은 강제의 의미이다.
이 명령을 실행했을 때 어떤 일이 일어날지 미리 보고 싶다면 -n 옵션을 사용하는데, -n 은 가상으로 실행해보고 어떤 파일들이 지워질지 알려달라는 뜻이다.
git clean 이 무슨 짓을 할지 확신이 안 들 때는 항상 -n 옵션을 붙여서 먼저 실행해보자.
$ git clean -f -d -n
-
git clean 은 추적 중이지 않은 파일만 지우는 게 기본 동작이다.
.gitignore 에 명시했거나 해서 무시되는 파일은 지우지 않는다.
무시된 파일까지 함께 지우려면 -x 옵션이 필요하다.
$ git clean -f -d -x
-
clean 명령을 대화형으로 실행하려면 -i 옵션을 붙이면 된다.
7.4. 내 작업에 서명하기
-
Git 은 암호학적으로 안전하다. 하지만 그냥 되는 건 아니다.
저장소에 아무나 접근하지 못하게 하고 진짜로 확인된 사람에게서만 커밋을 받으려면 GPG 를 이용한다.
* GPG 소개
-
뭔가를 서명하려면 GPG 설정도 하고 개인 키도 설치해야 한다.
$ gpg —list-keys
가진 키가 없으면 키를 새로 만들어야 한다.
$ gpg —gen-key
서명에 사용할 수 있는 개인 키가 이미 있다면 user.signingkey 로 설정해서 사용할 수 있다.
$ git config —global user.signingkey 0A46826A
설정이 완료되면 이제 git 태그와 커밋에 서명할 때 등록한 키를 사용한다.
* 태그 서명하기
-
태그에 서명할 때 -a 대신 -s 를 사용하면 된다. (-a 는 unsigned annotated tag)
ex)
$ git tag -s v1.5 -m ‘my signed 1.5 tag’
git show 로 확인해보면 gpg 서명이 붙어 있는 걸 확인할 수 있다.
* 태그 확인하기
-
git tag -v tag_name 명령을 이용해 태그에 서명한 사람이 정말 그 사람이 맞는지 확인한다.
이 명령은 서명을 확인하기 위해 gpg 를 사용한다.
확인 작업을 하려면 서명한 사람의 gpg 공개키를 키 관리 시스템에 등록해 두어야 한다.
서명한 사람의 공개키가 없으면 error 메시지가 나온다.
* 커밋에 서명하기
-
최신 버전(v.1.7.9 이상)의 git 은 커밋에도 서명할 수 있다.
커밋에 서명하고 싶으면 git commit 명령에 -S 옵션만 붙여주면 된다.
ex)
$ git commit -a -S -m ‘signed commit’
서명을 확인하려면 git log 에 —show-signature 옵션을 붙여주자.
-
git log 로 출력한 로그에서 커밋에 대한 서명 정보를 알려면 %G? 포맷을 이용하면 된다.
ex)
$ git log —pretty=“format:%h %G? %aN %s”
서명을 한 경우 G, 그렇지 않은 경우 N 이 표시된다.
-
git 1.8.3 버전 이후로는 git merge 와 git pull 에서 gpg 서명 정보를 이용해 merge 를 허용하지 않을 수 있다.
merge 할 때 —verify-signatures 옵션을 붙이면 머지할 커밋 중 서명하지 않았거나 신뢰할 수 없는 사람이 서명한 커밋이 있으면 머지되지 않는다.
-
git merge 명령에도 -S 옵션을 붙일 수 있는데, 이 옵션을 붙이면 머지 커밋을 서명하겠다는 의미이다.
ex)
$ git merge —verify-signatures -S signed-branch
* 모두가 서명하게 하려면
-
태그와 커밋에 서명하는 것은 멋지지만 실제로 서명 기능을 사용하려면 팀의 모든 사람이 서명 기능을 이해하고 사용해야만 한다.
반드시 작업에 적용하기 전에 gpg 서명 기능을 이해하고 이 기능이 가지는 장점을 완전히 파악하고 있어야만 한다.
7.5. 검색
* Git Grep
-
git 의 grep 명령을 이용하면 커밋 트리의 내용이나 워킹 디렉터리의 내용을 문자열이나 정규식을 이용해 쉽게 찾을 수 있다.
기본적으로 대상을 지정하지 않으면 워킹 디렉터리의 파일에서 찾는다.
명령을 실행할 때 -n 옵션을 추가하면 찾을 문자열이 위치한 라인 번호도 같이 출력한다.
ex)
$ git grep -n Random
-
결과 대신 어떤 파일에서 몇 개나 찾았는지만 알고 싶다면 --count 옵션을 이용한다.
$ git grep --count Random
-
매칭되는 라인이 있는 함수나 메서드를 등의 정보도 함께 보고 싶다면 -p 옵션을 준다.
ex)
$ git grep -n -p Random *.kt # .kt 파일에서 Random 이 있는 함수와 클래스명 등이 보여진다.
-
—and 옵션을 이용해서 여러 단어가 한 라인에 동시에 나타나는 줄 찾기 같은 복잡한 조합으로 검색할 수 있다.
—break 와 —heading 옵션을 붙여 더 읽기 쉬운 형태로 잘라서 출력할 수도 있다.
ex)
$ git grep —break —heading -n -e ‘Random’ —and \(-e true -e false\) branch_name
-
git grep 명령은 grep 이나 ack 같은 일반적인 검색 도구보다 몇 가지 좋은 점이 있는데,
우선 매우 빠르다.
또한 워킹 디렉터리만이 아니라 git 히스토리 내의 어떠한 정보라도 찾아낼 수 있다.
* Git 로그 검색
-
git log 명령을 이용하면 diff 내용도 검색하여 어떤 커밋에서 찾고자 하는 내용을 추가했는지 찾을 수 있다.
BUF_MAX 라는 상수가 가장 처음 나타난 때를 찾는 문제라면 -S 옵션을 이용해 해당 문자열이 추가된 커밋과 없어진 커밋만 검색할 수 있다.
ex)
$ git log -SZBUFMAX —oneline #-SZ 와 검색어 사이에 띄워쓰기 없음
더 세세한 조건을 걸어 찾고 싶다면 로그를 검색할 때 -G 옵션으로 정규식을 써서 검색하면 된다.
-
라인 히스토리 검색이라는 미친듯 좋은 로그 검색 도구가 있다.
git log 에 -L 옵션을 붙이면 어떤 함수나 한 라인의 히스토리를 볼 수 있다.
예를 들어 zlib.c 파일에 있는 git_deflate_bound 함수의 모든 변경사항이 보고 싶다면..
$ git log -L :git_deflate_bound:zlib.c
git 이 함수의 처음과 끝을 인식하지 못할 때는 정규식으로 인식하게 할 수도 있다.
$ git log -L ‘/unsigned long git_deflate_bound/‘, /^}/:zlib.c
7.6. 히스토리 단장하기
* 마지막 커밋을 수정하기
-
git commit —amend 를 사용하면 된다.
$ git commit --amend
* 커밋 메시지를 여러 개 수정하기
-
예전 커밋을 수정하려면 다른 도구가 필요하다.
rebase 명령을 이용하여 수정할 수 있다.
현재 작업하는 브랜치에서 각 커밋을 하나하나 수정하는 것이 아니라 어느 시점부터 HEAD 까지의 커밋을 한 번에 rebase 한다.
대화형 rebase 도구를 사용하면 커밋을 처리할 때마다 잠시 멈춘다.
그러면 각 커밋의 메시지를 수정하거나 파일을 추가하고 변경하는 등의 일을 진행할 수 있다.
-i 옵션을 추가하면 대화형 모드로 rebase 할 수 있다.
예를 들어 마지막 커밋 메시지 세 개를 모두 수정하거나 그 중 몇개를 수정한다면..
$ git rebase -i HEAD~3
이 명령은 rebase 이기 때문에 메시지의 수정 여부와 관계없이 HEAD~3..HEAD 범위에 있는 모든 커밋을 수정한다.
-
중앙 서버에 push 한 커밋은 절대 고치지 말아야 한다.
push 한 커밋을 rebase 하면 결국 같은 내용을 두 번 push 하는 것이기 때문에 다른 개발자들이 혼란스러워 한다.
-
대화형 rebase 는 스크립트에 적혀 있는 순서대로 HEAD~3 부터 적용하기 시작하고 위에서 아래로 각각의 커밋을 순서대로 수정한다.
순서대로 적용하는 것이기 때문에 제일 위에 있는 것이 최신이 아니라 가장 오래된 것이다.
-
특정 커밋에서 실행을 멈추게 하려면 pick 이라는 단어를 edit 로 수정하면 그 커밋에서 멈춘다.
rebase 가 완료되면 git commit —amend 를 통해 커밋메시지를 수정하고, git rebase —continue 로 마무리한다.
$ git commit --amend
$ git rebase --continue
* 커밋 순서 바꾸기
* 커밋 합치기
-
squash 가 그 기능을 한다.
합치면서 커밋 메시지도 merge 하는데, 수정 가능하다.
* 커밋 분리하기
-
edit 를 사용하면 된다.
명령 프롬프트로 넘어간 다음 그 커밋을 해제하고 그 내용을 다시 두 개로 나눠서 커밋하면 된다.
ex)
$ git reset HEAD^
# git add
# git commit
# git add
# git commit
$ git rebase —continue
-
rebase 하면 목록에 있는 모든 커밋의 SHA-1 값이 변경된다.
절대로 이미 서버에 push 한 커밋을 수정하면 안 된다.
* filter-branch 는 포크레인
-
수정해야 하는 커밋이 너무 많아서 rebase 스크립트로 수정하기 어려울 것 같으면 다른 방법을 사용하는 것이 좋다.
모든 커밋의 이메일 주소를 변경하거나 어떤 파일을 삭제하는 경우 filter-branch 라는 명령으로 수정할 수 있는데.. rebase 가 삽이라면 이 명령은 포크레인이라고 할 수 있다.
-
filter-branch 도 역시 수정하려는 커밋이 이미 공개돼서 다른 사람과 함께 공유하는 중이라면 사용하지 말아야 한다.
-
filter-branch 는 히스토리 전체에서 필요한 것만 골라내는 데 사용하는 도구다.
filter-branch 의 —tree-filter 라는 옵션을 사용하면 히스토리에서 특정 파일을 아예 제거할 수 있다.
ex) 히스토리에서 passwords.txt 파일을 제거할 수 있다.
$ git filter-branch —tree-filter ‘rm -f passwords.txt’ HEAD
—tree-filter 옵션은 프로젝트를 checkout 한 후에 각 커밋에 명시한 명령을 실행시키고 그 결과를 다시 커밋한다.
-
실수로 편집기의 백업파일을 커밋했으면 git filter-branch --tree-filter ‘rm -f *~’ HEAD 를 하면 된다.
이 명령은 모든 파일과 커밋을 정리하고 브랜치 포인터를 다시 복원해준다.
이런 작업을 테스팅 브랜치에서 실험하고 나서 master 브랜치에 적용하는 게 좋다.
-
—filter-branch 명령에 —all 옵션을 추가하면 모든 브랜치에 적용할 수 있다.
-
다른 VCS 에서 코드를 임포트하면 그 VCS 만을 위한 디렉터리가 있을 수 있다.
SVN 에서 코드를 임포트하면 trunk, tags, branch 디렉터리가 포함된다.
모든 커밋에 대해 trunk 디렉터리를 프로젝트 루트 디렉터리로 만들 때도 filter-branch 명령이 유용하다.
$ git filter-branch —subdirectory-filter trunk HEAD
이제 trunk 디렉터리가 루트 디렉터리이고, git 은 입력한 디렉터리와 관련이 없는 커밋을 자동으로 삭제한다.
-
프로젝트를 오픈 소스로 공개할 때 아마도 회사 이메일 주소로 커밋된 것을 개인 이메일 주소로 변경해야 한다.
아니면 아예 git config 로 이름과 이메일 주소를 설정하는 것을 잊었을 수도 있다.
자신의 이메일 주소만 변경하도록 조심해야 한다.
filter-branch 명령의 —commit-filter 옵션을 사용하여 해당 커밋만 골라서 이메일 주소를 수정할 수 있다.
$ git filter-branch —commit-filter ‘
if [“$GIT_AUTHOR_EMAIL” = “abc@abc.com”];
then
GIT_AUTHOR_NAME=“myName”;
GIT_AUTHOR_EMAIL=“abcd@abc.com”;
git commit-tree “$@“;
else
git commit-tree “$@“;
fi’ HEAD
조건에 만족하는 커밋의 SHA-1 값만 바뀌는 것이 아니라 모든 커밋의 SHA-1 값이 바뀐다.
'프로그래밍 놀이터 > Tips' 카테고리의 다른 글
[도서 정리] 8. Git 맞춤 - ProGit (0) | 2020.01.13 |
---|---|
[도서 정리] 7. Git 도구 #2 - ProGit (0) | 2020.01.12 |
[도서 정리] 6. GitHub - ProGit (0) | 2020.01.10 |
[도서 정리] 5. 분산 환경에서의 Git - ProGit (0) | 2020.01.09 |
[도서 정리] 4. Git 서버 - ProGit (0) | 2020.01.08 |
댓글