꺼내먹는지식 준

GITHUB 협업1 기본기, conflict, branch 관리 본문

CS/깃헙

GITHUB 협업1 기본기, conflict, branch 관리

알 수 없는 사용자 2022. 3. 17. 19:24

협업을 위한 Git 사용법을 총 정리해보자. 

시작전 한가지 명심할 것,

git 이 어려운게 아니라 실제로 굉장히 수행하기 어려운 걸 git 을 통해 관리하기 때문에 git이 어렵게 느껴지는 것 

git은 엄청난 도구 

 

다음의 내용을 정리한다. 

1) 서론 

2) vscode 깃 사용법 

3) 협업중 발생하는 문제 (push conflict)

4) Head Master 

5) Time Machine

천천히 따라오면 모든 부분을 이해할 만 할 것이다. 

 

보통 프로젝트마다 git repository를 하나씩 만들어서 협업을 한다. 하나 만들고 시작하자. 

 

서론 

git 이 어려워서 dropbox 를 사용하곤 하지만, backup 이상의 역할이 없다. 

git은 upload와 동시에 기록이 남아서, 버전을 체계적으로 관리할 수 있다. 

 

버전을 체계적으로 관리한다는 것은 하나의 프로젝트에 대하여 타인이 수정한 내역을 내가 가지고 있는 데이터에 빠르게 반영하고, 동시에 나도 수정을 해서 바로 공유 한다는 것이다. 

 

파일은 원격 저장소(github.com)에 저장하여 함부로 삭제될 일 없도록 방지한다. 

github.com에서 upload 버튼을 클릭해서 파일 업로드 하는 것도 가능하다. 

파일이 업로드 되고, 각 업로드 시점의 snap shot 이 발생(버전 기록을 위한 commit)한다. 

다만, github.com에 들어가 직접 파일을 올리고 commit하는 경우는 없다. 들어가고 파일을 따로 올리고 올려진 파일 확인하고 하는 과정이 번거로울 뿐더러 각종 기능 사용이 불가능하다. 

 

상기에 언급된 과정들을 local computer에서 편리하게 관리할 수 있다. 

 


visual studio code git 사용

vs code는 git 을 사용하기 쉽도록 강력하게 연결되어 있다. 

지금부터 vs code에서 git 을 간단하게 사용하는 방법에 대해 논의해본다. 

(물론 CLI 을 통해 git을 더 편리하게 관리하는 법도 있지만, 여기서는 논의되지 않는다.)

 

vs code 메인 화면에서 다음의 버튼을 눌러 원격 저장소를 복제할 수 있다. 

먼저 local computer의 저장소화를 언급한 후, 원격 저장소 연동을 설명한다. 

Local comptuer 저장소화 

 

  • 탐색기에서 원하는 local repository를 선택하여 연 후 
  • 바로 위 이미지의 버튼을 누른다. 
  • 리파지토리 초기화 버튼을 클릭하여 내 컴퓨터에 저장소를 만든다. 

 

내 컴퓨터가 저장소가 되면 어떤 일이 일어날까? 

.git 폴더가 생성되는 것을 통해, 디렉토리가 저장소화 된 것을 확인할 수 있다. 

 

.git은 보이지 않도록 감춰져 있다. 보이게 하는 법은 MAC 기준 code $\rightarrow$ preference $\rightarrow$ setting $\rightarrow$ 검색 exclude $\rightarrow$ 숨김 폴더 .git제거 

 

.git 폴더만 있으면 완벽한 backup의 마무리이다. .git 이 없어지면 그간의 backup은 모두 사라진다. 

$\rightarrow$ .git만 메일로 공유해도 완벽한 migration이다.  

 

저장소에 나타나는 commit 대기 상태 - (M, U)

파일을 생성 혹은 수정하면 저장소에서 변경사항 저장을 요구한다. 

이 때 파일에 M, U 가 나타난다. 

 

  • M: 이미 저장소에 등록된 파일, 수정이 완료되었으니 수정 사항 저장 요구 
  • U: git이 아직 추적하고 있지 않는 새로 생성된 untracked file, 저장 요구 

 

