Git 재활 훈련 8일차 - git fetch, git pull

0

Git

목록 보기
8/14

Fetch와 Pull

원격 추적 branch

remote repository에 있는 code를 clone하면 다음과 같은 과정이 동작하게 되는 것이다.

  • remote github repo
                          master
                            |
                            v
commit1 ---> commit2 ---> commit3

위의 github repo를 clone하도록 하자. local에 다음과 같이 생기게 될 것이다.

  • local repo
                          master
                            |
                            v
commit1 ---> commit2 ---> commit3
                            ^
                            |
                        origin/master

내 local repo로 clone해오면 master branch가 생기는 것을 볼 수 있다. 이 master branch가 remote repo의 master branch이다. 그런데 origin/master와 같은 branch도 추가된 것을 볼 수 있다. 이는 remote repo에는 볼 수 없던 것인데, local repo에만 생긴다. 이러한 branch를 일컫어 원격 추적 branch라고 한다.

원격 추적 branch는 remote에 있는 master branch의 상태에 대한 참조로, 마음대로 움직이게 할 수 없다. 이는 system에서 사용하는 것으로 origin에 있는 master branch에 대한 마지막 commit을 포인팅하기 위한 bookmark라고 생각하면 된다.

즉, 하나의 pointer로 처음 시작할 때는 master branch와 동일한 commit을 가리키지만, 개발자가 local repo에서 master branch로 commit을 추가하고 작업을 이으면, master branch가 가리키는 HEAD commit은 앞으로 가는 것에 비해, origin/master처음 clone하거나 fetch, pull한 시점의 commit만 포인팅한다.

원격 추적 브랜치는 origin/master처럼 remote/branch라는 이름 형식을 가진다. 현재 내 local repo의 원격 추적 branch가 무엇인지 알고 싶다면 git branch -r을 사용하면 된다.

git branch -r
origin/HEAD -> origin/master
origin/master

origin/master라는 원격 추적 branch를 볼 수 있다.

만약 local에서 master branch를 두 번 commit하였다면, 다음과 같이 된다.

  • local repo
                                                    master
                                                      |
                                                      v
commit1 ---> commit2 ---> commit3 ---> commit4 ---> commit5
                             ^
                             |
                        origin/master

원격 추적 branch인 origin/master는 가만히 있는 것을 볼 수 있다.

이제 실제 실습을 해보면서 실제로 그렇게 동작하는 지 확인해보도록 하자. github에서 remote repository로 demo를 만들도록 하자. 만들었다면 local에서 다음의 명령어를 통해 github repo에 code를 넣도록 하자.

echo "test" >> test.txt
git init
git add test.txt
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/colt/demo.git
git push -u origin main

이제 두 가지의 commit을 더 만들어보도록 하자.

echo "test2" >> ./test.txt
git add ./test.txt
git commit -m "second commit"

echo "test3" >> ./test.txt
git add ./test.txt
git commit -m "second commit"

이제 다음과 같은 상황이 된 것이다.

  • local repo
                           main
                            |
                            v
commit1 ---> commit2 ---> commit3
    ^
    |
origin/main

원격 추적 branch인 origin/main보다 local branch인 main가 두 개의 commit을 더 앞서고 있는 상황이다. 이를 확인할 수 있는 방법으로 git status를 입력해보도록 하자.

it status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

2 commits가 원격 추적 브랜치인 origin/main보다 앞서고 있다는 것이다.

원격 추적 브랜치인 origin/main도 브랜치이기 때문에 checkout이 가능하다.

git checkout origin/main

text.txt를 확인하면 test 밖에 없는 것을 볼 수 있다. 이는 두번째, 세번째 commit들이 없는 것을 볼 수 있다.

원격 추적 브랜치인 origin/main은 아직도 첫번째 commit에만 있는 것이다. 원격 추적 브랜치는 remote repo의 HEAD를 따라간다. 따라서, git push를 통해서 업데이트해주도록 하자.

git checkout main
git push origin main

git status로 확인해보도록 하자.

