공동작업용 중앙 저장소 생성. 우선 공동작업용 폴더를 하나 만들었다. 그리고 그 안에서 –bare 옵션을 주고 git을 초기화하여 repository를 생성한다. –bare옵션을 주면 로컬작업이 안되고 git repository 자체만 존재한다.
~/git_test> mkdir remote_mp.git
~/git_test> cd remote_mp.git
~/g/remote_mp.git> git init --bare
/home/batmask/git_test/remote_mp.git/ 안의 빈 깃 저장소를 다시 초기화했습니다
~/g/remote_mp.git ls
HEAD config description hooks info objects refs
–bare 옵션으로 공동 작업용 git repository를 만드는 경우, 폴더명을 “.git”으로 끝내는게 관례라고 한다. 초기화 후, 파일들을 보면, git init 로 생성했을 때 보이는 “.git” 숨김폴더가 생성되지 않고 현재위치에 .git폴더로 들어가던 파일과 디렉토리들이 자리잡은걸 볼 수 있다.
git remote
위에서 생성한 git repository를 공동작업하는 remote repository로 사용할 것이다. 이렇게 하기 위해선 새로 생성한 git repository를 원격 repository로 등록해야 한다. 이전에 작업하던 myproject 디렉토리로 이동한 후, git remote add 명령어를 사용해보자.
git remote add <remote name> <remote url>
-----------------------------------------
~/g/myproject │ main> git remote add origin ~/git_test/remote_mp.git
~/g/myproject │ main> git remote -v
origin /home/batmask/git_test/remote_mp.git (fetch)
origin /home/batmask/git_test/remote_mp.git (push)git remote add로 새로 생성했던 remote_mp.git을 origin으로 remote에 추가했다. 위의 내용처럼 git remote -v 를 쓰면 현재 설정된 remote repository 들의 리스트를 볼 수 있다. 여기서 사용한 “origin”은 remote repository를 다루기 쉽게 대체하는 일종의 닉네임이다. 관례적인 이름이고, 다른 이름을 사용 할수도 있다.
git push
이제 작업중이던 소스를 remote repository에 올려보자. 내 소스 또는 변경사항을 remote repository에 올리는 방법은 git push 를 사용한다.
git push <remote> <branch>~/g/myproject │ main> git push -u origin main
오브젝트 나열하는 중: 42, 완료.
오브젝트 개수 세는 중: 100% (42/42), 완료.
Delta compression using up to 12 threads
오브젝트 압축하는 중: 100% (41/41), 완료.
오브젝트 쓰는 중: 100% (42/42), 3.91 KiB | 1.30 MiB/s, 완료.
Total 42 (delta 12), reused 0 (delta 0), pack-reused 0 (from 0)
To /home/batmask/git_test/remote_mp.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
이렇게 하면, 로컬의 main브랜치가 remote repository인 origin에 main 브랜치를 만들어 올라간다. 이 때, remote의 브랜치는 “origin/main” 으로 쓰인다.
-u 옵션은 “–set-upstream” 과 동일한 옵션으로 이렇게 설정해 놓으면, main 브랜치와 origin/main을 git이 연결해서 서로 추적(tracking)하는 상태로 만든다. 이렇게 되면, push나 pull 등을 쓸 때, 특별히 지정하지 않아도 tracking하는 대상을 찾아 자동으로 동작하게 된다.
헷갈리지 않게 한단계식 살펴보자. 일단, git log를 origin에 대해 써보면 다음과 같다.
~/g/myproject │ main> git log origin/main --oneline --graph
* 59513b6 (HEAD -> main, origin/main, animal) add cat sound
* 1d9d3e7 conflict resolved
* b7ae4b0 add love and ask func.
* a83283b (new_feature) conflict resolved
|\
| * 78f1da3 test added 'main changed'
| * fac78e4 new-feature is merged to main
| |\
| * | d19622e (testing) add call() to saying.py
* | | ca5c7c8 text added 'new_feature changed'
| |/
|/|
* | 5737e57 add shout() to saying.py
|/
* 650a1e6 delete dont_needed_anymore.txt
* b502882 add file maybe dont needed anymore later
* a67ee7e add more text to readme.txt
* 046e349 add sleep function
* 0d52a66 the first commit.
origin/main은 remote repository의 브랜치를 나타내는 표현이다. origin에서 main 브랜치에 대한 로그를 보겠다는 얘기. 흥미로운 점은 main을 제외한 다른 브랜치들은 표시가 안되는데, 내가 로컬에서 브랜치를 따서 사용한 모든 commit들이 표시되는걸 알 수 있다. ‘나는 main 브랜치만 올린거 같은데?’ 라고 생각할 수 있는데, main과 연관된 모든 commit들이 올라가게 된다. 브랜치의 commit들도 main에 merge가 됐기 때문에 전부 올라가지만, 거기에 해당하는 포인터인 branch들은 올라가지 않기 때문에 표시되지 않는다. 다르게 말하면, merge되지 않은 별도의 branch에 해당하는 commit들은 올라가지 않는다.
branch를 remote로 올리려면 main에서 해줬던 것과 비슷하게 다음과 같이 쓴다.
git push -u <remote repo> <local branch name>:<remote branch name>
git push -u origin testing
git push -u origin testing:my_testbranch를 올리면서 local과 remote를 다른이름을 쓸 수 있는데, 위처럼 branch 이름뒤에 ” : “을 쓰고 remote의 branch 이름을 쓰면 된다.
이렇게 branch를 하나씩 올리기 힘들땐, 한번에 올리는 방법이 있다.
git push --all origin이렇게 하면, 모든 브랜치와 태그까지 전부 올라간다. 태그만 올리고 싶을 땐 다음을 쓴다.
git push --tags origin이렇게 올리다보면, 제대로 올라갔는지 remote에 어떤 branch가 존재하는지 궁금할 것이다. git branch 에 ‘-r’ 옵션을 쓰면 remote의 브랜치들을 보여준다. 또는 git ls-remote 명령어를 써도 된다.
~/g/myproject │ main> git branch -r
origin/main
origin/new_feature
~/g/myproject │ main> git ls-remote
From /home/batmask/git_test/remote_mp.git
59513b6014b03230e628f24212837388b88f4ef3 HEAD
59513b6014b03230e628f24212837388b88f4ef3 refs/heads/main
e7b2cd791c6bfad6eebe1fd239f2caa4dff40d96 refs/heads/new_featureremote branch들은 “origin/main” 과 같이 remote의 이름이 붙는다. ls-remote를 했을 때 결과는 좀 다른데, commit 해시가 같이 출력되는걸 볼 수 있다.
git clone
이제 내가 다른 사용자라고 생각해보자. 공유 repository를 remote로 해서 소스를 받아오고 싶을 것이다. 이 때 사용하는게 git clone이다.
지금 여기서는 혼자 테스트중이기 때문에 상위디렉토리로 이동하여 거기서 clone을 해보겠다.
git clone <url> <workdir>
----------------------------------
~/g/myproject │ main> cd ..
~/git_test git clone ~/git_test/remote_mp.git user2_mp
'user2_mp'에 복제합니다...
완료.
~/git_test> cd user2_mp
~/g/user2_mp │ main> ls -la
합계 20
drwxrwxr-x 3 batmask batmask 4096 5월 2 20:16 .
drwxrwxr-x 6 batmask batmask 4096 5월 2 20:12 ..
drwxrwxr-x 7 batmask batmask 4096 5월 2 20:17 .git
-rw-rw-r-- 1 batmask batmask 142 5월 2 20:16 readme.txt
-rw-rw-r-- 1 batmask batmask 511 5월 2 20:16 sayings.py
상위 디렉토리로 올라가서 remote git 에 있는걸 user2_mp 디렉토리에 복제하도록 했다. 여기서 workdir 은 생략 가능한데, 그런경우 remote의 git 폴더에서 “.git”을 제외한 이름으로 폴더가 생성된다. 여기서라면 “remote_mp”라고 생성된다.
복제된 폴더에 들어가보면, .git을 포함하여 모든 내용이 들어와 있음을 알 수 있다. 또한, push할 때 설정하던 “–set-upstream”같은 옵션을 따로 지정하지 않아도 자동으로 local-remote branch간 tracking이 이루어진다.
git fetch / pull
중앙 repository는 여러사람이 공동작업으로 계속 수정이 되기 때문에, 내 수정사항을 올리는 전에 다른사람들이 변경한 내용을 먼저 받아와 내 local repository에 반영한 후 올려야한다. 이렇게 local repository에서 remote의 수정사항을 내 수정사항과 merge한 후, git push로 올려야 remote에서 발생하는 충돌을 피할 수 있다. 이 때, remote의 내용을 받아오는 명령어가 git pull이다. 실제로, remote에 변경사항이 있는채로 git push를 시도하면 에러가 발생하며 막힐 것이다.
테스트를 위해서 먼저 처음 작업하던 myproject 폴더에서 수정사항을 만들어 remote에 올려보자. myproject 폴더에서 readme.txt에 다음 텍스트를 추가한다.
...
Let's modify remote
...해당 내용을 commit한 후, git push로 remote에도 반영하자.
~/g/myproject │ main !1> git commit -am "Let's modify remote"
[main 82761a2] "Let's modify remote"
1 file changed, 2 insertions(+)
~/g/myproject │ main ⇡1> git push
오브젝트 나열하는 중: 5, 완료.
오브젝트 개수 세는 중: 100% (5/5), 완료.
Delta compression using up to 12 threads
오브젝트 압축하는 중: 100% (3/3), 완료.
오브젝트 쓰는 중: 100% (3/3), 329 bytes | 329.00 KiB/s, 완료.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /home/batmask/git_test/remote_mp.git
59513b6..82761a2 main -> main
처음에 git push를 할 때 -u 옵션을 써서 upstream이 설정되어 있기 때문에 여기서는 그냥 git push 만 써도 알아서 작동한다. 출력되는 메세지들을 보면, remote로 올리는 과정에서 변경내용인 Delta를 생성하고, 압축하는등 여러 작업이 진행되는걸 볼 수 있다. 이제 user2_mp 폴더로 옮겨 이를 받아와보자.
git pull <remote> <remote branch>보통은 이미 현재 local branch에 해당하는 remote branch가 tracking 상태로 연결되어 있기 때문에, 뒤의 옵션 없이 그냥 git pull이면 충분하다. 다만, 큰 오해의 소지가 있는데 git pull은 어디까지나 “현재의 local branch”에 remote의 branch를 가져오는 방법이라는 점이다. 명령어가 <remote> <branch> 형식으로 되어있어 git push 와 동일해 보이나, git push에서 명시하는 <branch> 는 local branch를 말하고, 여기 git pull에서는 remote branch를 말한다. 다시 말해 git pull 은 무조건 현재 HEAD가 위치하는 브랜치 기준으로, local branch는 명시할 필요가 없다.
이제, 받아와 보자.
~/g/user2_mp │ main> git pull
remote: 오브젝트 나열하는 중: 5, 완료.
remote: 오브젝트 개수 세는 중: 100% (5/5), 완료.
remote: 오브젝트 압축하는 중: 100% (3/3), 완료.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
오브젝트 묶음 푸는 중: 100% (3/3), 309 bytes | 309.00 KiB/s, 완료.
/home/batmask/git_test/remote_mp URL에서
59513b6..82761a2 main -> origin/main
업데이트 중 59513b6..82761a2
Fast-forward
readme.txt | 2 ++
1 file changed, 2 insertions(+)
remote_mp에서 받아와 Fast-forward merge가 됐다는 로그가 보인다.
이처럼 ff merge가 된다면 너무 좋겠지만, git pull을 하다가 충돌이 생기면 어떻게 될까? 양쪽에서 같은 부분을 수정해서 충돌을 발생시켜놓고 어떻게 되는지 알아보자. myproject 디렉토리에서 다음과 같이 readme.txt를 수정한 후, remote에 올렸다.
myproject changed. How can I handle conflict?
~/g/myproject │ main !1> git commit -am "myproj changed"
[main c3db55b] myproj changed
~/g/myproject │ main ⇡1> git push
오브젝트 나열하는 중: 5, 완료.
오브젝트 개수 세는 중: 100% (5/5), 완료.
Delta compression using up to 12 threads
오브젝트 압축하는 중: 100% (3/3), 완료.
오브젝트 쓰는 중: 100% (3/3), 347 bytes | 347.00 KiB/s, 완료.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /home/batmask/git_test/remote_mp.git
82761a2..c3db55b main -> main
이제 user2_mp에서도 readme.txt에 마지막줄에 다음과 같이 추가했다.
user2_mp changed to make conflict!
commit을 하고, push전에 remote의 내용 받아오도록 git pull을 시도해 보자.
~/g/user2_mp │ main !1> git commit -am "user2_mp changed"
[main 505b455] user2_mp changed
1 file changed, 1 insertion(+)
~/g/user2_mp │ main ⇡1> git pull
remote: 오브젝트 나열하는 중: 5, 완료.
remote: 오브젝트 개수 세는 중: 100% (5/5), 완료.
remote: 오브젝트 압축하는 중: 100% (3/3), 완료.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
오브젝트 묶음 푸는 중: 100% (3/3), 327 bytes | 327.00 KiB/s, 완료.
/home/batmask/git_test/remote_mp URL에서
82761a2..c3db55b main -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.길게 써있는데 에러가 발생했고 어떻게 처리해야 할지에 대한 설명이다. 에러 내용은 “divergent branches”, 즉 갈라진 브랜치라는건데, myproject에서 생성한 commit이 있고 user2_mp에서 생성한 commit이 존재해서 기존 base에서 두 브랜치가 갈라진 형태이기 때문이다.