저장소 등록 방법 - (add, commit, push)

  • + 버튼: add $\rightarrow$ add 완료, staging area로 파일이 올라간다. staging area는 말그대로 대기 상태로 commit전까지는 로컬 저장소에도 저장이 된 상태가 아니다. 
  • commit: staging area의 add된 파일만 commit 된다. $\rightarrow$ project folder에 있는 여러 파일 중, 선별적으로 commit 하는 것이 가능하다. 이는 로컬 저장소에 파일 수정 내역등을 저장하는 단계이고, 아직 원격 repository에는 올리기 전 단계이다. 
  • push: commit된 file을 원격 저장소에 저장하는 과정이다. $\rightarrow$ 바로 밑에서 관련 내용이 나온다. 

 $\rightarrow$ 즉 한마디로 다음의 과정은 프로젝트 폴더에 파일을 생성한 후, 그중 원하는 파일만 선별적으로 골라서 그 순간의 상태를 저장하고, 기록함으로써 버전 관리를 할 수 있게 된다. 

ADD 버튼 +
commit 명을 기입한 후 체크 버튼
push 버튼

 

원격 저장소 연동 

파일의 backup은 내 위치로부터 멀 수록 좋다. 같은 로컬이면 실수로 삭제하는 일이 발생할 수 있다. 

유의점: 하나의 local 저장소여러개의 원격 저장소와 연결될 수 있다. 

이에 따라 내가 연결하는 원격 저장소에 별명을 붙여줄 수 있다. 

보통 별명은 origin 으로 한다. 

다음의 사진은 local 저장소 원격 저장소를 기입하여 연동하는 방법이다. 

github.com에서 본인의 repository 를 생성한후, https 형태로 주어진 원격 저장소 주소를 복사한다. 

다음과 같은 형태로 나타나기도 한다. 위에서 복사한 주소를 local 저장소 vscode에 기입하여 연결한다. 

유의할점, vscode에서 반강제적으로 github에 로그인을 시킨다. 

 

이때 로그인을 하게되면 발생하는 치명적인 문제가 있다. 

 

기존 내가 의도하는 신청 루트는 Add remote from URL로, 

https://github.com/HajunKim/test.git 이나, 

vscode에서는 계속해서 

HajunKim/~~ 

즉, 내 repository에서 추가하고 싶은 project명만을 기입하기를 기대한다. 

이때 만약 위 주소를 입력하게 되면 내 github에는 다음의 이름을 갖는 새로운 repository가 생성된다. 

"https://github.com/HajunKim/test.git"

굉장히 황당하고 어처구니 없는 경험이다. 

한번 로그인이 되면, 로그아웃을 해도 vscode는 계속해서 repository명만을 기입하도록 유도한다. 

그러니 Publish Branch 같은 갖잖은 버튼에 현혹되지 말고, 무조건 

Add Remote 를 클릭하면, URL 을 작성할 수 있는 란이 나타나고 여기에 작성하면 된다. 

연동 결과, local 에 commit해 놓은 1개의 파일을 push하여 원격저장소에 올린다. 

 

Git Graph

 

git graph는 내 git 기록 과정을 시각화해서 보여준다. 

extension에서 다운 받아서 사용한다.

자세한 용례는 본 글에서 점차적으로 다뤄질 예정이니 일단 넘어간다. 

v2 로 파일을 두개 더 생성하고, 기존 파일을 수정하여 commit, push 해보자. 

잘 추가되었다. 

graph를 통해 살펴봐도 잘 등록 되었다. 

 

이번에는 local repository에만 새로운 버전을 추가를 해보자. 

 

새로 생성한 파일을 "v3" 라는 commit 명으로 add 하고 commit을 완료하자 다음과 같이 나타났다. 

add 했을 때까지는 graph에 어떠한 변화도 없다가 commit을 마친 후에 어떠한 변화가 나타난 것을 알 수 있다. 

여기서 master와 origin/master 는 뭐고, 무슨 뜻일까? 

여기서 origin/master 에서 origin은 우리의 원격 저장소의 이름이다. 즉, origin/master 에 등록된(push된) 가장 최근의 저장 내역이 v2 라는 것이고, 로컬에는 commit 까지 완료했으므로 저장되어 v3 를 가리킨다. 

 