git status

On branch main
Your branch is up to date with 'origin/main'.

원격 추적 브랜치가 local main branch에 따라온 것을볼 수 있다.

원격 추적 branch로 작업하기

원격 추적 branch는 clone할 당시에 github에 있는 branch들의 정보를 담고 있다고 했다. 이를 통해서 github에서 clone 시에 각종 branch로 이동해 작업을 진행할 수 있다.

먼저 test에 사용될 remote repo를 보도록 하자.

https://github.com/Colt/remote-branches-demo

여기에 들어가면 catus.txtrose.txtmain branch에 있는 것을 볼 수 있다.

해당 repo를 clone해보도록 하자.

git clone https://github.com/Colt/remote-branches-demo

clone 후에 현재 어떤 branch에 있는 지 확인해보도록 하자.

git branch 
* main

remote repo로 돌아가서 github에 어떤 branch들이 있는 지 보도록 하자.

main
fantasy
food
more-fantasy
morefood
movies

6개의 branch들이 있는 것을 알 수 있다. 위에서 git branch 명령어로 확인했듯이, 해당 branch들은 내 local에 없는 것을 알 수 있다. 그런데, github에 있는 food branch로 가서 apple.txt라는 파일들 보고싶다면 어떻게해야할까??

이것을 가능하게 해주는 것이 바로 원격 추적 branch이다. 우리는 원격 추적 branch를 통해서 github에 있는 branch로 이동이 가능하고, 파일 수정, 추가가 가능한 것이다. 원격 추적 branch가 우리 local에 있는 지 확인해보도록 하자.

git branch --all
* main
  remotes/origin/HEAD -> origin/main
  remotes/origin/fantasy
  remotes/origin/food
  remotes/origin/main
  remotes/origin/more-fantasy
  remotes/origin/morefood
  remotes/origin/movies

모든 원격 추적 branch들이 있는 것을 알 수 있다. 이 원격 추적 branch를 통해서 우리는 local의 branch로 접근이 가능한 것이다.

가령 main branch는 내 local에 있는 개인 branch이지만, 이 branch는 원격 추적 branch로 remotes/origin/HEAD를 따른다. remotes/origin/HEAD는 또한 remotes/origin/main을 따르고 이는 origin/main을 의미한다. 따라서, 내 local의 main branch가 origin/main을 따르듯이, origin/food branch는 내 local의 food branch를 따를 것이다.

그런데, 우리의 local에는 food branch가 없다. food branch가 없지만 git switch를 통해서 food branch를 만들고 이를 origin/food branch를 따르도록 할 수 있다.

git switch food

Branch 'food' set up to track remote branch 'food' from 'origin'.
Switched to a new branch 'food'

이제 apple.txt 파일을 확인해보도록 하자.

cat ./apple.txt 
                             ___
                          _/`.-'`.
                _      _/` .  _.'
       ..:::::.(_)   /` _.'_./
     .oooooooooo\ \o/.-'__.'o.
    .ooooooooo`._\_|_.'`oooooob.
  .ooooooooooooooooooooo&&oooooob.
 .oooooooooooooooooooo&@@@@@@oooob.
.ooooooooooooooooooooooo&&@@@@@ooob.
doooooooooooooooooooooooooo&@@@@ooob
doooooooooooooooooooooooooo&@@@oooob
dooooooooooooooooooooooooo&@@@ooooob
dooooooooooooooooooooooooo&@@oooooob
`dooooooooooooooooooooooooo&@ooooob'
 `doooooooooooooooooooooooooooooob'
  `doooooooooooooooooooooooooooob'
   `doooooooooooooooooooooooooob'
    `doooooooooooooooooooooooob'
     `doooooooooooooooooooooob'
jgs   `dooooooooobodoooooooob'
       `doooooooob dooooooob'
         `"""""""' `""""""'

food branch로 이동된 것을 볼 수 있다.

git status로 현재 food branch가 어떤 원격 추적 branch를 따라가고 있는 지 확인해보도록 하자.

git status

