Git 다음 단계로

Head 분리하기, 상대참조(^,~), revert, reset을 배워보자.

Bora Lee
11 min readDec 11, 2018

이 스토리는 learngitbranching사이트의 내용을 글로 정리하기 위해 만들었습니다. 초급 수준의 Git 활용을 벗어나 중/고급 수준의 Git 활용을 연습하고 싶다면 위 사이트의 interactive모드로 Git 명령어를 실습해 보는것을 추천드립니다.

위 사이트의 레벨은 다음과 같습니다

  • 메인
    * Git 기본
    1. Git 커밋 소개
    2. Git에서 브랜치 쓰기
    3. Git에서 브랜치 합치기(Merge)
    4. 리베이스(rebase)의 기본
    * 다음 단계로
    1. HEAD 분리하기
    2. 상대참조(^) (Relative Refs)
    3. 상대참조 #2 (~)
    4. Git에서 작업 되돌리기
    * 코드 이리저리 옮기기
    1. Cherry-pick 소개
    2. 인터랙티브 리베이스 소개
    * 종합선물세트
    1. 딱 한개의 커밋만 가져오기
    2. 커밋들 갖고 놀기
    3. 커밋 갖고 놀기 #2
    4. Git 태그
    5. Git describe(묘사)
    *고급 문제
    1. 9천번이 넘는 리베이스
    2. 다수의 부모
    3. 브랜치 스파게티
  • 원격
    *Push&Pull — Git 원격 저장소!
    1. Clone소개
    2. 원격 브랜치(remote branch)
    3. Git Fetch
    4. Git pull
    5. 가짜 팀워크
    6. Git push
    7. 엇갈린 히스토리
    *Origin 그 너머로 — 고급 Git 원격 저장소
    1. Push Master!
    2. 원격 작업과 merge하기
    3. 원격 저장소 추적하기
    4. Git push의 인자들
    5. Git push 인자 — 확장판!
    6. Fetch의 인자들
    7. Source가 없다
    8. pull 인자들

이번 글에선 [메인 > 다음 단계로]를 정리하도록 하겠습니다.

HEAD 분리하기

Git에서 여기저기로 옮겨다니기

Git의 고급기능들에 대해 더 알아보기 전에, 여러분의 프로젝트를 표현하는 커밋 트리(commit tree)에서 이동 할 수 있는 여러가지 방법들을 아는것이 중요합니다.

여기저기 이동하는 것에 익숙해지면, 여러분이 다른 git 명령어들을 사용하는 능력도 아주 좋아질 것입니다!

HEAD

먼저”HEAD”에 대해 이야기해 봅시다. HEAD는 현재 체크아웃된 커밋을 가리킵니다. — 다시 말하자면 현재 작업중인 커밋입니다.

HEAD는 항상 작업트리의 가장 최근 커밋을 가리킵니다. 작업트리에 변화를 주는 git 명령어들은 대부분 HEAD를 변경하는것으로 시작합니다.

일반적으로 HEAD는 브랜치의 이름을 가리키고있습니다(bugFix와 같이). 커밋을 하게 되면, bugFix의 상태가 바뀌고 이 변경은 HEAD를 통해서 확인이 가능합니다.

직접 확인해 봅시다. 여기서 우리는 보이지 않던 HEAD를 커밋전, 후에 드러낼 것입니다.

$git checkout C1
$git checkout master
$git commit
$git checkout C2

보세요! HEAD가 master브랜치 아래에 숨어 있던 거군요.

HEAD 분리하기

HEAD를 분리한다는 것은 HEAD를 브랜치 대신 커밋에 붙이는 것을 의미합니다. 명령을 사용하기 전의 모습은 다음과 같습니다:

HEAD -> master -> C1

$git checkout C1

이제는 이렇게 되는군요

HEAD -> C1

HEAD 분리하기 연습