해결전략은 merge, rebase, fast-forward only 3가지로 나온다. 자동으로 어떤 전략을 택할지 git config를 이용해 설정해 놓을 수 있다고 안내하고 있다. rebase나 merge는 수동으로 작업을 해야할거 같지만, fast-forward조차 처리가 안되는건 좀 문제가 있어 보인다. 그러니 기본적으로 ff only 설정을 해놓자.
git config pull.ff only설정을 바꿨으면, 다시 원래 문제로 돌아가자. ff only설정을 하고 git pull을 다시 시도한다고 해도, 어차피 충돌이 생겨서 fast-forward는 진행이 안되는 상황이다. 여기서 잠깐, git pull 이란건 사실 두가지 명령어의 복합 명령어다. 바로, git fetch + git merge(ff only) 라고 볼 수 있다. git fetch라는건 remote repository에서 local repository로 변경사항을 update해서 가져오는 명령어다. 가져와서 어디에 저장이 되는건가 싶은데, 우리가 “origin/main”, “orign/new_feature”와 같이 remote에 생성했던 브랜치들은 실제로 local repository에 브랜치처럼 존재한다.

vscode 에서 git graph를 이용해 보면, 위와같이 나오는데, origin/HEAD, origin/main, origin/new_feature 와 같이 remote에 대한 브랜치들이 보인다. 실제로는 이 브랜치들이 remote와 동기화가 되고, main에서는 이 동기화된 branch와 merge를 하든, rebase를 하든 fast-forward merge를 하든 작업이 되는 것이다. 그리고, 이 origin 브랜치들이 실제 remote repository로부터 데이터를 가져와 update되도록 하는 명령이 git fetch이다.
간단히 얘기해서 git pull을 실행하는 대신, git fetch 후에 origin 브랜치와 직접 merge나 rebase를 진행해도 된다는 얘기. 단, 주의할건 rebase인데, 공유된 commit을 rebase하는건 금지된다.
여기선 간단히 merge를 진행해 보겠다. merge를 하기전에, remote 브랜치를 말하는 origin/branch들이 그냥 브랜치나 동일하다고 했으므로, diff를 수행할 수 있다.
~/g/user2_mp │ main ⇣1⇡1> git diff HEAD origin/main
diff --git a/readme.txt b/readme.txt
index 8f0c1a4..25e103d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -9,5 +9,5 @@ new_feature has changed
Let's dance on sage!
Let's modify remote
-user2_mp changed to make conflict!
+myproject changed. How can I handle conflict?
양쪽에서 추가한 내용이 충돌한 것이 보인다. merge 를 해보자.
~/g/user2_mp │ main ⇣1⇡1> git merge origin/main
자동 병합: readme.txt
충돌 (내용): readme.txt에 병합 충돌
자동 병합이 실패했습니다. 충돌을 바로잡고 결과물을 커밋하십시오.충돌이 발생했다. readme.txt 파일을 보면 다음과 같다.
<<<<<<< HEAD
user2_mp changed to make conflict!
=======
myproject changed. How can I handle conflict?
>>>>>>> origin/main
파일을 편집해서 충돌을 해결하자. 양쪽의 수정사항을 둘 다 적용했다.
user2_mp changed to make conflict!
myproject changed. How can I handle conflict?
commit을 해주자.
~/g/user2_mp │ main ⇣1⇡1 merge ~1> git commit -a -m "remote conflict has resolved"
[main 7218e18] remote conflict has resolved이제 git log를 살펴보면,