On branch food
Your branch is up to date with 'origin/food'.

nothing to commit, working tree clean

원격 추적 branch인 origin/food를 따르는 것을 볼 수 있다.

이렇게 원격 추적 branch의 이름과 동일한 branch로 이동하면 local에서도 remote repo의 원격 추적 branch를 얻어낼 수 있는 것이다.

사실 git switch가 나오기 이전에는 다음과 같은 명령어를 통해서 원격 추적 branch를 따랐다.

git checkout --track origin/food

해당 명령어는 git switch에 비해서 불편하기 때문에 이제는 자주 사용되지 않는다. 물론 git checkout food와 같이 입력만해도 원격 추적 branch를 찾아주고 checkout해주지만 git switch를 더 사용하도록 하자.

git fetch

github에 다음의 remote repo가 있다고 하자.

commit1 ---> commit2

해당 remote repo를 내 local로 clone하면 다음과 같이 된다.

              master
                |
                v
commit1 ---> commit2
                ^
                |
            origin/master

local에서 작업을 좀 더 해보도록 하자.

                             master
                                |
                                v
commit1 ---> commit2 ---> local commit3
                ^
                |
            origin/master

origin/mastermaster가 분리된 것을 볼 수 있다. 그런데, 만약 github에 있는 remote repo에서 다른 사람이 다음과 같이 commit을 했다고 하자.

commit1 ---> commit2 ---> commit3 ---> commit4 ---> commit5

이제 내 local의 원격 추적 bracnh인 origin/master와 remote repo의 master와는 완전히 다른 길을 걷고 있다. 어떻게 해야할까?

이러한, 충돌 문제를 해결해주는 것이 바로 git fetchgit pull이다.

   (worksapce)
Working Directory   |    Stating Area   |local Repository | remote Repository |
-------------------------------------------------------------------------------
    git add ------> |   git commit ---> |   git push ---> |                   |
                    |                   |                 | <---- git fetch   |
                    |                   |                 |                   |
    <--------------------------git push------------------------------            

git fetchgit pull은 remote에서 local로 file을 업데이트하는데, git fetch는 local repository에 올리는 대신에 git pull은 바로 working directory로 올린다.

즉, git pull 시에는 remote repo의 변경 사항들이 바로 내 working directory에 반영이되어 code를 수정시키는 반면에 git fetch는 local repository로 가서 내 local code들을 바로 수정시키지 않는다.

fetching은 remote repo에서 changes(변화들)을 다운로드하지만, 자동으로 우리의 working directory file들에 merge되지 않는다. 이는 우리의 local code에 강제적으로 merge를 시키지 않고, 내 local repo에서 다른 이들이 개발한 변화 내용들을 볼 수 있도록 한다.

git fetch <remote>

git fetch만 해도 보통 git fetch origin과 동일하다.

branch를 지정해서 가져올 수도 있다.

git fetch <remote> <branch>

branch를 생략하면 모든 원격 추적 branch들을 update한다.

이제 우리의 문제 상황을 보도록 하자.

  • github
commit1 ---> commit2 ---> commit3 ---> commit4 ---> commit5
  • local repo
                             master
                                |
                                v
commit1 ---> commit2 ---> local commit3
                ^
                |
            origin/master

여기서 git fetch를 사용하면 내 local working directory에는 code 변화가 적용되지 않으므로, 다음과 같이 된다.

  • local repo
                                        origin/master
                                              |
                                              v
                  commit3 ---> commit4 ---> commit5
                 /
                /
commit1 ---> commit2 ---> local commit3
                               ^
                               |
                             master

내 local commit3에는 영향이 없다. 왜냐하면 내 local working directory에 code를 부어놓지 않기 때문이다.

git fetch 실습

git fetch를 사용하는 대표적인 두 가지 경우들이 있다.
1. git fetch를 통해서 내 local에는 없는 remote repo의 branch 가져오기
2. git fetch를 통해서 내 local에는 없는 commit 사항들 local repository로 가져오기 (단, working directory와 병합 X)