다음 레벨로 넘어가기 위해서는, HEAD를 bugfix에서 분리하고 그 커밋에 붙이세요.

각 커밋은 그것의 해시값으로 특정지을수 있습니다. 각 커밋의 해시값은 각 커밋을 나타내는 원안에 나타나있습니다.

$git checkout bugFix

$git checkout C4

상대참조(^) (Relative Refs)

상대 참조

Git에서 여기저기 이동할 때 커밋의 해시를 사용하는 방법은 조금 귀찮습니다. 실제로 Git을 사용할 때는 터미널화면 옆에 예쁘장하게 커밋트리가 보이진 않으니까요. 매번 해시를 확인하려고 git log 명령어를 치고 있을 겁니다.

나아가서, 실제 Git에서는 해시들이 훨씬 더 깁니다. 예를 들어 이전 레벨에 소개했던 커밋의 해시는 fed2da64c0efc5293610bdd892f82a58e8cbc5d8입니다. 쓰기 쉬워 보이진 않네요....

다행히도, Git은 똑똑합니다. 해시가 커밋의 고유한 값임을 보여줄 수 있을 만큼만 명시해주면 됩니다. 위의 긴 문자열 대신 fed2만 입력해도 되는 겁니다.

말했듯이, 커밋들을 해시로 구분하고 사용하는것이 아주 편하다고 볼 수는 없습니다. Git의 상대 참조(Relative Ref)가 여기서 등장합니다. 굉장한 기능입니다.

상대 참조로 우리가 기억할 만한 지점(브랜치 bugFix라던가 HEAD라던가)에서 출발해서 이동하여 다른 지점에 도달해 작업을 할 수 있습니다.

상대 커밋은 강력한 기능인데, 여기서 두가지 간단한 방법을 소개하겠습니다.

  • 한번에 한 커밋 위로 움직이는 ^
  • 한번에 여러 커밋 위로 올라가는 ~<num>

먼저 캐럿 (^) 연산자 부터 알아보겠습니다. 참조 이름에 하나씩 추가할 때마다, 명시한 커밋의 부모를 찾게 됩니다.

master^는 "master의 부모"와 같은 의미 입니다.

master^^ 는 "master의 조부모(부모의 부모)"를 의미합니다

master 위에 있는 부모를 체크아웃 해 봅시다.

$git checkout master^

Boom! 됬습니다. 커밋의 해시를 입력하는 것보다 훨씬 쉬운 방법입니다.

또한 참조인 HEAD도 상대참조를 위해 사용할 수 있습니다. 커밋트리 위쪽으로 움직이기위해 여러번 사용 해 봅시다.

$git checkout C3
$git checkout HEAD^
$git checkout HEAD^
$git checkout HEAD^

쉽군요! 이제 우린 HEAD^를 통해 시간을 거슬러 올라갈 수 있습니다.

상대 참조 연습

이 레벨을 완료하기 위해서는, bugFix의 부모 커밋을 체크아웃 하십시오. 이렇게 하면 HEAD가 분리 될 것입니다.

해시를 이용해서도 할 수 있지만, 상대 참조를 활용하는 것을 연습해 보세요!

$git checkout bugFix^

상대참조 #2 (~)

“~” 연산자

커밋트리에서 위로 여러 단계를 올라가고 싶을 수 있습니다. ^를 계속 입력해서 올라가는것 말고 좋은 방법이 있습니다. Git 에는 틸드 (~) 연산자가 있습니다.

(~) 틸드 연산자는 (선택적) 올라가고 싶은 부모의 갯수가 뒤에 숫자가 옵니다. 직접 확인해 보죠.

돌아가고 싶은 커밋의 갯수를 ~뒤의 숫자로 명시해 줍시다.

$git checkout HEAD~4

Boom! 아주 간결합니다. — 상대 참조는 대단해요.

브랜치 강제로 옮기기

