TIL16, Git: Fundamentals!

sunghoonKim·2020년 12월 3일
0

깃을 공부할 때 Learn Git Branching을 이용하였는데, 깔끔한 UI로 깃을 이해하는데 정말 많은 도움이 되었다. 덕분에 쉽게 배웠지만, 쉽게 배운 만큼 잊는 것도 금방이겠지. 잊지 않기 위해 블로그에 정리해본다.


Git Fundamentals

앞으로 적을 깃 커맨드 중 몇몇은 약소화 한 것으로, 실제 커맨드와는 다르다. 깃의 전반적인 개념을 이해하는 것이 중점이므로, 방해가 될 수 있는 디테일을 좀 쳐냈다. 그러니까, 정확한 커맨드는 꼭 구글링하여 찾아보도록 하자.

1. Commit

현재까지 작업한 내용을 기록한다.

  • git add . : 작업 내용을 스테이징 시킨다.
  • git commit -m "커밋 메시지" : 스테이징된 작업 내용을 커밋 히스토리에 기록한다.

2. Branching

현재 있는 커밋에서 새로운 브랜치를 시작한다. 브랜치 관련해서 매우 다양한 커맨드가 있다.

  • git branch <branch_name> : 브랜치 생성한다.
  • git branch <branch_name> <commit_hash> : 브랜치를 특정 커밋에 생성한다.
  • git checkout <branch_name> : 브랜치로 HEAD를 이동한다.
  • git checkout -b <branch_name> : 브랜치 생성 후, 해당 브랜치로 HEAD를 이동한다.
  • git checkout -b <branch_name> <commit_hash> : 특정 커밋에 브랜치 생성과 이동을 동시에 실행한다.
  • git branch -f <branch_name> <commit> : 특정 커밋에 브랜치를 생성, 혹은 기존의 브랜치 HEAD를 특정 커밋에 강제로 옮긴다.

3. Merging

두개의 브랜치를 병합한다. git mergegit rebase 로 2가지 방법이 존재.

git merge <source_branch>

  • git checkout <place_branch>: 먼저 머지가 될 장소 브랜치로(머지 후에 살아남을 브랜치) 이동하는 과정이 선행되어야 한다.
  • git merge <source_branch>: 병합을 진행.
  • git merge <bugFix>

위에서 보이듯, 병합 후에는 머지 커밋이 생성된다.

git rebase <source_branch>

  • git checkout <source_branch>: 먼저 머지가 될 소스 브랜치로 이동하는 과정이 선행되어야 한다.
  • git rebase <place_branch>: 병합을 진행.

혹은

  • git rebase <place_branch>:<source_branch>: 위의 두 과정을 한번에.
  • git rebase <master>:<bugFix>

특징으로는, 소스 브랜치가 장소 브랜치에 붙여지는 것이기 때문에, 커밋 히스토리가 깔끔하게 일직선으로 나타나게 된다. (a.k.a git merge 처럼 머지 커밋이 생성되지 않는다.)

4. Detaching HEAD & 상대경로!

기본적으로 HEAD는 브랜치에서 가장 상위 커밋에 자동적으로 붙어있게 된다. 하지만, 원하는 커밋 어느 것에나 checkout 을 통해 HEAD를 옮겨줄 수도 있다. 다만, 그렇게 되면 HEAD가 브랜치에서 떨어져 나온, Detached HEAD 상태에 놓이게 된다. 이 상태에서 커밋을 한다면, HEAD가 어느 브랜치에도 속하는 것이 아니기 때문에, 해당 커밋은 추적되지 않는다. 다시 브랜치로 HEAD를 돌리려면, 다시 checkout 커맨드를 이용.

checkout 을 통해 특정 커밋으로 이동할 때는, 해당 커밋의 해쉬 id를 통해 이동할 수도 있지만, 상대 경로를 이동할 수도 있다.

  • git checkout C4 : C4로 이동한다. (편의를 위해 C4를 썼지만, 실제로는 각 커밋 hash id가 온다.)
  • git checkout C4^ : C4에서 한 단계 위 커밋으로 이동한다.
  • git checkout C4^2 : C4가 merge commit 일 경우에, 두번째 부모로 이동한다.
  • git checkout HEAD~ : HEAD를 기준으로 한 단계 위 커밋으로 이동한다.
  • git checkout C4~5 : C4를 기준으로 다섯 단계 위 커밋으로 이동한다.