먼저 첫번째 경우를 알아보도록 하자. 실습을 위해서 새로운 github repo를 하나 만들어보도록 하자. 새로운 github repo를 만들었다면, local repo를 만들기 위해서 아래의 script를 입력하도록 하자.

echo "# demo" >> README.md
git init
git add README.md
git commit -m "commit1"
git branch -M main
git remote add origin https://github.com/colt/demo.git
git push -u origin main

첫번째 'commit1'이 만들어졌다. 먼저 github를 통해서 새로운 branch를 하나 만들어보도록 하자.

main branch --> 'View all branches' --> 'New branch' --> 'test'

test branch가 만들어졌으면 test branch로 이동하고 github에서 README.md 파일을 수정하도록 하자. README.md 파일을 클릭한 다음 가운데 오른쪽에 있는 'Edit this file'을 클릭

  • README.md
# demo
# test

맨 아래에 있는 Commit changes에 다음의 description을 추가하도록 하자. 'commit changes' 입력

완료가 되었다면, 이제 내 local pc에서 해당 branch와 commit 사항을 확인해보도록 하자. 현재의 상황을 정리하면 다음과 같다.

- github
 main
   |
   v
commit1
      \
       ----> commit2
                ^
                |
               test
-----------------------------------------------------------
- local repo
 main    
   |     
   v        
commit1 
   ^        
   |    
origin/main

위와 같은 상황이다. commit2를 가진 test branch를 얻어오기 위해서 git fetch를 사용해보도록 하자.

이전에도 말했듯이 git fetch를 사용하면 모든 원격 추적 branch들을 가져온다고 했다. 따라서, github에 있는 test branch에 대한 원격 추적 branch를 가져오게 되는 것이다.

git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 354 bytes | 354.00 KiB/s, done.
From https://github.com/colt/demo
 * [new branch]      test       -> origin/test

origin/test를 잘 가져온 것을 볼 수 있다. 이제 test branch로 이동해보도록 하자.

git switch test 
Branch 'test' set up to track remote branch 'test' from 'origin'.
Switched to a new branch 'test'

성공적으로 test branch로 이동하게 되었다. README.md를 확인해보도록 하자.

cat ./README.md 
# demo
# test

test branch가 가진 commit이 적용되어 #test가 README에 추가된 것을 볼 수 있다.

이렇게 git fetch를 통해서 github에 있는 원격 추적 branch를 안전하게 가져올 수 있다. 안전하게 가져온다는 말은 내가 local working directory에 수정한 사항들에 대해서 변동없이 가져올 수 있다는 것이다.

현재의 local repo의 commit 사항을 정리하면 다음과 같이 된다.

- github
 main
   |
   v
commit1
      \
       ----> commit2
                ^
                |
               test
-----------------------------------------------------------
- local repo
 main    
   |     
   v        
commit1 
   ^   \              
   |    --------> commit2     
   |                 ^
origin/main          |
                origin/test

이제 git fetch의 두 번째 사용 용도인 내 local에는 없는 commit 사항들을 가져오는 경우에 대해서 해보도록 하자.

먼저 local repo에서 main branch로 이동시켜주도록 하자.

git switch main 
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

다음으로 새로운 commit인 'commit2'를 만들어보도록 하자.

echo "local commit2" >> ./README.md
git add ./README.md
git commit -m "local commit2"

이제 다음과 같이 된 것이다.

- local repo
                     main    
                       |     
                       v        
commit1 -------> local commit2
   ^   \              
   |    --------> commit2     
   |                 ^
origin/main          |
                origin/test

다음으로 github에 main branch로 새로운 commit2를 추가해보도록 하자.

github repo 접속 -> README.md 클릭 -> 'Edit this file' 클릭 -> 'commit2' 추가

  • README.md
# demo
commit2

맨 아래의 'commit changes' -> 'commit2' 입력 -> 'Commit changes' 클릭

현재 아래와 같은 상황이 된 것이다.

- github
              main
                |
                v
