티스토리 뷰

반응형

버전 관리 도구 git

어떠한 프로젝트를 계속해서 수정하는 데 계속해서 이름을 바꿔야 한다.

최종.제발최종 등

이게 수동으로 버전 관리를 하는 것.

이걸 자동화하고 싶어서 버전관리 프로그램을 쓴다.

 

수동 버전 관리는

1. 파일의 이름이 더럽다.

2. 파일이 너무 많이 생긴다.

3. 여러가지 정보를 적어도 정보가 빈약하다.

 

정말 여러 가지 버전 관리 시스템 중에 git을 사용한다.

그런데 git만큼 범용적으로 쓰는 게 없다. 그래서 git을 배운다.

 

버전관리 시스템을 사용하는 3가지 이유

버전/ 백업/ 협업

 

대부분은 협업을 위해서 버전관리 시스템을 쓴다. 그런데,

 

제일 행복한 버그는 문법. 해결 안하면 안 돌아간다.

정말 가장 무서운 버그는 논리 오류로 인해 찾을 수 없으며, 시장 상황 예측 불가 상황에서 터진다.

 

따라서 이전 버전으로 가면서 버그를 찾는다. 그러면 그 버그가 없는 최초의 버전 다음 버전에서 버그를 찾는다.

그렇기에, 버전관리 시스템의 가장 중요한 목적은 협업도 그렇지만 디버깅이다.

 

목표

어떻게 하면 버전을 잘 만들까?

좋은 버전은 무엇일까?

 

일단 GUI 기준으로 배우지만, 커맨드라인을 반드시 익혀야 한다. CLI (커맨드라인)에서의 깃이 중요하다.

협업에서는 커맨드라인을 기준으로 이야기한다.

 

  • 디버깅을 쉽게 하기 위해서는 하나의 버전이 작을수록 좋다.
  • 하나의 버전 안에는 하나의 주제만 들어가는 게 좋다.
  • 그리고 각각의 버전은 언제나 실행가능한 상태를 유지하는 게 좋다.

 

Visual Studio Code에서 새 프로젝트 만들기

 

오픈 폴더를 누른 후 > 폴더를 만들고 > 폴더 안에 들어가서 오픈 폴더를 하면 프로젝트가 생성된다.

 

visual studio의 해당 폴더 안에서 우클릭 -> new file을 하면 새로운 파일을 만들 수 있다.

이를 ctrl +s로 저장한다.

 

 

Source Control이라고 하는 버튼에서, open repository

 

그리고 커밋 위쪽에 메세지를 적으면 된다.

 

여기선 에러가 난다.

커밋 버전을 만들 떄는 내가 누구인지, 어디다가 연락해야 하는지 적어야 한다.

 

이는 git log를 열어서 샘플 명령어를 입력해야 한다.

그러면 git bash를 열어야 한다.

터미널에서 + 버튼을 눌러 git bash를 추가한다.

오른쪽 위에 + 버튼을 누르면 된다.

이 깃 배쉬에 입력한다.

git config --global user.name "유저 이름"
git config --global user.email "유저@gmail.com"

 

그리고 나서 다시 커밋 버튼을 누르면 된다.

git log #커밋 상황을 볼 수 있다.

 

git bash에서 git log를 입력하면 커밋 상황도 볼 수 있다.

 

유저 이름과 이메일을 적는 건 단지 버전을 누가 생성했는지를 확인하기 위함.

인증을 위한 건 절대 아니다.

 

 

만약에 여기서,  첫번째 버전을 만들고, 두번째 버전을 만들어야 했는데, 그 상태에서 커밋을 잊어버리고 3번째 버전까지 만들었다.

 

복수의 변경사항을 하나의 버전으로 만드는 방법을 알아야 한다.

 

참고. source control에서 전버전과 비교해서 어느 부분을 수정했는지 알 수 있다. 초록색 부분이 수정된 부분이다.

 

 

source control에서 +(add)를 누르면 staged changes로 올라간다.

이러면 커밋 대기상태이다.

그 상태에서 커밋하면 v2 버전이 만들어진다.

 

그러면 3번째 파일만 남게 되는데, 이걸 v3로 만들면 된다. 

 