위의 동작들을 한번에 체인해서 실행할 수도 있다.

  • git checkout HEAD~^2~2 : HEAD를 기준으로 한 단계 위 커밋으로 이동, 머지 커밋에서 두번째 부모로 이동, 그곳에서 두 단계 위의 커밋으로 이동.

5. Reversing Changes

이미 기록된 커밋을 되돌리는 커맨드. 2가지가 존재 한다. git resetgit revert 가 그것.

git reset

  • git reset <커밋 hash id 혹은 상대경로>
  • git reset HEAD~1

인자로 주어진 커밋으로 HEAD가 이동한다. 이후에 남아있는 커밋은 따로 회수 되지 않으면 다음 커밋 사이클에서 garbage collector에 의해 사라진다.

git revert

  • git revert <되돌릴 커밋 hash id 혹은 상대경로>
  • git revert HEAD

인자로 주어진 커밋을 되돌리는 커밋을 생성 한다.

git reset 은 커밋 히스토리에 혼란이 생성시킬 가능성이 있으므로, 보통 공동으로 작업하는 브랜치 보단, 개인으로 작업하는 브랜치에서 사용된다. git revert 는 새로운 커밋을 생성하는 방식이기 때문에, 혼란이 올 걱정이 없어(팀원은 단순히 새 커밋을 기존의 브랜치에 머지 시키면 된다), 공동으로 작업하는 브랜치에서 사용하기에 적합.

6. Cherry-pick

HEAD가 있는 곳에 선택한 커밋을 골라서 복사한다. 복사되는 순서는 커밋이 인자로 넘겨받는 순서를 따른다.

  • git cherry-pick <복사할 커밋 리스트>
  • git cherry-pick c2 c4

원하는 커밋만 골라서 쏙쏙 넣는 아주 유용한 기능.

7. Interactive Rebase

리베이스에서 -i 플래그를 주면, 더 다양한 기능을 제공하는 인터렉티브 리베이스를 실행한다. 커밋 순서 변경, 커밋 삭제 등 여러 기능을 지원. 기존의 리베이스는 브랜치를 병합하는게 목적이라면, 인터렉티브 리베이스는 커밋 히스토리를 수정하는데 더 큰 목적을 두는 듯 하다.

  • git rebase -i <선택 범위. 여기에 선택되는 커밋은 포함되지 않는다.>
  • git rebase -i HEAD~4

실제로는 이렇게 깔끔한 UI 같은건 없다. ^-^

(참고로 interactive rebase에 대해서는 이후의 글에서 더 자세히 다뤄볼 예정)

8. Tag

특정 커밋에 태그를 달아 둘 수 있다. 태그는 해당 커밋에 영구적으로 설정 되기 때문에 중요한 커밋에 경우 한번 태그를 설정 해두어 매번 hash id를 찾는 것 대신 간단한 태그 이름으로 접근할 수 있는 장점이 있다.

  • git tag <tag_name> <commit_hash_id>

9. Cloning

Url를 통해 특정 remote repo를 local에 복사, 저장한다.

  • git clone <url>

10. Fetching

remote repo에서 커밋 히스토리를 내려 받는다. 단, 바로 적용되는 것이 아니라 작업 내용에 대한 기록만 내려받아 o/ 브랜치에 저장한다. 해당 기록에 따라, 변경점을 반경하려면, 머지를 거쳐야 한다.

o/ 브랜치는 리모트를 반영하는 브랜치이다. 기존의 브랜치와는 다르게 checkout, commit, rebase등의 수정기능을 사용할 수 없다. 따라서, 준식 멘토님께서 항상 말했듯이, "우리는 리모트에 갈 수 없다."

  • git fetch : 아무런 인자가 주어지지 않으면, 리모트내 모든 변화 사항을 가져온다.
  • git fetch origin master

위에서 보이듯이, master 브랜치는 아무일도 일어나지 않지만, o/master 브랜치에는 적용이 되었다. 이후에, 마스터에 해당 내용을 적용하기 위해선, git merge o/master 를 통해야 한다.

바로 파일을 수정하는 것이 아니기 때문에, 다음에 나올 내용인 git pull 보다, 리모트에 생긴 변화를 반영할 때 더 안전한 방법이다. 먼저 o/master 만 적용시키고, 해당 내용에 대해서 충분히 숙지한 후, 본래 브랜치에 적용시킬 수 있기 때문.

11. Pulling

리모트에 생긴 변화를 곧장, 현재 브랜치에 적용시킨다. git fetchgit merge 를 한번에 실행하는 셈.

  • git pull <remote> <branch>
  • git pull origin master

