git 기본

Jeongyun Heo·2020년 12월 29일
0

git

목록 보기
1/4
post-thumbnail

Learn Git Branching
https://learngitbranching.js.org/?locale=ko

Git 명령어
commit
branch
checkout
cherry-pick
reset
revert
rebase
merge

export tree 와 import tree로 여러분의 친구들에게 트리를 공유해주세요

훌륭한 학습 자료가 있으신가요? build level로 레벨을 만들어 보시거나, 친구의 레벨을 import level로 가져와서 실험해보세요

메인

git 기본

git의 주요 명령어를 깔끔하게 알려드립니다

Git 커밋

커밋은 Git 저장소에 여러분의 디렉토리에 있는 모든 파일에 대한 스냅샷을 기록하는 것입니다. 디렉토리 전체를 복사하여 붙여넣는것과 유사하지만, 훨씬 유용한 방법입니다!

Git은 가능한 한 커밋을 가볍게 유지하고자 하기때문에, 커밋할 때마다 디렉토리 전체를 복사하진 않습니다. 각 커밋은 저장소의 이전 버전과 다음 버전의 변경내역("delta"라고도 함)을 저장합니다. 그래서 대부분의 커밋이 그 커밋 위의 부모 커밋을 가리킵니다. -- 다음 화면에서 곧 살펴보게 될 것입니다.

저장소를 복제(clone)하려면 모든 변경분(delta)를 풀어내야 하는데, 이 때문에 명령행 결과로 아래 문구를 볼 수 있습니다.

resolving deltas

알아야 할 것이 꽤 많습니다만, 일단은 커밋을 프로젝트의 스냅샷들로 생각하면 충분합니다. 커밋은 매우 가볍고 커밋 사이의 전환도 매우 빠르다는 것을 기억해주세요!

git commit

방금 만든 커밋은 부모는 C1이고, 어떤 커밋을 기반으로 변경된 것인지를 가리킵니다.

Git 브랜치

깃의 브랜치도 놀랍도록 가볍습니다. 브랜치는 특정 커밋에 대한 참조(reference)에 지나지 않습니다. 이런 사실 때문에 수많은 Git 애찬론자들이 자주 이렇게 말하곤 합니다:

브랜치를 서둘러서, 그리고 자주 만드세요

브랜치를 많이 만들어도 메모리나 디스크 공간에 부담이 되지 않기 때문에, 여러분의 작업을 커다란 브랜치로 만들기 보다, 작은 단위로 잘게 나누는 것이 좋습니다.

브랜치와 커밋을 같이 쓸 때, 어떻게 두 기능이 조화를 이루는지 알아보겠습니다. 하지만 우선은, 단순히 브랜치를 "하나의 커밋과 그 부모 커밋들을 포함하는 작업 내역"이라고 기억하시면 됩니다.

브랜치가 어떤 것인지 연습해보죠.

newImage라는 브랜치를 살펴보겠습니다.

git branch newImage 👇

위 이미지에 브랜치의 모든 것이 담겨 있습니다! 브랜치 newImage가 커밋 C1를 가리킵니다.

이 새로운 브랜치에 약간의 작업을 더해봅시다. 아래 버튼을 눌러주세요

git commit 👇

앗! main 브랜치가 움직이고, newImage 브랜치는 이동하지 않았네요! 그건 우리가 새 브랜치 위에 있지 않았었기 때문입니다. 별표(*)가 main에 있었던 것이죠.

아래의 명령으로 새 브랜치로 이동해 봅시다.

git checkout [브랜치명]

이렇게 하면 변경분을 커밋하기 전에 새 브랜치로 이동하게 됩니다.

git checkout newImage; git commit 👇

이거죠! 이제 우리의 변경이 새 브랜치에 기록되었습니다!

git checkout -b newImage 👈 newImage라는 브랜치를 생성하고 그 브랜치에 체크아웃까지 한다.

브랜치와 합치기(Merge)

좋습니다! 지금까지 커밋하고 브랜치를 만드는 방법을 알아봤습니다. 이제 두 별도의 브랜치를 합치는 몇가지 방법을 알아볼 차례입니다. 이제부터 배우는 방법으로 브랜치를 따고, 새 기능을 개발 한 다음 합칠 수 있게 될 것입니다.

