본문 바로가기
git

2021.01.21 git fork vs clone

by 해맑은 코린이 2021. 1. 21.

2021.01.21_ git fork vs clone _정리노트

 

ㅎㅇㅎㅇ 오늘은 프로젝트 협업 시에 유용한 명령어인 fork를 나혼자 정리하고, clone이랑 비교해서 한번 정리해보려고 함.

 

먼저 fork와 clone의 차이를 크게 적게씀.

 

clone 

원본 레포지토리의 내용을 내 로컬 레포지토리로 완전히 복사한다. 연결된 레포지토리는 로컬에서 remote로 연결된 origin 이다. 

clone 한 프로젝트는 원본 레포지토리의 로그를 볼 수 없다. 권한이 없는 경우, 내 로컬의 변경 사항을 원본 레포지토리로 push 또한 할 수 없다. -- 소규모 팀작업에서는 활용하기에 좋을 수 있음.

만약 협업 중 각각 클론한 프로젝트에서 변경 사항을 적용하고 싶을 때, 상대방이 먼저 push ( 권한이 있으면 가능하다. ) 를 하게 되면, push 를 할 때 마다 fetch 를 수행한 후 push 를 해야한다는 번거로움이 있다. 

 

fork 

다른 사람의 레포지토리에서 어떤 부분을 수정하거나, 추가 기능을 넣고 싶을 때 해당 레포지토리를 내 레포지토리로 그대로 복제.

하지만 fork 한 저장소는 원본 레포지토리 ( 내가 연결한 레포지토리 ) 와 연결되어 있다. 

내 로컬 저장소와 연결 되어 있지 않음. 

만약 원본 레포지토리에 새로운 변경 사항인 commit 이 생기면, 포크된 레포지토리 ( 내가 지금 현재 사용 중인 로컬에 있는 포크된 레포지토리 ) 로 반영할 수 있는데, 이 때에는 fetch, rebase 등의 과정이 필요하다.

또한, 내 로컬 저장소에 있는 변경 사항을 원본 레포지토리에 적용도 가능하다. 

pull request 를 하면 되는데, 이 때 해당 원본 관리자로부터의 승인이 있어야 하고, 관리자가 승인하게 되면, 내가 만든 코드가 commit, merge 되어 원본 레포지토리에 반영 되게 된다. 풀리퀘를 하기 전에는 내가 포크한 레포지토리에만 변경 사항이 적용 된다. 

 

 

여기서 fetch 와 rebase라는 새로운 개념이 등장하는데, 그것도 자세히 설명 ㄱㄱ

 

 

 

 

그 전에, 우리가 프로젝트를 생성하고 작업을 진행할 때의 일반적인 흐름을 살펴보면,

 

 

 

우리가 보통 브랜치를 새로 생성하고 ( checkout 으로 생성 ) pull request 를 하게 되면, 각각의 작업 브랜치들이 나뭇가지처럼 뻗어나가게 된다.  

이 때 작업할 브랜치 생성과 checkout ( 작업할 브랜치로의 이동 ) 을 한꺼번에 하려면, 

git checkout -b 브랜치 이름

으로 한꺼번에 가능하며, 이렇게 작업할 브랜치에서 계속 커밋하게 되면, 이 작업 브랜치는 master 와 다르게 가지를 그리며 뻗어나간다. 

이렇게 각자의 브랜치에서 작업하고, 최종으로 배포하기 위해서는 보통 master 브랜치에 모두 합치는 작업을 한다. 

그러면 우리는 master 브랜치로 checkout 한 다음,

git merge 머지할 브랜치 이름

으로 머지하게 되면, 최종 master 에 병합 되게 되면서, 새로운 머지 커밋이 생성된다. 

 

하지만, 만약 같은 master 선상의 브랜치에서 작업을 하고 merge를 하게 되면, 

 

 

출처 - 깃 공식문서
출처 - git 공식 문서

 

"Fast-forward" 라는 명령어가 뜨면서 이렇게 hotfix 라는 브랜치의 최근 커밋 내역에 master 브랜치가 그냥 이동이 되게 된다. 새로운 머지 커밋을 생성하지 않는다는 말이다. 

 

 

내가 위에서 설명한 새로운 머지 커밋이 생성된다는 경우는, 위 그림에서 iss53 브랜치인 경우이다. 

 

출처 - 깃 공식 문서
출처 - 깃 공식 문서

 

 

이렇게..! master 브랜치와 다른 줄기를 뻗어서 작업한 경우는 , merge 를 시행하게 되면, 둘이 병합된, C6 이라는 새로운 머지 커밋을 생성하게 된다. 이 방법을 3-way Merge 라고 한다. 

이 경우는 같은 곳을 수정한 경우, 머지를 할 때 깃 컨플릭트를 발생 시키며, 커밋은 자동으로 중지 된다. 

이럴 경우에는 수동으로 컨플릭트를 수정 후 다시 커밋하게 되면 정상적으로 merge 된다!_!

 

 

 

뭔가 git 이라는게.. 이게 넘모 나에게는 어려운 개념이라 자꾸 추가적으로 무언가를 공부하기 전에 그 전 단계로 올라가서 복잡하긴..한데 머지도 정말 아무것도 모르고 한 거 같아서 먼저 정리를 해봤다. 