master는 가장 마지막 commit을 가리킨다. 즉, 원격에서는 v2이고, 로컬에서는 v3이다. 


협업 중 발생하는 문제

실제로 협업 과정에서는 내 로컬이 아닌, 다른 사람의 로컬에서도 원격 repository를 복제하고 파일을 추가할 것이다. 

 

다음의 과정을 직접 한번 수행해보자. 

 

다른 로컬에서 clone을 한번 해보자. 

새로운 vs code window 생성 

 

clone repository 

로컬에 폴더를 하나 더 생성한 후, clone 한다. (중앙에 clone 버튼)

 

이상적인 경우라면, 한 로컬에서 파일을 수정 후 push, 다른 로컬에서 push 된 파일을 pull, 그 후 새로운 수정 후 push 의 반복이겠지만, 실제로는 이러한 과정이 동시다발적으로 일어나며 conflict가 발생한다. 

이상적인 상황 예시 Head와 master에 대해서는 추후 나온다.

다음의 상황을 생각해보자. 

각 로컬에서 동일한 파일을 각자 수정 후 둘다 push $\rightarrow$ 생각하기도 싫은 환경이다.

(왼쪽: 수정전 가운데: 로컬 1 오른쪽: 로컬 2)

왼쪽의 파일을 각각 다음과 같이 수정한 후 push 해보자. 

한 로컬에서는 push가 되지만 뒤 늦게 push 한 로컬은 reject당했다. 

각 로컬에서 작업을 하다가 push를 할 때 이런 경우 문제가 발생한다. 

(overwrite 방지)

 

원격 저장소에 내게 없는 어떠한 파일 혹은 수정 사항이 있을 때의 push는 작동하지 않는다. 

즉, 먼저 pull을 통해 local 저장소에 선 반영을 해준후 push를 해줘야 한다. 

근데 pull을 하면 이러한 뼊따구 같은 황당한 문제를 만나는데, 이건 뭐 검색을 해봐도 잘 나오지 않는다. 

모두가 맞닥드리는 상황은 아니다. 

그때 

git config pull.rebase false

다음을 CLI 에 작성해주자. 

Pull type 이 merge가 아닌 rebase type으로 되어있는걸 false 처리하는 것이다. 

default 가 merge로 알고있는데, 왜이러는지는 도대체가 알 수 없어서 굉장히 고생을 했다. 

 

처리 후, pull이 잘 된다. 

 

pull 시 "3way merge" 가 되는데, 

3way merge는 다음과 같다. 

  • 즉 각 로컬에서 수정하기 전의 파일을 참고하여 각각 수정 사항과 비교
  • 만약 기존 파일과 로컬의 수정본들에서 값이 같다면 값을 유지
  • 한 파일에서만 수정을 했다면 그 값으로 업데이트 
  • 만약 둘다 수정을 했다면 사용자에게 선택 제안 

 

<<<<<<<HEAD 부분은 내가 수정한 부분이고, 

>>>>>>>b6d... 부분은 내가 수정했으나 원격 저장소에는 나랑 다른 "수정" 결과가 있어서 conflict 발생  

수정 사항을 승인해도 되고, 내 수정 사항으로 고집해도 되고, 혹은 비교해서 수정 내역들을 합치는 방법이 있다. 

먼저 compare changes를 눌러 기존 파일, 각 수정본을 봐보자. 

각 수정을 모두 허용해보자. 

둘이 단순 합쳐지지는 않고 다음 line에 추가되는 방식으로 합쳐졌다. (운영체제마다 결과가 다른 듯 하다.)

merge change도 + 를 눌러 add, commit, push 한 후, 다른 로컬에서 pull하여 수정사항을 반영하자. 

 

그래프 참고 

따로 수정된 내역이 하나로 합쳐(merge)졌다. 

 

그림으로 다음의 과정을 봐보자. 

  • 왼쪽 그림: 왼쪽에서 먼저 L 을 push 하고 오른쪽에서 R 을 Push 하려 했으나 reject 
  • 가운데 그림: 이에 따라 L을 먼저 pull 하여 R 과 합친 후 push 
  • 오른쪽 그림: 오른쪽에서 push 한 내역을 왼쪽에서도 pull해서 반영 
  • 이것이 -협.업- 이다 