처음으로 살펴볼 방법은 git merge입니다. Git의 합치기(merge)는 두 개의 부모(parent)를 가리키는 특별한 커밋을 만들어 냅니다. 두개의 부모가 있는 커밋이라는 것은 "한 부모의 모든 작업내역과 나머지 부모의 모든 작업, 그리고 그 두 부모의 모든 부모들의 작업내역을 포함한다"라는 의미가 있습니다.

그림으로 보는게 이해하기 쉬워요. 다음 화면을 봅시다.

여기에 브랜치가 두 개 있습니다. 각 브랜치에 독립된 커밋이 하나씩 있구요. 그 말은 이 저장소에 지금까지 작업한 내역이 나뉘어 담겨 있다는 얘기입니다. 두 브랜치를 합쳐서(merge) 이 문제를 해결해 볼까요?

bugFix 브랜치를 main 브랜치에 합쳐(merge) 보겠습니다.

git merge bugFix 👇

보셨어요? 우선, main이 두 부모가 있는 커밋(C4)을 가리키고 있습니다.

또, 커밋들의 색이 바뀐 것을 눈치 채셨나요? 이해를 돕기위해 색상으로 구분해 표현했습니다. 각 브랜치는 그 브랜치만의 색상으로 그렸습니다. 브랜치가 합쳐지는 커밋의 경우에는, 그 브랜치들의 색을 조합한 색상으로 표시 했습니다.

그런식으로 여기에 bugFix 브랜치 쪽을 제외한 나머지 커밋만 main 브랜치의 색으로 칠해져 있습니다. 이걸 고쳐보죠...

이제 main 브랜치에 bugFix를 합쳐(merge) 봅시다:

git checkout bugFix; git merge main 👇

bugFix가 main의 부모쪽에 있었기 때문에, git이 별다른 일을 할 필요가 없었습니다; 간단히 bugFix를 main가 붙어 있는 커밋으로 이동시켰을 뿐입니다.

짜잔! 이제 모든 커밋의 색이 같아졌고, 이는 두 브랜치가 모두 저장소의 모든 작업 내역을 포함하고 있다는 뜻입니다.

연습문제

아래 작업을 해서 이 레벨을 통과하세요:

  • bugFix라는 새 브랜치를 만듭니다
  • git checkout bugFix를 입력해 bugFix 브랜치로 이동(checkout)합니다.
  • 커밋 한 번 하세요
  • git checkout 명령어를 이용해 main브랜치로 돌아갑니다
  • 커밋 또 하세요
  • git merge 명령어로 bugFix브랜치를 main에 합쳐 넣습니다.

git checkout -b bugFix 👈 bugFix 라는 브랜치 생성하고 이동
git commit
git checkout main 👈 main 브랜치로 이동
git commit
git merge bugFix 👈 bugFix 내용 가져오기

Git 리베이스(Rebase)

브랜치끼리의 작업을 접목하는 두번째 방법은 리베이스(rebase)입니다. 리베이스는 기본적으로 커밋들을 모아서 복사한 뒤, 다른 곳에 떨궈 놓는 것입니다.

조금 어렵게 느껴질 수 있지만, 리베이스를 하면 커밋들의 흐름을 보기 좋게 한 줄로 만들 수 있다는 장점이 있습니다. 리베이스를 쓰면 저장소의 커밋 로그와 이력이 한결 깨끗해집니다.

어떻게 동작하는지 살펴볼까요...

여기 또 브랜치 두 개가 있습니다; bugFix 브랜치가 현재 선택되어 있다는 점을 눈여겨 보세요 (*별표 표시)

bugFix 브랜치에서의 작업을 main 브랜치 위로 직접 옮겨 놓으려고 합니다. 그렇게 하면, 실제로는 두 기능을 따로따로 개발했지만, 마치 순서대로 개발한 것처럼 보이게 됩니다.

git rebase 명령어로 함께 해보죠.

git rebase main 👇 현재 브랜치를 main 브랜치 위로 옮긴다

오! 이제 bugFix 브랜치의 작업 내용이 main의 바로 위에 깔끔한 한 줄의 커밋으로 보이게 됐습니다.

C3 커밋은 어딘가에 아직 남아있고(그림에서 흐려짐), C3'는 main 위에 올려 놓은 복사본입니다.

