본문 바로가기
공부/Git & GitHub

[Git] git 비슷한 명령어 제대로 이해하기 (fetch/pull, checkout/switch, merge/rebase)

by 웅대 2024. 8. 19.
728x90
반응형

로그인이라는 기능을 구현할 때 git을 활용하여 버전 관리를 한다고 하면 일반적으로 다음 과정을 거친다.

 

1. login 브랜치를 생성한다.

git branch login

 

2. login 브랜치로 이동한다.

git checkout login

 

3. 변경 사항들을 작은 단위로 나누어 커밋한다.

git add *
git commit -m "Feat: Form 구현"
git add *
git commit -m "Feat: 유효성 검사 구현"

 

4. main 브랜치에 머지한다.

git checkout main
git merge login

 

5. 원격 저장소에 push 한다.

git push origin main

 

6. 이후 원격 저장소에 변경사항이 있다면 pull 한다.

git pull origin main

 

위 명령어들만 알아도 git을 활용하는데 큰 문제가 없으나 유사한 명령어를 사용해서 위와 유사한 과정을 구현할 수 있다.

 

git에는 다음과 같은 유사한 명령어들이 존재한다.

브랜치 이동 checkout switch
브랜치 합치기 merge rebase
변경사항  pull fetch

 

위와 같이 비슷한 명령어 간의 차이점이 궁금해졌고 이번 기회에 제대로 차이점을 공부하려고 한다.

 

checkout / switch

checkout 명령어는 switch 명령어보다 더 큰 범위의 명령어라고 볼 수 있다.

 

  • checkout : 브랜치를 변경하거나 작업 트리 파일을 복원할 때 사용한다.
  • switch : 브랜치를 변경할 때 사용한다.

switch 명령어는 checkout의 기능 중 브랜치를 변경하는 기능에 특화된 기능이다.

 

checkout 명령어는 브랜치를 변경하는 것도 가능하지만 이전 커밋으로 돌아갈 때 사용할 수도 있다.

 

git에는 HEAD가 존재하는데 HEAD는 현재 브랜치를 가리키는 포인터이고 브랜치는 브랜치에 담긴 커밋 중 가장 마지막 커밋을 가리킨다.

 

master 브랜치와 login 브랜치가 커밋 C로부터 분리되었다고 하고 각 브랜치 별로 새로운 커밋이 하나가 생겼닫고 하면 다음과 같은 구조가 된다.

여기서 아래 명령어를 통해 HEAD가 가리키는 브랜치를 변경할 수 있다.

 

git checkout login
git switch login

 

위 두 명령어의 결과는 아래 그림으로 동일하다.

 

즉 브랜치를 이동할 때 checkout과 switch의 기능은 동일하다.

 

하지만 checkout은 switch와 다르게 추가로 작업 트리 파일을 복원할 때 사용할 수도 있다.

 

이 말은 쉽게 말해서 브랜치가 아닌 특정 커밋을 직접적으로 가리킬 수 있다는 뜻이다.

 

커밋 C의 해쉬 값이 cccccc라고 가정하면 해당 해쉬 값을 사용해서 HEAD가 해당 커밋을 직접적으로 가리키게 만들 수 있다.

 

git checkout cccccc

 

위와 같이 HEAD가 커밋을 직접적으로 가리키게 한다면 아래와 같은 문구가 출력된다.

Note: switching to 'cf58dda9b74c99ef247397aac610257a203569b1'.
You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command.
Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false

 

현재 'detached HEAD' 상태에 있다고 알려주고 있다.

 

가끔 개발을 하다보면 실수로 HEAD가 커밋을 직접적으로 가리키게 만들 때가 있는데 이럴 때 push를 시도하면 detahced HEAD 상태라고 오류가 발생한다.

 

이럴 땐 단순하게 커밋이 아닌 브랜치를 가리키도록 하면 해결할 수 있다.

 

HEAD가 커밋을 직접적으로 가리키게 만드는 경우는 다음과 같은 상황에서 유용하게 쓰일 수 있다.

 

  • 특정 커밋의 작업 트리 파일을 확인할 때
  • 특정 커밋으로부터 브랜치를 생성할 때

특정 파일의 특정 커밋에서의 내용을 보고 싶을 때 해당 커밋으로 checkout해서 내용을 확인할 수 있다.

 

또는 이전 커밋에서 브랜치를 생성할 때 사용할 수 있다.

 

위에서 HEAD가 C를 직접적으로 가리키는 상황을 다시 보자.

 

 

HEAD가 커밋 C를 가리키고 있을 때 커밋 C에서 브랜치를 생성할 수 있다.

 

여기서 만약 logout 브랜치를 만든다면 아래 그림처럼 변한다.

 

git branch logout

 