Pull vs Fetch

 

Pull

  • 원격 저장소로부터 필요한 파일을 다운 + 병합
  • 로컬 브랜치와, 원격 저장소 origin/master가 같은 위치를 가리킨다.

Fetch 

  • 원격 저장소로부터 필요한 파일을 다운 (병합은 따로 해야 함)
  • 로컬 브랜치는 원래 가지고 있던 로컬 저장소의 최근 커밋 위치를 가리키고, 원격 저장소 origin/master는 가져온 최신 커밋을 가리킨다.

git push --force

push를 실수로 잘못했을 때 취소 할 수 있는 방법이 존재는한다. 

하지만 git push --force 는 대혼란이 일어날 수 있다. 타인이 이미 pull 했다면? 생각만해도 끔찍하다. 

실수를 하면 실수를 취소하지말고 마지막 작업한 거를 다시 수정해서 push 하면 된다. 


Git Head, Master 

Head 와 Master를 잘 이해해야 git 에서 제공하는 환상적 기능들을 사용할 수 있다. 

ex. time machine

 

 

project folder 

.git $\rightarrow$ 저장소 (human의 영역이 아니고 machine의 영역 )

 

commit massage, 파일 이름, 작성한 시간, 이메일, 등등을 헤쉬값으로 생성 

그 결과 만들어지는게 commit ID: 5d231fwe24f32iuf384y3174 이런 형태의 ID 

방금 만들어진 commit ID 가 master의 값이 되고, master 는 마지막 commit을 가리킨다. 그리고 Head 는 마스터를 가리킨다. 

 

 

여기서 Head, Master 개념을 정리하고 넘어가자. 

 

  • Head: 현재 commit을 카리킨다. 
  • Master:  마지 막 commit을 가리킨다. 

얼핏 보기에 둘이 어떤 차이가 있나 싶지만, 본 글 추후에 설명이 충분히 되니 조금만 더 인내심을 갖고 읽어보자.

 

v1 commit시, 다음과 같이 commit id 가 생성되었고, master 는 마지막 commit 인 064afd를 가리켰다. Head 는 마스터를 가리키고있다. 

 

v2 commit시, parent는 기존 Head 가 가리키는 것을 참고한다. (Head가 가리키는 것이 현재 commit이기 때문에 다음 commit 에서참고할 parent) 그리고 Head 는 마스터를 가리키기에 master가 064 ~ , 때문에 parent 가 064~ 가 된다. 

새롭게 만들어지는 commit ID 는 HEAD 의 새로운 값이 된다. 이때 HEAD 가 master 를 가리키고 있기에 master 값이 변한다. 

 

 

linked list 구조 왜 이런 구조를 가지고 있는가? 뒤에 나온다. 

(head를 바꾸려면 checkout 명령을 사용하면 된다.)

 

*미리 생각해볼 것*

처음 commit 생성시 master 는 commit ID 를 가리키고, HEAD 는 마스터를 가리키게 생성 

commit 시 parent 는 기존 HEAD 가 가리키는 것 참고

Commit ID 는 HEAD 의 새로운 값 (HEAD 가 master 를 가리키고 있기에 master 값 변함)

 

...

git log --oneline

git 은 무조건 HEAD 를 본다. (현재 commit을 가리키니까)  

Head 는 master, master v3 즉 ed8d~ 를 가리킨다. 

 v3 의 parent 는 5d9a ~ 이고, 

5d9a~의 v2는 064af~ 를 parent로 갖는다. 

 


Time Machine

만약 내가 git에서 과거 버전으로 돌아가고 싶다면 어떻게 해야 할까? 

다음의 두 기능이 필요할 것이다. 

  • 과거 버전으로 원할한 복구 
  • 기존 버전으로 돌아오고 싶을 때 바로 복구 가능 

HEAD는 working directory가 어떤 버전과 같은지 가리킨다. 

즉 과거 버전으로 옮기고 싶다면 HEAD 를 과거 버전의 commit으로 옮기면 된다. 