main이 아직 그대로라는 문제가 남아있는데요, 바로 해결해보죠...

우리는 지금 main 브랜치를 선택한 상태입니다. bugFix 브랜치쪽으로 리베이스 해보겠습니다...

git rebase bugFix 👇 현재 브랜치를 bugFix 브랜치로 옮긴다

보세요! main이 bugFix의 부모쪽에 있었기 때문에, 단순히 그 브랜치를 더 앞쪽의 커밋을 가리키게 이동하는 것이 전부입니다.

git checkout -b bugFix 👉 bugFix라는 새 브랜치를 만들고 이동
git commit
git checkout main
git commit
git checkout bugFix
git rebase main

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

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

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

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

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

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

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

git checkout C1; git checkout main; git commit; git checkout C2

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

HEAD 분리하기

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

HEAD -> main -> C1 👈 HEAD는 main을 가리키고 있고 main은 C1을 가리키고 있다.

git checkout C1 👇

이제는 이렇게 되는군요

HEAD -> C1 👈 HEAD는 C1을 가리키고 있다.

상대 참조

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

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

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

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

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

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

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

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

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

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

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

git checkout main^

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

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

git checkout C3; git checkout HEAD^; git checkout HEAD^; git checkout HEAD^

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

main에서 bugFix 부모로 한 번에 이동하기
👉 git checkout bugFix^

"~" 연산자

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

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

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

git checkout HEAD~4

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

브랜치 강제로 옮기기

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

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

git branch -f main HEAD~3

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

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

git branch -f main HEAD~3

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

git branch -f main C6
git checkout HEAD~1
fit branch -f bugFix HEAD~1

Git에서 작업 되돌리기

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

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

Git 리셋(reset)

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

어떤 그림인지 한번 보죠:

git reset HEAD~1

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

Git 리버트(revert)

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

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

git revert HEAD

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

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

작업을 여기저기로 옮기기

지금까지 우리는 git의 기초를 배웠습니다. -- 커밋을하고, 브랜치를 만들고, 소스 트리 여기저기를 돌아다녔습니다. 이런 개념들을 아는 것만으로도 git repository의 힘을 90%이상 사용하고 개발자들이 필요로하는 작업의 대부분을 할 수 있습니다.

그 나머지 10% 기능이, 복잡한 작업(또는 작업중 막혔을때) 중에 꽤 유용할 수 있습니다. 이제 배워 볼 다음 개념은 "작업을 여기저로 옮기기" 다시 말해, 개발자들의 언어로 "이 일은 여기에, 저 일은 저기에 두고 싶어" 정확하고 우아하고 유연하게.

다소 과해 보일 수 있지만, 간단한 개념입니다.

Git 체리-픽 (Cherry-pick)

이 시리즈의 첫 명령어는 git cherry-pick 입니다. 다음 과 같은 형태로 사용합니다:

git cherry-pick <Commit1> <Commit2> <...>
현재 위치(HEAD) 아래에 있는 일련의 커밋들에대한 복사본을 만들겠다는 것을 간단히 줄인 말입니다. 개인적으로 저는 cherry-pick을 아주 좋아합니다 왜냐하면 조금의 마법이 첨가되있고 이해하기 쉽기 때문입니다.

데모를 확인해봅시다

여기 repository가 있습니다. main으로 복사하고 싶은 작업이 있는 브랜치 side가 있습니다. 이것은 rebase를 통해서 할 수 있습니다(이미 배운), 하지만 체리-픽이 이 작업을 어떻게 수행하는지 확인해 봅시다.

git cherry-pick C2 C4

됐습니다! 우리는 C2와 C4 커밋을 원했고 git이 우리가 원하는 곳 바로 밑에 톡 떨어뜨려 줬습니다. 아주 간단하죠!

Git 인터렉티브 리베이스(Interactive Rebase)

Git 체리-픽은 여러분이 원하는 커밋이 무엇인지 알때(각각의 해시값도) 아주 유용합니다 -- 체리-픽이 제공하는 간단함은 아주 매력적입니다.

하지만 원하는 커밋을 모르는 상황에는 어쩌죠? 고맙게도 git은 이런상황에 대한 대안이 있습니다. 우리는 이럴 때 인터렉티브 리베이스를 사용하면됩니다 -- 리베이스할 일련의 커밋들을 검토할 수 있는 가장 좋은 방법입니다.