merge의 결과로 머지 커밋이 생성 되는 것을 볼 수 있다.

12. Pushing

로컬에서 작업한 내용을 리모트에 반영 시키는 커맨드. push 를 하면 o/ 브랜치 또한 바로 반영이 되어 업데이트 된다.

  • git push <remote> <branch>
  • git push origin master

13. Stashing

working directory 에서 작업을 하던 중, 가끔 다른 브랜치를 확인하고 싶어질 때가 있다. 그치만 깃은 checkout을 허용하지 않는다. 현재까지의 작업물이 있기 때문에, 그것들을 해결해 주어야 하기 때문.

그러기 위해선 현재까지의 작업내용을 커밋으로 남겨야 하는데, 아직 작업 내용을 커밋으로 남기기에는 또 너무 애매한 경우가 있다. 그럴 경우, git stash 를 사용한다. git stash 는 현재까지 작업한 내용을 stash 라 하는 임시 저장소에 다 때려넣어 working directory를 깔끔하게 만들어주는 역할을 한다.

  • git stash : 현재까지의 작업 내용을 stash 에 저장.
  • git stash list : stash 에 저장된 작업 내용들의 리스트를 확인.
  • git stash apply : 가장 최근에 stash 된 작업 내용을 현재 working directory에 적용.
  • git stash apply --index : 저장된 작업 내용의 staged 상태까지 적용시킨다. (떠났을 때의 상황을 완벽하게 재현)
  • git stash apply stash@{#} : 특정 작업 내용을 현재 working directory에 적용.
  • git stash drop : 가장 최근에 stash 된 작업 내용을 리스트에서 삭제
  • git stash drop stash@{#} : 특정 작업 내용을 리스트에서 삭제
  • git stash pop : applydrop 를 동시에 실행.

연습

1. Handling Diverged Remote

팀원과 내가 같은 브랜치를 클론한 뒤 각자의 작업을 하던 중, 팀원이 먼저 기능 구현을 마무리 하여 리모트에 반영을 시켰다. 그것을 모르고 있던 나는, 나의 기능을 마무리 한 뒤, 푸시를 하여 반영을 시키려 하지만, 푸시가 되지 않는다. 나의 로컬과 리모트가 다르기 때문.

협업을 하는 과정에서 흔하게 일어날 수 있는 상황이다. 어떻게 해결해야 할까. 먼저 리모트에서 추가된 부분을 나의 로컬에 내려받고, 내가 작업하던 내용들을 해당 부분에 붙여야 할 것이다. 그렇게 해야 베이스가 되는 커밋 히스토리가 비슷해져 푸시를 할 수 있기 때문.

위의 내용을 git command 로 구성해보자면,

  1. git fetch origin master
  2. git rebase o/master
  3. git push origin master

혹은

  1. git pull --rebase origin master
  2. git push origin master

리베이스 대신 머지를 사용하는 방법도 있다.

  1. git fetch origin master
  2. git merge o/master
  3. git push origin master

혹은

  1. git pull origin master
  2. git push origin master

2. In case when your remote master is locked but you made commits in your local master

리모트에 있는 마스터는 푸시를 바로 할 수 없도록 락이 걸려있다. 하지만 나는 그것을 깜빡하고 마스터에서 작업한 뒤 커밋을 하였다. 푸시를 해보고 나서야 리모트에 락이 걸려있다는 것을 발견. 어떻게 해결 해야 할까.

먼저 해당 작업물을 새로 브랜치를 만들어 저장한다. 그후 마스터에서 커밋을 삭제. 새로운 브랜치를 푸시하여, PR 요청을 하는, 정상적인 방법으로 작업물을 리모트 마스터에 병합시킨다. 깃 커맨드로는 아래와 같이 구성된다.

  1. git branch -f feature master
  2. git reset HEAD~
  3. git checkout feature
  4. git push origin feature

깔끔한 UI가 있으니, 확실히 깃의 개념에 대해서 수월하게 이해할 수 있었던 것 같다. 깃이 이런 깔끔한 UI로 구성 돼 있으면 좋으련만. 그럼 깃을 배우려고 그 고생을 안해도 될텐데 말이다. 혹시 터미널에서 코딩하는 걸 좋아하는 백엔드 개발자 분들이 공사를 치는 건가? 🤔

1개의 댓글

역시 15기 프둥이 고수 토니!!
터미널로 삽질하면 배운 저보다 스마트한 스마트맨...

답글 달기