이제 여러분은 상대 참조의 전문가 입니다. 이제 이걸로 무언가를 해봅시다.

제가 상대 참조를 사용하는 가장 일반적인 방법은 브랜치를 옮길 때 입니다. -f 옵션을 이용해서 브랜치를 특정 커밋에 직접적으로 재지정 할 수 있습니다. 이런 식으로 말이죠:

git branch -f master HEAD~3

(강제로) master 브랜치를 HEAD에서 세번 뒤로 옮겼습니다. (three parents behind HEAD).

방금의 커맨드를 직접 확인해 봅시다.

$git branch -f master HEAD~3

됬네요! 우리는 상대 참조를 통해 C1을 간결한 방법으로 참조할 수 있었고 브랜치 강제(-f)를 통해 브랜치를 저 위치로 빠르게 옮길 수 있었습니다.

상대참조 #2 (~) 연습

자 이제 상대 참조와 브랜치 강제의 조합을 봤으니 다음 레벨을 해결해 봅시다.

이 레벨을 통과하기 위해서, HEADmasterbugFix를 제시되는 골지점으로 옮겨 주십시오.

$git branch -f master C6

$git checkout HEAD~1

$git branch -f bugFix HEAD~1

Git에서 작업 되돌리기

Git에는 작업한 것을 되돌리는 여러가지 방법이 있습니다. 변경내역을 되돌리는 것도 커밋과 마찬가지로 낮은 수준의 일(개별 파일이나 묶음을 스테이징 하는 것)과 높은 수준의 일(실제 변경이 복구되는 방법)이 있는데요, 여기서는 후자에 집중해 알려드릴게요.

Git에서 변경한 내용을 되돌리는 방법은 크게 두가지가 있습니다 — 하나는 git reset을 쓰는거고, 다른 하나는 git revert를 사용하는 것입니다. 다음 화면에서 하나씩 알아보겠습니다.

Git 리셋(reset)

git reset은 브랜치로 하여금 예전의 커밋을 가리키도록 이동시키는 방식으로 변경 내용을 되돌립니다. 이런 관점에서 "히스토리를 고쳐쓴다"라고 말할 수 있습니다. 즉, git reset은 마치 애초에 커밋하지 않은 것처럼 예전 커밋으로 브랜치를 옮기는 것입니다.

어떤 그림인지 한번 보죠:

$git reset HEAD~1

그림에서처럼 master 브랜치가 가리키던 커밋을 C1로 다시 옮겼습니다; 이러면 로컬 저장소에는 마치 C2커밋이 아예 없었던 것과 마찬가지 상태가 됩니다.

Git 리버트(revert)

각자의 컴퓨터에서 작업하는 로컬 브랜치의 경우 리셋(reset)을 잘 쓸 수 있습니다만, “히스토리를 고쳐쓴다”는 점 때문에 다른 사람이 작업하는 리모트 브랜치에는 쓸 수 없습니다.

변경분을 되돌리고, 이 되돌린 내용을 다른 사람들과 공유하기 위해서는, git revert를 써야합니다. 예제로 살펴볼게요.

$git revert HEAD

어색하게도, 우리가 되돌리려고한 커밋의 아래에 새로운 커밋이 생겼습니다. C2라는 새로운 커밋에 변경내용이 기록되는데요, 이 변경내역이 정확히 C2 커밋 내용의 반대되는 내용입니다.

리버트를 하면 다른 사람들에게도 변경 내역을 밀어(push) 보낼 수 있습니다.

Git에서 작업 되돌리기 연습

이 레벨을 통과하려면, local 브랜치와 pushed 브랜치에 있는 최근 두 번의 커밋을 되돌려 보세요.

pushed는 리모트 브랜치이고, local은 로컬 브랜치임을 신경쓰셔서 작업하세요 -- 어떤 방법을 선택하실지 떠오르시죠?

$git reset HEAD~1

$git checkout pushed

$git revert HEAD

--

--