자세히 알아보죠...

인터렉티브 리베이스가 의미하는 뜻은 rebase 명령어를 사용할 때 -i 옵션을 같이 사용한다는 것입니다.

이 옵션을 추가하면, git은 리베이스의 목적지가 되는 곳 아래에 복사될 커밋들을 보여주는 UI를 띄울것 입니다. 각 커밋을 구분할 수 있는 각각의 해시들과 메시지도 보여줍니다.

"실제" git 에서는 UI창을 띄우는것 대신에 vim과 같은 텍스트 편집기에서 파일을 엽니다. 저희는 배우는 것이 목적이기에 같은 역할을 하는 작은 대화창을 만들어서 대신했습니다.

인터렉티브 리베이스 대화창이 열리면, 3가지를 할 수 있습니다:

  • 적용할 커밋들의 순서를 UI를 통해 바꿀수 있습니다(여기서는 마우스 드래그앤 드롭으로 가능합니다)

  • 원하지 않는 커밋들을 뺄 수 있습니다. 이것은 pick을 이용해 지정할 수 있습니다(여기서는 pick토글 버튼을 끄는것으로 가능합니다)

  • 마지막으로, 커밋을 스쿼시(squash)할 수 있습니다. 불행히도 저희 레벨은 몇개의 논리적 문제들 때문에 지원을 하지 않습니다. 이거에 대해서는 넘어가겠습니다. 요약하자면 커밋을 합칠 수 있습니다

자! 예시를 확인해 봅시다.

버튼을 누르면 인터렉티브 리베이스 대화창이 뜰 것 입니다. 커밋들의 순서를 바꿔보고(커밋을 빼 봐도 됩니다) 결과를 확인해봅시다!

git rebase -i HEAD~4

창 떠서 조작해봄

Boom! Git이 UI를 통해 명시한 그대로 커밋들을 복사했습니다.

로컬에 쌓인 커밋들

개발 중에 종종 이런 상황이 생깁니다: 눈에 잘 띄지 않는 버그를 찾아서 해결하려고, 어떤 부분의 문제인지를 찾기 위해 디버그용 코드와 화면에 정보를 프린트하는 코드 몇 줄 넣습니다.

디버깅용 코드나 프린트 명령은 그 브랜치에 들어있습니다. 마침내 버그를 찾아서 고쳤고, 원래 작업하는 브랜치에 합치면 됩니다!

이제 bugFix브랜치의 내용을 main에 합쳐 넣으려 하지만, 한 가지 문제가 있습니다. 그냥 간단히 main브랜치를 최신 커밋으로 이동시킨다면(fast-forward) 그 불필요한 디버그용 코드들도 함께 들어가 버린다는 문제죠.

여기에서 Git의 마법이 드러납니다. 이 문제를 해결하는 여러가지 방법이 있습니다만, 가장 간단한 두가지 방법 아래와 같습니다:

  • git rebase -i
  • git cherry-pick

대화형 (-i 옵션) 리베이스(rebase)로는 어떤 커밋을 취하거나 버릴지를 선택할 수 있습니다. 또 커밋의 순서를 바꿀 수도 있습니다. 이 커맨드로 어떤 작업의 일부만 골라내기에 유용합니다.

체리픽(cherry-pick)은 개별 커밋을 골라서 HEAD위에 떨어뜨릴 수 있습니다.

문제
이번 레벨을 통과하기 위해 어떤 방법을 쓰시든 자유입니다만, main브랜치가 bugFix 브랜치의 커밋을 일부 가져오게 해주세요.

git rebase -i main 👉 git rebase -i 브랜치이름

창 떠서 조작함

현재 선택된 브랜치와 상관 없이 (main 브랜치로 다시 선택할 필요 없이) 바로 리베이스 할 수 있음

git rebase bugFix main 👉 git rebase "리베이스 할 브랜치" "옮길 브랜치"

커밋들 갖고 놀기

이번에도 꽤 자주 발생하는 상황입니다. newImage와 caption 브랜치에 각각의 변경내역이 있고 서로 약간 관련이 있어서, 저장소에 차례로 쌓여있는 상황입니다.