쨋든 나는 일반적으로 merge를 할 때 정말 아무생각 없이 했음을.. 깨닫.. 

 

 

자 쨋든 내가 merge 에 대해서 왜 설명했을까?

 

바로 내가 설명할 rebase와 비교 되는 개념이기 때문!!!

 

rebase 

merge는 보통 두 브랜치가 줄기로 나누어 졌다가 다시 하나의 줄기로 합쳐지는 것이라면, rebase를 먼저 시행하고, merge를 하면, 이력 로그 자체가 하나의 줄기로 만들 수 있기 때문!

또한, rebase 명령으로 한 브랜치에서 변경된 사항을 다른 브랜치에 적용 시킬 수도 있다. 

 

출처 - 깃 공식문서

 

위 그림은 experiment 브랜치를 master 에 병합했을 때의 3-way Merge 된 그림이다. 

하지만 만약 머지하기전에 experiment 에서 rebase master 를 실행하게 되면 ? 

git checkout experiment

git rebase master

 

출처 - 깃 공식 문서

 

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

 

 

이렇게 하고 다시 master 로 checkout 하고 experiment를 merge 시키면..!

 

 

 

그렇게 되면  Fast-forward를 실행하여, master 가 C4' 로 이동하게 된다. 

 

이렇게 어려운데 결과적으로는 merge와 합치는 관점에서 보면 달라진게 없다.

그럼 왜 rebase를 할까..?

 

하지만 rebase가 훨씬 깔끔한 선형 히스토리를 생성한다. 우리가 일을 할 때 줄기 줄기 뻗어서 작업을 해도, 모든 작업들을 rebase를 하고 나서는, 모든 작업이 차례대로 일렬로 수행된 것처럼 보이기 때문이다.

 

히스토리만 달라진다고 생각하면 안된다. 우리는 나중에 변경 사항의 히스토리를 볼 때 훨씬 일의 진행 사항이 깔끔해보이기 때문이다. 

 

 

rebase 의 경우는 브랜치의 변경사항을 순서대로 다른 브랜치에 적용하면서 합치고 merge 의 경우는 두 브랜치의 최종결과만을 가지고 합친다.

 

 

또한 단순히 히스토리를 변경하는 기능 말고도, 다른 브랜치의 변경 사항을 master 브랜치로 합칠 수 있는 기능도 있다.

후 .. 복잡하지만 차근차근 살펴보면,

 

출처 - 깃 공식 문서

 

만약에 C3 에서 갈라진 server 브랜치와 client 브랜치 가 있다고 가정할 때, master에 server 말고, client 만 합치고 싶을 때, 즉 여기서 server 와는 관련이 없는 커밋은 C8, C9 인 이 커밋들을  우리는 master에 적용시키고 싶을 때! 이 때 ! rebase 를 사용한다. 

 

명령어는

 

git rebase --onto master server client

 

--onto 명령어를 사용하게 되면, 

 

출처 - 깃 공식 문서

 

 

어우.. 복잡하균... 쨋든 이렇게 C8, C9 만 master 브랜치와 일렬 구조로 적용하게 된다. 

자 이제 그럼 master를 client 가 있는 곳으로 이동 시켜야겠지!! 그럴 땐 fast-forward merge 가 되게 된다. 

 

git checkout master

git merge client

 

이제 머지하자~!

 

출처 - 깃 공식 문서

 

그럼 master 브랜치가 짠 하고 client 있는 곳으로 이동하게 된다. 

 

이제는 server 가 남았다. 얘는 따로 이동할 필요 없이,

 

git rebase <basebranch> <topicbranch>

 

즉 여기서는 , 

 

git rebase master server

 

이런 명령을 통해 바로 rebase 가 가능하다.

 

출처 - 깃 공식 문서

 

 

이렇게!!!! rebase 가 되게 된다. 이제 또 master 이동!!!!! 하려면 어떻게 !!!!!

 

git checkout master

git merge server

 

마지막으로......머지......하자 흐어... 그렇게 되면 다시 fast-forward merge 가 되고, 작업이 다 끝났으니 client, server 브랜치를 

 

git branch -d client 

git branch -d server

 

해당 명령어를 통해 지워주게 되면, 

 

출처 - 깃 공식문서

 

 

짞짞짝자ㅏㅈㄱ ... 매우 매우 깔끔해졌다.

 

rebase 할 때에는 한 가지를 주의해야한다.

 

이미 공개 저장소에 Push 한 커밋을 rebase 하지 말 것 !!

 

이 것에 대한 자세한 설명은 깃 공식 문서 에 있음... 내용 조차 너무 혼란스럽고, 너무 복잡해서.. 으어 이거에 대해선 좀 더 공부를 해야할 듯 싶다.. 해결 방법 또한 강제로 rebase 한 것을 다시 rebase로 돌려 놓는다니.. 너무 어렵다. 

지금까지 내 개인 프로젝트에는 히스토리 충돌이 날 때 아무생각 없이 히스토리를 강제로 병합시켜서 했었는데, 팀의 경우 매우 위험한 것 같다. 

 