여기서 logout 브랜치를 통해 커밋을 하고 싶다면 logout 브랜치로 checkout하여 HEAD가 logout 브랜치를 가리키도록 하면 된다.

 

git checkout logout

다시 HEAD가 커밋 C를 직접적으로 가리키는 상황을 보자.

 

이 상태에서 커밋을 생성하면 어떻게 될까?

git add *
git commit -m "new commit"

 

 

커밋이 생성되기는 하지만 새로 생성된 커밋을 가리키는 branch가 존재하지 않기 때문에 만약 HEAD가 다른 브랜치를 가리키도록 한다면 해당 커밋을 찾기가 어렵다.

 

그러나 커밋은 불변 객체이기 때문에 garbage collector가 커밋을 포인팅하고 있는 객체가 없어도 커밋을 삭제하지 않기 때문에 git reflog 명령어를 사용하면 이 커밋을 복구할 수 있다.

 

detatched HEAD 에러는 대부분 이러한 상태에서 발생한다.

 

실수로 브랜치가 아닌 커밋을 직접적으로 가리키게 만든 상태에서 새로운 커밋을 생성하고 push 한다면 오류가 발생하는 것이다.

 

만약 HEAD가 커밋을 가리키는 상태에서 새로운 커밋을 생성했고 이 커밋을 유지하고 싶다면 어떻게 할 수 있을까?

 

그냥 HEAD가 새로운 커밋을 가리키고 있는 상태에서 브랜치를 생성하면 된다.

 

git branch new

merge / rebase

  • merge : 두 개 이상의 개발 기록을 함께 결합한다.
  • rebase : 다른 베이스 위에 커밋을 재적용 한다.

그림으로 보는 편이 빠를 것이다.

 

아래처럼 두 개의 브랜치가 존재하는 경우를 생각하자.

여기서 login 브랜치를 master 브랜치에 병합한다면 아래와 같다.

git checkout master
git merge login

이러한 merge 방식은 3-way merge라고 한다.

 

각 브랜치의 최신 커밋과 base 커밋, 총 3개의 커밋을 이용하기 때문에 3-way merge라고 한다.

 

 

다시 아래처럼 두 개의 브랜치가 존재하는 경우를 생각하자.

 

이번에는 rebase를 사용해보자.

 

git checkout login
git rebase master

 

 

이렇게 rebase를 사용해서 base를 재설정하고 나면 merge를 할 때 Fast Forward Merge가 가능하다.

 

위 구조를 보면 master 브랜치와 login 브랜치가 같은 선상에 존재한다.

 

이 상황에서 master 브랜치로 이동 후 merge를 진행하면 새로운 커밋이 생기지 않고 master 브랜치의 참조 위치만 바뀔 뿐이다.

 

git checkout master
git rebase login

 

 

위의 3-way merge와 다르게 새로운 커밋이 생기지 않았다.

 

rebase는 master 브랜치에서 하지 않는 것이 좋다.

 

master 브랜치에서 rebase를 하면 어떤 일이 일어나는지 보자.

 

다시 아래처럼 두 개의 브랜치가 존재하는 경우를 생각하자.

 

이번에는 master 브랜치에서 rebase를 사용해보자.

git checkout master
git rebase login

 

 

master 브랜치가 기존 브랜치와 매우 다른 형태가 되어 버렸다.

 

이 상태에서 push를 한다면 협업하는 쪽에서 충돌이 날 가능성이 있다.

 

 

pull / fetch

pull과 fetch의 가장 큰 차이는 변경 사항 통합의 여부이다.

 

  • pull : 원격 저장소의 변경 사항을 가져와서 현재 브랜치에 통합한다.
  • fetch : 원격 저장소의 브랜치와 object를 가져와서 원격 추적 브랜치를 갱신한다.

fetch는 변경 사항만 가져오고 통합하는 과정을 진행하지 않는다.

 

pull은 기본적으로 fetch를 진행한 다음 옵션에 따라 merge 혹은 rebase를 수행한다.

 

만약 pull이 아닌 fetch를 사용하여 변경 사항을 반영하고 싶다면 fetch를 통해 원격 추적 브랜치를 갱신한 다음 원격 추적 브랜치를 자신의 브랜치에 merge를 하면 된다.

 

pull을 사용하지 않고 fetch를 통해 수동으로 원격 저장소의 변경 사항을 로컬 저장소에 반영해보자.

 

origin의 main branch의 변경 사항을 fetch를 통해 원격 추적 브랜치로 가져오자.

 

git fetch origin main

 

이제 원격 추적 브랜치는 최신 커밋을 가리키고 있고 이를 로컬 main 브랜치에 merge 하면 된다.

 

git checkout main
git merge origin/main

 

물론 merge가 아닌 rebase를 진행해도 된다.

 

 

 

 

 

728x90
반응형

댓글