본문 바로가기

우아한테크코스

git fetch, rebase, merge, pull 명령어 확실히 알아보기

Git을 1년 이상 사용하고 있지만 아직도 확실히 알지 못해서 사용할 때마다 미심쩍은 부분을 가진 명령어들에 대해 확실하게 정리하기 위해서 글을 작성한다.

 

사실 나 혼자 하는 프로젝트라면 Git 명령어들을 막 사용해도 별 상관이 없었다. 혹시나 잘못 사용해서 커밋 이력이 다 날아가더라도 내가 다 알고 있는 코드들이니까 다시 복구하면 된다. 그런데 이제 협업 프로젝트를 진행하다 보니 Git 명령어들을 잘못 사용해서 다른 사람의 작업 내역을 날아가게 할까 봐, 커밋 이력이 꼬이게 될까 봐 걱정이 되었다.

 

git push 하려니까 git pull 하라고 경고창이 뜨고 그래서 git pull 을 입력했더니

 

 warning: Pulling without specifying how to reconcile divergent branches is
    discouraged. You can squelch this message by running one of the following
    commands sometime before your next pull:

      git config pull.rebase false  # merge (the default strategy)
      git config pull.rebase true   # rebase
      git config pull.ff only       # fast-forward only

    You can replace "git config" with "git config --global" to set a default
    preference for all repositories. You can also pass --rebase, --no-rebase,
    or --ff-only on the command line to override the configured default per
    invocation.

 

이런 경고창이 뜬 적이 셀 수도 많았다. 그럴때 어떤 옵션을 선택해야 하는 질 몰라서 아무거나 입력하거나, 변경이력을 없애고 다시 pull을 받은 후 다시 코드 작업을 하는 식으로 해결하였다🥲

 

그래서 fetch, rebase, merge, pull 이 4개 명령어만 잘 알고 있으면 협업 프로젝트를 문제없이 진행할 수 있다! 는 생각으로 정리를 해봤다. (add, commit, push는 기본이니)

 

우선 명령어 전에 origin, HEAD 같은 용어들 먼저 알고 가야 한다.

 

git branch -a 명령어를 입력하면 이렇듯 로컬 저장소, 원격 저장소에 있는 브랜치들 정보가 나타나게 된다.

여기서 origin 이 붙은 브랜치들은 원격 저장소의 브랜치를 뜻한다 (원격 저장소 이름을 origin 으로 등록했기 때문이다)

 

git remote -v 명령어로 어떤 원격 저장소들이 연결되어 있는지 확인 가능

HEAD는 현재 내가 작업하고 있는 브랜치를 뜻한다. 위의 사진에서는 * 표시가 되어 있는 fix/291-gitchan-api 가 HEAD이다. checkout 을 해서 브랜치를 이동하게 되면 HEAD 도 해당 브랜치로 이동하게 된다.

 

그러면 origin/HEAD 는 무엇을 뜻할까? 원격 저장소의 디폴트 branch의 HEAD 를 뜻한다. 원격 저장소의 현재 상태를 뜻하는 것이다. 

 

그리고 원격 브랜치는 원격 저장소의 상태를 반영하기만 한다. 로컬 저장소에서 원격 브랜치로 커밋을 해도 원격 브랜치를 갱신하지 않는다.

 

왼쪽이 로컬저장소. o/main 에서 커밋을 해도 o/main 이 갱신되지 않는다

git fetch

git fetch 는 원격 저장소에서 데이터를 가져오는 방법이다.

로컬 저장소에 없는 2개의 커밋을 가진 원격 저장소가 있다. 

 

이때, git fetch를 하면, 원격 저장소의 새 커밋 2개가 로컬 저장소에 다운로드되고, 원격 브랜치 origin/main 이 업데이트되는 것을 확인할 수 있다.

즉, git fetch 명령어는 원격 저장소에는 있지만 로컬에는 없는 커밋들을 다운로드하고 로컬 저장소의 원격 브랜치가 가리키는 곳을 업데이트하는 역할을 한다. 그러나, 로컬 브랜치의 상태는 전혀 바꾸지 않는다. 

 

원격 저장소에서 내려받은 데이터들을 로컬 저장소에 업데이트를 시켜보자!

사실 git fetch 하고 git rebase o/main, git merge o/main 해서 로컬 저장소에 반영을 시킬 수 있다.

 

그렇다면 rebase와 merge는 어떤 차이가 있을까? 

git merge

git merge를 하면 두 개의 부모(parent)를 가리키는 특별한 커밋을 만들어 낸다. 

 

iss53 브랜치의 작업이 다 끝나서 master 브랜치에 작업 내용을 합치고 싶다. 그러면 합칠 브랜치에서 합쳐질 브랜치를 merge 하면 된다. 

이 상황에서는 git checkout master로 master 브랜치로 이동한 뒤 git merge iss53을 해주면 된다. 

 

그러면 Git은 각 브랜치가 가리키는 커밋 두 개와 공통 조상 하나를 사용하여 3-way Merge를 한다. 

 

그리고 단순히 브랜치 포인터를 최신 커밋으로 옮기는 게 아니라 3-way Merge의 결과를 별도의 커밋으로 만들고 나서 해당 브랜치가 그 커밋을 가리키도록 이동시킨다.

 