이 명령어가 checkout이다. $\rightarrow$ checkout 은 HEAD를 옮긴다. 

과거버전으로 정말 이동이 되는지 checkout 을 해보자. 

works6, 7 은 애초에 아직 commit 되지 않은 상태이기에, works5.txt 는 과거로 돌아가는 과정 속에서 없어진 것을 확인할 수 있다. 

바로 위 예시는 한 프로젝트의 예시로, 상단의 엑셀 파일 git 과 바로 그 밑 git graph와 차이가 있다점 참고 

 

v3에서 v2로 check out 을 했다고 가정해보자. 

graph를 볼 때는 항상 HEAD를 먼저 본다. (현재 commit의 위치이므로)

이걸 보면 working directory 가 v2 겠구나, 를 알 수 있다. (만약 같지 않다면 commit 이 완료되지 않은 내용이 있겠구나 ~ 할 수 있다.)

 

다만, graph를 잘 보면 master 가 사라진 것을 알 수 있다. v3 가 홀랑 날라갔다.. 

git log 를 보면 HEAD를 시작으로 parent만 따라가기에 v2, v1 만 보인다.

git log --oneline "--all" 하면 다 볼 수 있다.

위 내용을 적용해서 언급한 프로젝트 예시에 적용해보면 다음과 같이 나타난다. 

master 와 HEAD가 다르다. 

이를 통해 아까 한 말의 의미를 이해할 수 있다. 

  • Head: 현재 commit을 카리킨다. 
  • Master:  마지막 commit을 가리킨다. 

참 쉽죠? 

 

기존 버전으로 다시 돌아오고 싶을 때 는 어떻게 할까? 

  • git checkout master 
  • git checkout (v3 ID)

만으로도 돌아올 수 있다. 하지만 특별 이유 없으면 ID 보다는 master 를 선호한다. 

 

왜 인고 하면, 

git checkout ed8d4d

ID 를 통해 checkout을 하면 

다음과 같이 HEAD 가 master 를 안가리키고 v3를 같이 직접 가리키고 있는 것을 볼 수 있다. 

Graph를 봐도 master 를 가리키지 않고 바로 v3 를 가리킨다. (가리킬 때 bold 처리 된다. 거지같은 UI)

 

 

반면 다음과 같이 master branch 로 checkout 하면 

git checkout master

graph 까지 완벽하게 잘 가리키는 것을 확인 할 수 있다. 

 

git checkout master 로 HEAD 의 위치가 v3 에서 master 로 이동된 것을 볼 수 있다. (파란색 태두리 원)


Time Machine을 그림과 예제로 복습해보자. 

 

 

Head 와 master 의 변천사

 

A가 생성 되자 Head $\rightarrow$ master $\rightarrow$ A 

B가 생성 되자 Head $\rightarrow$ master $\rightarrow$ B

..

D가 생성 되자 Head $\rightarrow$ master $\rightarrow$ D

 

B로 시간 여행을 해보자. 

git checkout B (왼쪽 그림)

시간 여행을 끝내보자. 

git checkout master  (오른쪽 그림)

만약 git checkout D를 한다면 상단에서 언급했던 예시처럼 Head 가 D를 직접 가리킨다.

이걸 detached HEAD state라고 한다. HEAD 가 branch가 아니라 버전을 직접 가리킨다는 뜻이다. 

Detached 는 잘못하면 내용을 다 날릴 수 있어서 특별한 경계를 요구한다. 

왜?

이 상태에서 commit하면, 다음과 같이 E가 생기는데, HEAD는 버전을 직접 가리키므로, HEAD 가 움직인다.


 

(아까 미리 생각해보자가 여기서 힘을 발휘한다! commit 시 master가 아니라 HEAD 가 움직인다.

HEAD 가 master를 가리키고 있을 때는 master 가 같이 움직이지만 그렇지 않을 때는 HEAD 만 움직인다!)


HEAD 가 master를 가리키면 master 가 움직이지만.. 그렇지 않기에 master 와 Head 는 궁극적으로 다른 곳을 가리킨다. 