그러니 애초에 pull 을 할 때  git pull --rebase 이런식으로 하는 것이 미연에 방지를 할 수 있는 방법이다. 

 

일단 최대한 이해한만큼 정리하자면,


내 브랜치에서 작업을 하고, 다른 사람도 내 브랜치를 pull 받아서 작업을 한다고 했을 때, 내가 혼자 이것저것 뚝딱뚝딱하다가 멋대로 rebase를 해서 말그대로 갑자기 자라나던 가지를 잘라버리고, master로 base 를 바꿔버리면 다른 사람이 작업하던 내 브랜치는 그대로 가지가 남아있는데,

나는 이미 바뀌어진 베이스를 가진 브랜치로 push를 해버렸으니, 다른 사람이 작업하던 내 브랜치의 base와 바뀌어버린 내 base가 충돌하는 상황이 벌어진다.

그렇게 되면, 깃에서는 기본적으로 base 가 다른 push 는 병합을 거부하기 때문에,

모두가 나의 rebase로 인해 수동으로 병합을 해서 다시 pull 해야 하는 번거로운 상황이 벌어진다.


 

 

으어 .... 역시 깃은 지옥에서 온 것이 분명해........

 

 

 

최종으로 비교해서 정리하자면, rebase 는 merge 와 비슷한 기능을 하는데, 말 뜻 그대로 베이스를 다시 배치한다는 느낌으로, 그 베이스를 master와 같은 선상으로 일치시켜 히스토리를 깔끔하게 정리하는 느낌으로 가져가고, 머지는 과정이 간단한 대신에 히스토리가 더러워진다는 느낌으로 가져가보았다...! 

 

 

 

그래서 협업 중 충돌을 막기 위해서, 최종적으로 히스토리를 일괄적으로 정리할 때, rebase를 사용하고,

merge 옵션 중, squash and merge 라는 옵션으로 실행하기도 하는데, 

이 옵션은 또 지금까지 내가 한 커밋들을 하나로 합친 후 merge 된다고 한다. 하지만 메인 브랜치와의 관계는 전혀 상관없는 거라고 하니, 브랜치들간의 관계를 잘 생각해서 그때 그때 다르게 사용하면 될 것 같다.

 

 

 

 

 

흐어............ 이제 fetch로 간다. 

 

fetch

는 pull 과 비교되는 개념으로 이해하면 될 것 같다. 

pull 은 fetch 와 merge를 합친 개념으로, 

이 때 fetch 는 원본 저장소의 데이터를 로컬에 가져오기만 하는 것이다. 

pull 을 작동하게 되면, 데이터를 가져와 원본 저장소와 자동으로 합치게 되는데, 데이터를 가져오는 과정이 fetch인 것이고, fetch를 실행하게 되면, 원본 저장소의 최신 커밋 이력인 로그를 볼 수 있다. 

이 때, 가져온 최신 커밋 이력은 이름 없는 브랜치로 로컬 데이터에 가져오게 되고, 이 이름 없는 브랜치는 'FETCH_HEAD' 라는 이름으로 checkout 할 수가 있다. 

이를 진행하고, 완벽하게 내 로컬 저장소로 들고와서 합치고 싶다면, pull 이나 FETCH_HEAD branch 를 merge 를 실행하면, 내 로컬 저장소에 반영 된다. 

 

 

fetch 는 앞에꺼 공부하다 보니 비교적 이해하기 쉬운듯... ? 말그대로 원본 저장소의 커밋 이력 보고 싶으면 fetch 를 수

행하여 커밋 이력을 보고 , 만약 코드의 차이점을 보고 싶으면, 

 

git diff HEAD origin/master

 

라는 명령어로 지금 현재 내 로컬 브랜치의 HEAD ( 내 로컬 저장소의 master ) 와 원본 저장소의 origin/master 간의 차이를 볼 수 있다. 

 

이렇게 본 후에 이제 내 로컬 저장소로 불러와도 되겠다 ~ 하면 pull 이나 FETCH_HEAD branch 를 merge 를 수행하면 되는 것이다. 

 

pull 사용하는 것은 코드의 변경 사항을 로컬 저장소로 바로 가져오므로, 신중히 고려할 일이 생기면 fetch를 통해 미리 변경사항을 확인하고 들고오면 될 것 같다.

 

 

 


 

 

이렇게 간만에 git 포스팅을 했는데... 혼란하다 혼란해 정리하고 공부하면서도 rebase 에 대해서는 아직 완벽히 이해하진 못한 것 같아, 쓰면서 많이 익혀야 할 것 같다. 나머지는 협업시에 참고할만 한 것들이 많아서 앞으로 잘 써봐야겠음!! 

오늘의 포스팅 끝끝

 

'git' 카테고리의 다른 글

2020.10.14_Django Secret Key  (0) 2020.10.14
2020.10.11_git ignore 생성  (0) 2020.10.12
2020.09.26_git pull request  (0) 2020.09.26
2020.09.13_git_merge  (0) 2020.09.14
2020.08.29_git 레포지토리 생성 및_ 기초  (2) 2020.08.30

댓글