때로는 이전 커밋의 내용을 살짝 바꿔야하는 골치아픈 상황에 빠지게 됩니다. 이번에는 디자인 쪽에서 우리의 작업이력(history)에서는 이미 한참 전의 커밋 내용에 있는 newImage의 크기를 살짝 바꿔 달라는 요청이 들어왔습니다.

이 문제를 다음과 같이 풀어봅시다:

  • git rebase -i 명령으로 우리가 바꿀 커밋을 가장 최근 순서로 바꾸어 놓습니다
  • git commit --amend 명령으로 커밋 내용을 정정합니다
  • 다시 git rebase -i 명령으로 이 전의 커밋 순서대로 되돌려 놓습니다
  • 마지막으로, main을 지금 트리가 변경된 부분으로 이동합니다. (편하신 방법으로 하세요)

이 목표를 달성하기 위해서는 많은 방법이 있는데요(체리픽을 고민중이시죠?), 체리픽은 나중에 더 살펴보기로 하고, 우선은 위의 방법으로 해결해보세요.

최종적으로, 목표 결과를 눈여겨 보세요 -- 우리가 커밋을 두 번 옮겼기 때문에, 두 커밋 모두 따옴표 표시가 붙어있습니다. 정정한(amend) 커밋은 따옴표가 추가로 하나 더 붙어있습니다.

git rebase -i HEAD~2
git commit --amend
git rebase -i HEAD~2
git rebase caption main

커밋 갖고 놀기 #2

이전 레벨에서 보셨듯이 rebase -i 명령으로 커밋의 순서를 바꿀 수 있습니다. 정정할 커밋이 바로 직전(top)에 있으면 간단히 --amend로 수정할 수 있고, 그리고 나서 다시 원하는 순서로 되돌려 놓으면 됩니다.

이번에 한가지 문제는 순서를 꽤 많이 바꿔야한다는 점인데요, 그러다가 리베이스중에 충돌이 날 수 있습니다. 이번에는 다른 방법인 git cherry-pick으로 해결해 봅시다.

git cherry-pick으로 HEAD에다 어떤 커밋이든 떨어 뜨려 놓을 수 있다고 알려드린것 기억나세요? (단, 그 커밋이 현재 가리키고 있는 커밋이 아니어야합니다)

간단한 데모로 다시 알려드리겠습니다:

git cherry-pick C2

좋아요! 계속할게요

그럼 이번 레벨에서는 아까와 마찬가지로 C2 커밋의 내용을 정정하되, rebase -i를 쓰지 말고 해보세요. ^.~

git checkout main
git cherry-pick C2 C3

Git 태그

이전 강의에서 배웠듯이, 브랜치는 이동하기 쉽습니다. 작업의 완료, 진행에 따라 이리저리 이동하면서 서로 다른 커밋을 참조하게 됩니다. 브랜치는 쉽게 변하며 임시적인 것입니다 항상 바뀌고 있죠.

이런 상황에서, 여러분은 여러분의 프로젝트의 역사(작업 이력)에서 중요한 지점들에 영구적으로 표시를 할 방법이 없을까 궁금할것입니다. 주요 릴리즈나 큰 브랜치 병합(merge)이 있을때가 그런 상황이겠군요. 이런 상황에 커밋들을 표시할 브랜치보다 영구적인 방법이 있을까요?

당연히 있습니다! Git 태그는 딱 이런 상황을 위해 존재합니다 -- Git 태그는 특정 커밋들을 브랜치로 참조하듯이 영구적인 "milestone(이정표)"으로 표시합니다.

중요한 점은, Git 태그는 커밋들이 추가적으로 생성되어도 절대 움직이지 않는다는 것입니다. 여러분은 태그를 "체크아웃"한 후에 그 태그에서 어떤 작업을 완료할 수 없습니다 -- 태그는 커밋 트리에서 특정 지점을 표시하기위한 닻같은 역할을 합니다.

자 태그가 무엇을 하는지 예제를 통해 알아봅시다

프로토타입의 첫 버전인 C1에 태그를 만들어 봅시다.

git tag v1(태그이름) C1(커밋 지정)

자! 아주 쉽죠. 우리는 태그의 이름을 v1이라고 지었고 커밋 C1을 지정해서 참조했습니다. 만약 커밋을 지정해주지 않으면 git은 HEAD가 있는 지점에 태그를 붙일 것입니다.