git graph

추가로 git graph를 쓰면 엄청난 버전 관리를 할 수 있다.

어떤 파일을 수정했는지, 누가 만들었는지 전부 알 수 있다.

 

클릭하고 파일을 누르면 수정 사항까지도 다 알 수 있다.

 

추가적으로 버전 관리를 할 때는 프로젝트 폴더에 .git이라는 이름의 폴더가 생긴다.

 

visual studio 설정 -> exclude -> .git이라는 폴더를 보이게 해라. 그럼 원리를 알 수 있다.

 

git의 원리

 

git의 원리는 간단하다. 프로젝트 폴더에서 .git 폴더를 제외한 부분은 작업 디렉토리(working directory)로, 여기서 파일을 생성하면 변경 내용이 스테이지 영역(Stage Area)으로 이동하여 add 명령으로 커밋 대기 상태로 바뀐다. 

 

커밋을 하면 변경 내용이 저장소(repository)로 이동하며, 이때 커밋 메시지, 작성자, 시간 등의 정보를 포함하여 하나의 텍스트로 만들고 해시값을 계산하여 커밋 ID로 사용한다.

 

커밋 ID는 내용을 기반으로 생성되며, 변경 내용에 따라 커밋 ID가 결정된다. 

저장소에는 각 커밋의 정보와 함께 이전 커밋의 ID를 가리키는 parent 정보가 포함되어 있다. 

따라서 파일의 변경이 있을 때만 용량이 증가하며, 파일명은 다르지만 내용이 같은 경우에도 동일한 형태로 인식된다. 

 

그러나 파일 이름이나 내용, 작성 시간, 작성자 등이 변경되면 커밋 ID가 완전히 달라진다.

 

각각의 버전은 그 버전이 만들어진 시점의 스냅샷이다.

 

main과 head 포인터의 차이

저장소를 만들면 Head가 생기며, 기본적으로 main을 가리킨다.

커밋 id는 Head가 가르키는 main에 기록된다.

 

  • Head -> main -> v1

여기서 버전 v2가 추가되면, main이 head의 대리자 역할을 한다.

Head 대신 main이 바뀐다.

 

  • Head -> main -> v2->v1

여기서 또 버전 v3가 추가되면?

  • Head -> main -> v3-> v2->v1

즉, main이 Head의 대리자로서 움직인다.

그러면 Head와 main을 이원화했을 때 오는 유용함은 뭘까?

 

Head와 main의 이원화

Head가 현재 시점을, main이 마지막 시점을 가리키므로 각각의 역할에 따라 부모 커밋을 명확하게 식별할 수 있다.

 

Head는 현재 시간이고, main은 마지막 시간이다.

디버깅을 하기 위해서는 워킹 디렉토리를 원하는 시점으로 바꾸는 걸 자유자재로 할 수 있어야 한다.

 

다시 말해서, 우리는 시간 여행을 해야 한다.

타임 머신의 개발자는 2가지가 필요하다. 하나는 내가 있었던 시간.(마지막 시간), 다른 하나는 내가 여행하고 싶은 시간.(현재 시간)

 

만약에 v1이 만들어진 시점으로 만들고 싶다.

그러면 Head를 v1으로 바꾼다.

 

그러면 워킹 디렉토리와 스테이지 에어리어는 v1가 만들어진 시점의 스테이지 에어리어로 바뀌어진다.

 

그렇게 Head를 바꾸는 명령어가 바로 cheak out이다.

 

Cheak out 방법

git graph에서 우클릭하여 '체크아웃'을 선택하면 해당 버전으로 이동한다.

현재로 돌아오고 싶을 때는 main에다가 마우스를 올려놓고, 다시 check out branch를 하면 된다.

 

현재로 돌아올 때는 이유를 모르면 그냥 무조건 checkout main을 하면 된다.

그러면 head가 main을 가리킨다.

 

main 파란색 테두리 -> 헤드가 v3를 직접 가리키고 있다는 뜻이다.

이를 편하게 볼 수 있는 명령어

git log --oneline --graph

 c69dffa (HEAD -> main) v3
* 67d9ad2 v2
* 9caa2bf v1

 

head-> main->v3를 가리키는 거에서