이때부터는 master가 더이상 last 커밋이 아니고  HEAD 가 last commit이 되어버렸다. 

이때 git checkout master 를 하면..? 

E 를 그냥 날리게 된다. 

 

이런 경험을 몇번 쌓이면 git 최고 기능 중 하나인 시간 여행 잘 안하게 된다... 

 

다음의 기능은 실험 용도로 활용할 수 있다.

버려질 가능성이 높은 실험적인 작업을 detached head 상태로 실험 수행하고, git checkout D, (E 생성후 작업 수행 checkout) 으로 그냥 삭제해버릴 수 있다.  

 

근데 실험 만약 실험이 성공적으로 끝내고 E 를 유지하고 싶으면 어떡해야할까? 

그냥 master 로 돌아가면 날라간다. 

이 때, 새로운 master의 역할을 할 무언가(exp)를 만든다. 이걸 branch라 한다. 

 

새로운 commit 발생시 버전은 D 를 가리킨다. master 즉 최종단에 commit이 추가되는 것으로 보인다. 

 

checkout 이 꼭 브랜치를 이동다니는 것이라고 생각하지 않아도 될 것 같다. 

이러한 일련의 과정이 오른쪽 이미지의 나무같아서 branch라 불린다고 한다. 


다음의 과정을 직접 실험해 보았다. 

git checkout 을 통해 v3 로 시간 여행을 하고나자 v4 ~ 이하는 나뉘었다. 

여기서 checkout 이라는 새로운 버전을 commit하자 추가 된 것을 볼 수 있다. 

다시 master 로 checkout 하자 이하에 달려있던 버전 checkout 은 소멸되었다... 

 

그럼 이번에는 branch를 생성해서 소멸되지 않도록 해보자. 

v3 로 checkout 후, 새로운 파일 생성 후 commit 

 

단, push하면 다음과 같은 경고가 checkout을 먼저 하라고 경고해주고 진행이 안된다. 

여기서 checkout master 를 하면 아까와 같은 상황이 발생하니 버전 checkout2에 branch를 생성한다. 

branch 가 생성되었다! 

git checkout master 하자, 

다음과 같이 branch가 잘 생성된 것을 볼 수 있다. 

 

조금만 더 실험을 해보자. 

checkout v2 

브랜치가 더 나뉜것을 볼 수 있다. 

이번 실험에서는 계속해서 UI에 대하여 간략하게 설명을 한다. 

 

파란 선 원이 가리키는 곳이 Head 가 가리키는 곳이다.

log를 보면 다음과 같다. 

Head 가 master 로부터 detach 되었다. 

checkout3 라는 commit을 하자 local 에서 head 가 이동한다. 

git log --oneline 으로 현재 HEAD 가 가리키는 version을 알 수 있다. 

git log --oneline --all 은 모든 내역을 보여준다. 

이를 통해 지금 local master의 위치를 확인 할 수 있다. 

branch1 은 checkout2 를 가리키고 있다. 

이제 push 를 해보자. 

Head 가 branch를 가리키지 않고 있을 때는 Push가 안된다. 

b1 혹은 master 를 가리키면 push가 될 것으로 보인다. 

 

checkout2 로 checkout하자 다음과 같다. 

git log --oneline

git log --oneline --all

checkout2 으로 checkout 하자 head 는 checkout2 를 가리킨다. 

다만 유의해야 할 점은 log에 나타난 것 처럼 HEAD 는 checkout2 를 가리키지, b1 을 가리키는게 아니다. 

push 도 안된다. 

그러면 다음으로 b1 으로 checkout 을 일단 하고, 

b1 으로 checkout 된 건 b1 이 bold 처리됨으로서 확인 할 수 있다. UI 가 좀 약하니 유의할 것 

그럼 여기서 commit을 하면 HEAD 가 가리키는 b1 도 이동을 할까? 

확인해보자. 

그렇다! 

마치 이제는 b1이 master 의 역할을 하는 것이다. 

즉 , master 는 default branch 그 이상도 이하도 아니었던 것이다. 

 

 

이것으로 협업을 위한 아주 기본기는 마무리한다. 

 

-merge 추가 필요 

 

Comments