commit1 ---> commit2
      \
       ----> commit2
                ^
                |
               test
-----------------------------------------------------------
- local repo
                  main    
                    |     
                    v        
commit1 -------> local commit2
   ^   \              
   |    --------> commit2     
   |                 ^
origin/main          |
                origin/test

local의 origin/main과 원격지인 githubmain과 서로 다른 commit을 가리기키고 있다. 그런데, 내 local repo에서도 github의 commit2와는 다른 local commit2가 있다.

따라서, 무턱대고 git pull을 해버리면 remote repo에 있는 commit2가 local에 있는 code들을 건드릴 수 있다는 것이다.

git fetch를 통해서 remote github에 있는 commit2를 안전하게 local repository로 이동시키도록 하자.

git fetch 
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.

잘 가져와졌는 지를 확인하기 위해서 origin/main으로 가보도록 하자.

git checkout origin/main 

cat ./README.md
# demo
commit2

문제없이 fetch를 통해서 github에 있는 main branch commit을 가져올 수 있었다.

정리하면 현재의 상황은 다음과 같은 것이다.

- github
              main
                |
                v
commit1 ---> commit2
      \
       ----> commit2
                ^
                |
               test
-----------------------------------------------------------
- local repo
            origin/main
                |
                v          main
        ----> commit2        |
       /                     v
commit1 -------------> local commit2
       \                        
        --------> commit2    
                     ^
                     |
                origin/test

다시 local repo의 main branch로 가서 README.md가 github에 있는 commit2 영향을 받았는 지 아닌지 확인해보도록 하자.

git checkout main

cat ./README.md 
# demo
local commit2

github에서의 commit2가 local에 있는 local commit2에는 영향을 미치지 않는 것을 볼 수 있다.

git pull

git pullgit fetch는 모두 remote repo에 있는 code를 가져온다는 특징이 있지만, 가장 큰 차이점 중 하나는 git pull의 경우 head branch를 업데이트한다는 것이다. 즉, working directory를 바꾼다는 것이다.

따라서, 다음과 같이 정리할 수 있다.

git pull = git fetch + git merge

원격 추적 branch를 git fetch로 local repo에 가져온 다음에 git merge를 통해, working directory에 연결하는 것이다.

사용 방법은 다음과 같다.

git pull <remote> <branch>

가령 git pull origin main이라면 origin에서 main branch를 가져와서 내가 현재 있는 branch에 병합하는 것이다. 만약 main이 아니라 demo에 있었다면 demo branch에 remote repo의 main branch를 가져와 merge시키는 것이다.

github에 새로운 repository인 demo를 만들고, 우리의 local에 다음의 명령어를 입력하도록 하자.

mkdir demo
cd demo
echo "# demo" >> README.md
git init
git add README.md
git commit -m "commit1"
git branch -M main
git remote add origin https://github.com/colt/demo.git
git push -u origin main

작업이 끝났다면, github에 새로운 commit을 추가해보도록 하자.
github repo 접속 -> README.md 클릭 -> 'Edit this file' 클릭 -> 'commit2' 추가

  • README.md
# demo
remote commit2

맨 아래의 'commit changes' -> 'commit2' 입력 -> 'Commit changes' 클릭

우리의 local에는 새로운 commit을 만들어 추가해보도록 하자.

echo "test" > ./test.txt
git add ./test.txt
git commit -m "local commit2"

두번째 commit2가 만들어진 것을 볼 수 있다.

현재까지의 상황을 정리한 것은 다음과 같다.

- github
              main
                |
                v
commit1 ---> commit2
-----------------------------------------------------------
- local repo

                 main
                  |
                  v
commit1 ---> local commit2
    ^
    |
origin/test

이제 git pull origin main을 통해 github에 있는 commit2를 가져와보도록 하자.

git pull origin main
cat ./test.txt 
test

git fetch와 달리 local working directory에 test.txt가 업데이트 된 것을 볼 수 있다. 이렇듯 git pull의 경우 바로 local working directory에 code를 반영한다는 점이 있다.

git pull과 merge conflict