main을 더블클릭(check out)을 하면  head와 main이 동시에 v3를 가리킨다.

head -> v3

main -> v3

 

그러면 헤드가 직접 움직인다.

이 상태에서 버전을 추가하면 헤드가 직접 움직인다.

main -> v3

head -> v4

 

이러면 미래로 가버리는 것이다.

 

그러면 현재를 main으로 두고, v4는 새로 생성된다.

 

그러면 다시 check out main을 하면 v4는 아무도 가리키지 않기 떄문에, 실제적으로 없어진다. 즉, 날아간다.

 

main을 바꾸는 방법

 

그러면 main을 v2로 바꾸는 방법?

reset current branch->hard

헤드가 가리키는 메인이 움직인다.

 

주의 : 이렇게 되면 브랜지를 형성하지 않았을 시 앞의 브랜치는 날아갈 수 있다.

 

 

Checkout은 Head를 바꾼다.

reset은 Head가 가리키는 branch를 바꾼다.

마음대로 branch를 옮길 수 있다면, 아주 자유자재로 된다.

 

그래서 리셋이 복잡하다. 만약 헤드가 브랜치를 가리키고 있다면?

attached head state라면 main이 branch.

그렇지 않다면, head가 버전을 직접 가리키고 있다면 check out과 똑같다.

 

트리 구조 같은 느낌이다.

 

이전 버전으로 돌아가서 수정 후 커밋하게 되면 이후 버전들엔 어떤 영향을 미치는가?

 

어떤 버전도 버리지 않는다.

커밋 id만 기억하고 있으면 복구가 가능하다.

 

git checkout 커밋 id

 

만약 실수로 만들어서, main을 v3(미래)로 옮기고 싶다고 하면?

git checkout main 후에, git reset --hard 커밋Id

ex) git reset --hard c69dffa8

 

새로운 branch로 main을 만들어서 따라가게 하는 게 정석이다.

 

 

Branch를 이해하기

그러면 이 head가 main을 가리키지 않고 v3를 직접 가리키는 상황은 왜 만들 걸까?

exp를 branch로 만들어서 또 다른 branch로 형성하기 위해서이다.

 

헤드는 exp에 있고, main branch는 v3에 있다.

이상황에서, main은 defalut branch고, exp를 branch를 만들었기 때문에 커밋 아이디를 알 필요가 없다. 

그렇게 되면 exp, main branch라는 branch 명만 알면 된다.

그럼 그냥 더블클릭한 하면 왔다 갔다 할 수 있다.

 

이제 exp는 branch기 떄문에 다른 branch로 이동해도 사라지지 않는다!

 

즉, main -> v3, head -> v4였는데, 

exp라는 branch를 만들어서 v4를 가리키도록 대신한 것이다.

 

main ->v3, exp->v4

그러면 head(파란 동그라미)가 바뀌더라도 찾아갈 수 있다.

 

 보통 실험 작업을 하거나 새로운 기능을 만들 때, main 작업에서 오른쪽 기능을 클릭해서 feature/login 같이 한다.

 

조심해야 할 것 : 현재 우리의 저장소는 main 브랜치에 체크아웃되어 있다.

그렇기에 항상 어디에 체크아웃되어 있는지 잘 봐야 한다. 

 

그러면 feature/login branch를 더블클릭해서 체크아웃 상태로 만들거나, 아예 만들때 체크아웃으로 만들어놓는다.

 

거기서 커밋을 하면, 위쪽으로 branch가 옮겨간다.

그렇게 main에서도 커밋을 하면 branch가 갈라진다.

 

이 상태가 되면 branch가 갈라지게 된 것이다.

 

Branch 병합

main이 feature에 병합할꺼냐, feature가 main에 병합할것인가에 따라서 결과가 많이 다르다.

 

head -> main -> b -> a-> b2이고,

exp ->b2.

 

b에는 a.txt, b.txt

b2에는 a.txt,b2.txt

 

이 상태에서 main이 exp를 병합하려면 head가 main을 가리키도록 한 상태에서 exp를 병합.

 

그런데 여기서 당연히 a,b,b2를 전부 합하면 main이 부모가 될까?

아니다. b2와 b가 둘 다 부모가 된다.