이번 레벨에서는 goal에 나타난것과 같이 태그를 만들고 v1을 체크아웃하면 됩니다. 분리된 HEAD 상태로 변하는것을 확인 해 보십시오 -- 이것은 v1 태그에 직접 커밋을 할 수 없기 때문입니다.

다음 레벨에서는 태그의 더 흥미로운 활용 방법을 확인해 볼 것입니다.

git tag v0 C1

git tag v1 C2

git checkout v1

Git Describe

커밋 트리에서 태그가 훌륭한 "닻"역할을 하기 때문에, git에는 여러분이 가장 가까운 "닻(태그)"에 비해 상대적으로 어디에 위치해있는지 describe(묘사)해주는 명령어가 있습니다. 이 명령어는 git describe 입니다!

Git describe는 커밋 히스토리에서 앞 뒤로 여러 커밋을 이동하고 나서 커밋 트리에서 방향감각을 다시 찾는데 도움을 줍니다; 이런 상황은 git bisect(문제가 되는 커밋을 찾는 명령어라고 간단히 생각하자)를 하고 나서라던가 휴가를 다녀온 동료의 컴퓨터에 앉는 경우가 있습니다.

Git describe 는 다음의 형태를 가지고 있습니다:

git describe <ref>

<ref>에는 commit을 의미하는 그 어떤 것이든 쓸 수 있습니다. 만약 ref를 특정 지어주지 않으면, git은 그냥 지금 체크아웃 된 곳을 사용합니다 (HEAD).

명령어의 출력은 다음과 같은 형태로 나타납니다:

<tag>_<numCommits>_g<hash>

tag는 가장 가까운 부모 태그를 나타냅니다. numCommits은 그 태그가 몇 커밋 멀리있는지를 나타냅니다. <hash>는 묘사하고있는 커밋의 해시를 나타냅니다.

간단한 예제를 확인해 봅시다. 아래의 트리에서:

git tag v2 C3

git describe main 명령은 다음을 출력합니다:

v1_2_gC2

git describe side는 다음을 출력합니다:

v2_1_gC4

이정도면 git describe를 충분히 활용할 수 있습니다! 이 레벨의 몇 지점을 describe 명령어를 통해 확인해보면서 느낌을 익혀 봅시다.

준비가 되면 커밋을 한번해서 레벨을 종료하세요. 자유롭게 연습해보세요 :P

git describe main 👉 v0_2_gC2

git describe side 👉 v1_1_gC4

git describe bugFix 👉 v1_2_gC6

부모를 선택하기

~ 수식처럼 ^ 수식 또한 뒤에 숫자를 추가 할 수 있습니다.

몇개의 세대를 돌아갈지 정하는 것 대신(~의 기능) ^ 수식은 병합이된 커밋에서 어떤 부모를 참조할지 선택할 수 있습니다. 병합된 커밋들은 다수의 부모를 가지고 있다는것을 기억하시나요? 어떤 부모를 선택할지 예측할 수가 없습니다.

Git은 보통 병합된 커밋에서 "첫" 부모를 따라갑니다. 하지만 ^ 수식을 숫자와 함께 사용하면 앞의 디폴트 동작대로가 아닌 다른 결과가 나타납니다.

여기 병합된 커밋이 있습니다. 우리가 main를 수식없이 체크아웃한다면 병합된 커밋의 첫 부모를 따라 올라갈 것입니다.

(화면에서는 첫 부모는 병합된 커밋 바로 위에 위치해 있습니다.)

이만 줄이고, 직접 확인해봅시다.

git checkout main^

간단하죠 -- 우리한테 익숙한 모습입니다.

자 이제 두번째 부모를 선택해봅시다...

git checkout main^2

보이나요? 다른 부모를 선택해 올라갔습니다.

^수식과 ~수식을 이용해 커밋트리에서 효과적으로 움직일 수 있습니다.:

git checkout HEAD~ 👇

git checkout HEAD^2 👇

git checkout HEAD~2 👇

빛처럼 빠르게 말이죠!

더 대단한 것은 이 수식들은 같이 사용할 수 있다는 겁니다! 확인해봅시다:

git checkout HEAD~^2~2 👇

앞과 같은 움직임이지만 하나의 명령으로 표현되었습니다.

0개의 댓글