origin/main 과 병합이되서 새 commit이 생겨났다. remote의 내용을 local에 반영했으니, 이제 이 병합된 내용을 다시 remote에 올려보자.
~/g/user2_mp │ main ⇡2> git push
오브젝트 나열하는 중: 10, 완료.
오브젝트 개수 세는 중: 100% (10/10), 완료.
Delta compression using up to 12 threads
오브젝트 압축하는 중: 100% (6/6), 완료.
오브젝트 쓰는 중: 100% (6/6), 669 bytes | 669.00 KiB/s, 완료.
Total 6 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
To /home/batmask/git_test/remote_mp.git
c3db55b..7218e18 main -> main
마지막으로 myproject에서 한 번 살펴보자. 위치를 myproject로 옮긴 후, git fetch를 해보자.
~/g/myproject │ main> git fetch
remote: 오브젝트 나열하는 중: 10, 완료.
remote: 오브젝트 개수 세는 중: 100% (10/10), 완료.
remote: 오브젝트 압축하는 중: 100% (6/6), 완료.
remote: Total 6 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
오브젝트 묶음 푸는 중: 100% (6/6), 649 bytes | 649.00 KiB/s, 완료.
/home/batmask/git_test/remote_mp URL에서
c3db55b..7218e18 main -> origin/main
origin/main 이 업데이트 되었음이 보인다. 기존 변경사항을 그대로 적용했으므로 git pull을 시도해보자.
~/g/myproject │ main ⇣3> git pull
업데이트 중 c3db55b..bd63cc8
Fast-forward
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
자연스럽게 fast-forward 병합이 이루어진게 보인다. 이렇게해서 push, pull, fetch와 함께 origin 브랜치들을 어떻게 다뤄야 할지 알아봤다.