그리고 c라는 커밋 아이디가 생기고, 그리고 이 c를 main이 가리킨다.

 

만약에 exp가 main을 병합하면?

똑같다. a,b,b2를 전부 가지게 된다.

그리고 b2와 b가 부모인 건 똑같다.

근데 다른 건, head가 exp를 가리키고, exp가 c를 따라간다.

 

현재 작업을 내가 살려둘 branch로 놓은 후에 작업하는 것.

 

파란색 동그라미(head)가 메인에 가있다.

 

그래서 main을 더블 클릭(head가 main을 가리키게)하고, merge into current branch를 클릭하면 main에 합쳐진다.

 

만들어진 branch는 merge branch 커밋이다. 즉, 병합하는 과정에서 자동으로 생성된 것이다.

parent는 두개의 버전이 다 parent가 된다.

 

항상 head를 찾아야 한다. head가 main을 가리키고 있으면, 자신이 main에 있다는 걸 알 수 있다.

parent 중에 feature 작업이 있다면 feature 작업이 끝나고 병합했다는 것도 파악할 수 있다.

 

marge commit 메세지는 바꾸는 경우는 없다.

100개의 브랜치도 2개씩 머지하면 다 머지할 수 있다.

머지 횟수에는 제한이 없고, 개인 별로 브랜치를 나눈 다음에 일이 끝난 후 머지로 전부 합친다.

 

Note : git-flow 머지 전략

develop branch가 머지 전략이고, 새로운 기능이 추가가 될때 branch를 따고, 작업이 끝나면 결합하고, 또 결합하고 하는 용도

 

 

나중에 다시 복습해야 한다. 2분밖에 안걸린다.

2주만 하루에 한번씩 한다.

 

반대로 feature login이 main을 병합한다는 건 무슨 의미인가? 큰일 난다는 소리다.

main에서 하고 있는 작업은 필수적이라서, main의 작업은 feature login으로 자주자주 땡겨줘야 좋다.

안 그러면 충돌이 일어난다.

 

git의 충돌

 

같은 파일을 수정하거나, 같은 파일에서 같은 행을 수정했을 때 충돌이 일어난다.

이걸 해결할 수 있어야 한다.

 

같은 파일 0 1 2 3 4에서

메인은 0, 1,m2,3,m4로 수정하고,

exp 브랜치는 0, 1,2,e3,e4를 가지고 있다면,

3행,4행,5행이 현재 코드가 다르다.

 

이 상태에서 main이 

3way merge라는 메커니즘에 의해서 충돌을 감지한다.

 

2 way vs 3 way merge

2 way merge -> 전의 파일은 상관 없고, 메인과 exp 브랜치의 파일만 비교함

0,1,?,?,?

자동으로 합쳐줄 수 없다.

 

3 way merge -> exp와 메인의 공통 조상을 찾는다.

그러면 0,1,2,3,4가 공통 조상이고, 이 조상을 BASE라고 이름붙인다.

 

그리고 base와 각각의 branch를 3자대면한다.

0,1은 양쪽 다 안바뀌었으므로 안바뀌었다.

 

그러면 2와 m2는?

m2 자체는 수정되었으므로 m2의 편을 든다.

마찬가지로 e3,3도 e3는 수정되었으므로 e3의 편을 든다.

그러면 0,1,m2,e3

 

m4, e4에서는? -> both modified

both modified면? 거기서 충돌한다.

이 기능을 아는 게 제일 중요하다.

 

공백도 변화로 치긴 한다.

 

 처음에 start를 커밋.

그리고 새로운 branch를 만든다. 만약에 위로 만든대로 비슷하게 하면?

 

 

이렇게 충돌이 난다.

 

head에서는 e1인데, 아래에서는 e3라서 병합이 안되었다.

이렇게 이상한 상태에서 커밋하면 절대로 안된다.

 

이때 아래의 버튼을 눌러 에디터를 연다.

그러면 어디가 문제인지 한 눈에 알 수 있다.

 

제일먼저 봐야 되는 건, 어디에서 충돌이 났는가이다.

어떻게 바꿀 것인가를 선택하는 것이다.