이렇게 3-way Merge를 하면 conflict 가 발생할 수도 있다. 두 브랜치에서 같은 파일의 한 부분을 동시에 수정했다면 git 은 어떤 변경점을 적용해야 할지 모르기 때문이다. resolve를 잘해주면 된다. 

 

그리고 3-way Merge 말고 Fast-forward 방식으로 머지가 될 때도 있다.

master 브랜치에서 긴급하게 수정이 필요해서 hotfix 브랜치를 생성해서 작업을 수행했다. 이 hotfix 브랜치의 변경 내역을 master 브랜치에 반영해야 한다.

 

git checkout master, git merge hotfix를 해주면 hotfix 브랜치가 가리키는 C4 커밋이 C2 커밋에 기반한 브랜치이기 때문에 브랜치 포인터는 Merge 과정 없이 그저 최신 커밋으로 이동한다. 이를 Fast-forward 방식이라고 한다. 이후 hotfix 브랜치는 쓸모를 다했기 때문에 삭제를 해주면 된다.

 

git rebase

이제 rebase는 어떻게 커밋 이력을 합치는지 알아보자.

동일한 조상 C2에서 각각 master, experiment 브랜치에 작업을 수행했다.

 

이때 git checkout master, git merge experiment를 한다면 아래와 같이 3-way-merge를 통해 새로운 커밋을 생성할 것이다.

 

그렇다면 git checkout experiment, git rebase master 명령어를 수행한다면 어떻게 될까? 명령어를 해석해 본다면 experiment의 base 브랜치를 master 브랜치로 바꾼다는 뜻이다. 

커밋 이력은 이렇게 변하게 되는데, git 이 하는 일은 이렇다.

일단 두 브랜치가 나뉘기 전인 공통 커밋으로 이동하고 나서 그 커밋부터 지금 Checkout 한 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 어딘가에 임시로 저장해 놓는다. Rebase 할 브랜치(experiment)가 합칠 브랜치(master)가 가리키는 커밋을 가리키게 하고 아까 저장해 놓았던 변경사항을 차례대로 적용한다.

 

그러고 나서 git checkout master, git merge experiment를 하면 Fast-forward 머지가 일어나고 커밋 이력은 아래와 같이 된다. 

이 그림에서 C4'로 표시된 커밋의 내용은 위에 머지를 진행했을 때의 C5의 커밋 내용과 같을 것이다. 

Merge 이든 Rebase 든 둘 다 최종 결과물은 같고 커밋 히스토리만 다르다는 것이 중요하다. Rebase는 선형의 깨끗한 히스토리를 만든다.

 

rebase에 주의사항이 있는데 이미 공개 저장소에 Push 한 커밋을 Rebase 해서 다시 Push 하면 안 된다. 

관련한 글은 https://git-scm.com/book/ko/v2/Git-브랜치-Rebase-하기 여기 잘 정리되어 있다. 

git pull

이제 드디어 git pull을 알아보자. git pull 은 간단히 말하면 git fetch와 git merge를 합친 명령어이다.

그리고 git pull --rebase는 git fetch와 git rebase를 합친 명령어이다. 

이제 위의 경고창에서 이것들이 무엇을 뜻하는지 알 수 있게 되었다. 

pull을 할 때 merge 방식, rebase 방식 아니면 fast-forward 방식을 할지 선택하라는 것이었다! 😎

 

      git config pull.rebase false  # merge (the default strategy)
      git config pull.rebase true   # rebase
      git config pull.ff only       # fast-forward only

 

git push, pull, fetch의 인자들

우리가 보통 git push는 git push origin [로컬브랜치명], git pull과 git fetch는 그냥 인자 없이 쓰는 경우가 많은데 해당 명령어의 인자들에 대해서 알면 굉장히 유용하므로 다시 정리를 해본다.

 

git push <remote> <place>

예를 들어 git push origin main이라고 하면 git 은 내 저장소에 있는 "main"라는 이름의 브랜치로 가서 모든 커밋들을 수집한다. 그다음 "origin"의 "main"브랜치로 가서 이 브랜치에 부족한 커밋들을 채워 넣는다. 

 

그런데 로컬 브랜치의 이름과 푸시할 원격 브랜치의 이름이 달라야 한다면 어떻게 해야 할까?

 

git push origin <source>:<destination> 

그럴 땐 이런 식으로 ":" 으로 구분해서 source에 로컬브랜치명, destination에 원격브랜치명을 입력해 주면 된다. 원격브랜치가 존재하지 않으면 git 은 원격브랜치를 알아서 만들고 푸시를 해준다 👏

 

git fetch와 git pull 은 데이터를 원격에서 로컬로 받아줘야 하니 source와 destination의 위치를 git push 때와 반대로 해주면 된다.

 

git fetch를 인자 없이 수행하면 원격저장소에서 모든 원격 브랜치들로 커밋들을 내려받는다. 

 

 

 git push와 git fetch에 source에 "없음"도 지정해 줄 수 있다.

 

git push origin :side

이럴 때는 원격 브랜치의 side 란 브랜치를 삭제하고

 

git fetch origin :bar

이럴때는 로컬 브랜치에 bar 란 브랜치를 생성한다.

 

 

참고자료 

https://learngitbranching.js.org/?locale=ko 

https://git-scm.com/book/ko/v2/

 

Learn Git Branching

An interactive Git visualization tool to educate and challenge!

learngitbranching.js.org