그런데, git pull 시에 local working directory에 바로 반영된다면, 내가 만든 commit과 conflict(충돌)이 발생시킬 수도 있다.

다음의 사례를 확인해보자.

                                main
                                  |
                                  v
github: commit1 --> commit2 --> commit3
                    
     origin/main
           |               
           v         
local:  commit1
           ^
           |
         main

다음과 같은 상황이 있다고 하자. git pull을 하게되면 commit2commit3가 들어오게 된다. 이때 충돌이 발생하개되면, 충돌을 해결하여 그 변경 사항을 기록하는 새로운 commit이 생기게된다.

                                  main
                                   |
                                   v
github: commit1 --> commit2 --> commit3
                        |           |
                        |           |     
                        |           |     
                        v           v    
local:  commit1 --> commit2 --> commit3 --> commit4
           ^                                   ^
           |                                   |
     origin/main                              main

가령 다음의 경우에서 commit2commit3이 들어왔을 때 merge에서 conflict가 발생했다면 conflict에 대한 수정이 필요하다. 이 수정 사항에 대해 반영된 결과가 바로 commit4인 것이다.

이제 충돌 상황을 만들어보도록 하자. 먼저 github로 가서 'demo' repository를 만들자. 'demo' repository를 만들었다면, 다음의 code를 입력하여 local repo를 만들도록 하자.

mkdir ./demo
cd ./demo
echo "# demo" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/colt/demo.git
git push -u origin main

다음으로 local working directory에서 'commit2'를 만들어보도록 하자.

echo "local commit2" >> ./README.md
git add ./README.md
git commit -m "add local commit2" 

github로 가서 README.md를 수정해주도록 하자.
github repo 접속 -> README.md 클릭 -> 'Edit this file' 클릭 -> 'remote commit2' 추가

  • README.md
# demo
remote commit2

맨 아래의 'commit changes' -> 'remote commit2' 입력 -> 'Commit changes' 클릭

remote commit2가 생성되었다면, 이제 git pull을 통해서 local working directory로 가져와보도록 하자.

git pull origin main
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 348 bytes | 348.00 KiB/s, done.
From https://github.com/colt/demo
 * branch            main       -> FETCH_HEAD
   f238289..6db8d73  main       -> origin/main
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

다음과 같이 된다. 이는 automerge에 실패했다는 것을 나타낸다. README.md로 가보도록 하자.

  • README.md
# demo
<<<<<<< HEAD
local commit2
=======
remote commit2
>>>>>>> 6db8d733ec7281265132df8af4fdf98a3af73fcd

둘 중 하나를 선택하거나, 둘 다 선택하거나, 둘 다 선택하지 않거나해야한다. 우리의 경우 둘 다 있도록 설정해주자.

  • README.md
# demo
local commit2
remote commit2

해당 merge에 대한 변경사항을 commit을 통해 반영해주어야 한다. 따라서, 다음의 명령어가 필요하다.

git add ./README.md
git commit -m "merge commit3"

merge commit을 만든 것이다. 해당 동작을 그려보면 다음과 같다.

                        main
                         |
                         v
github: commit1 --> remote commit2
                         |
                         |     
                         |
                         v
local:  commit1 --> local commit2 --> merge commit3
           ^                               ^
           |                               |
    origin/main                           main

아주 이쁘게 merge commit3가 만들어진 것을 볼 수 있다.

git pull의 동작을 정리하면 다음과 같다.
1. git fetch로 remote repo의 변경 사항을 local repository로 가져오기 (local working directory에는 반영X)
2. git merge로 local working directory와 local repository의 diff에 대해서 합병 시도
2-1. 충돌이 발생했다면 merge 작업을 수동으로 해준 뒤에 merge commit을 작성
2-2. commit을 remote repo에 push
3. 끝 (충돌이 없었다면 merge commit을 만들 지 않아도 된다.)

즉, git pull은 remote repo에 있는 변경 사항을 현재 head branch에 병합을 시킨다는 것이다.

0개의 댓글