그러면 충돌된 줄을 수정하고, complete merge 버튼을 누른 후, 커밋을 하면

 

아주 정상적으로 커밋이 된다.

 

 

github.com

백업을 안전하기 위해서는 저장소를 하나 만든다.

원격 저장소를 만들어야 하는데, 이 원격 저장소를 제공하는 장소가 github이다.

 

Create new repository를 만든다.

 

public - open source

private - 

 

이 저장소에다가 우리의 저장소를 올리기 위해서는 원격 저장소의 주소를 지역 저장소에게 알려줘야 한다.

https주소를 저장한다.

 

그다음 visual studio에서, source control의 ...을 찾는다.

remote > add remote

추가로 주소를 엔터치면 나오는 remote의 이름도 입력해준다.

그 다음에 push를 해서, github.com에서 인증을 하면 된다.

 

이러면 원격 저장소에 모든 버전이 올라간 상태이다.

 

원격 저장소로 어디까지 올렸는지를 확인해야 한다. 아니면 전체를 다시 올려야 한다.

그래서 origin / main이라는 branch가 생기고, 이게 가리키게 된다.

 

이는 remote traking branch다. 깃이 자동으로 관리해준다.

이 상태에서 버전을 다시 커밋 한다고 해보자.

orgin은 아래인데, main은 위이다.

즉, 아직 push하지 않았다는 뜻이다.

 

이 상태에서 push하면 origin이 위로 따라가게 된다.

그래서 만약에 내 컴퓨터에 있는 버전은 뭘 하든 상관이 없다.

그런데 이미 push한 커밋은 건들지 마라. 그걸 수정하는 순간에 대 혼란이 난다.

 

github 기능

 

이슈를 해서 이슈를 달 수도 있다.

 

assignees로 해서 특정 누구한테 보낼 수도 있다.

 

Clone 기능

다른 컴퓨터로 가져갈 때 쓰는 게 clone

 

b에서 c로버전이 바뀌었을 때, 한 사람이 pull or fetch가 가능하다.

fetch는 원격 저장소에 방금 업데이트 된 그 버전(c) 자체를 가져온다.

main은 b인데, origin/main이 c가 된다.

 

아직 내 지역 저장소가 원격 저장소의 내용을 병합하지 않았다는 뜻.

 

여기서 git merge origin main 하면

orign과 main이 병합된다.

그러면 그냥 b를 가리키던 main이 c를 가리키게 된다.

 

pull = fetch + merge

즉, pull은 이 과정의 기능을 전부 포함하고 있다.

clone repository 후에 주소 추가.

그러면 .git을 만들 폴더를 만든다.

 

많은 오픈소스들을 가져와서 쓸 수 있다.

협업하는 사람의 환경을 시뮬레이션 할 수 있는 상태가 된다.

 

최고는 왼쪽에 있는 사람이 작업하고 있을 때, 다른 사람은 쉬는 것.

 

첫번째 사람이 push까지 하고 자러갔다.

그러면 두 번째 사람은 항상 작업하기 전에 일단 땡겨와야 한다.

 

일단 fetch를 보면

첫번째 사람(왼) / 두번째 사람 (오른)

 

그러면 이상태에서 merge into current branch를 할 때는, 첫번쨰 옵션을 끈다.

그렇게 되면 main/origin branch에 합쳐진다.

 

같은 방식으로 오른쪽에 있는 사람이 작업을 한다.

그렇게 해서 push를 하면 또 갱신.

 

그럼 또 첫 번째 사람이 pull해서 또 r3를 가져온다.

이 과정을 반복하는 게 협업.

 

충돌이 발생되었을 때, 첫번째 사람의 것이 바뀌고 push된 상태.

두번째 사람에겐 어떤 책임이 주어진거냐?

안전하게 코드를 만들고 다시 업로드해야 한다.

 

그러면 pull 하고, 어디가 바뀌었는지 알아야 한다.

 

push를 했을 때 이렇게 되어 있으면 origin/main이라는 완전히 다른 branch를 해결해야 한다.

colfict 발생했을 때는 위쪽에서와 같은 방법으로 해결하면 된다.

 

익숙해지면 5분도 안걸린다.

하루에 한 번씩 해보자.

 

반응형