프로그래밍/Git

[Git 개념 정리] Git 개념 정리 3️⃣ (브랜치 사용하기, Git을 통한 협업)

ourkofe's story 2024. 7. 25. 01:05

이번 글은 코드잇 강의를 수강하면서 배운 내용을 주로 하여 정리되어 있습니다. (코드잇 스프린트 데이터 애널리스트 트랙 1기 훈련생)


브랜치 사용하기


브랜치란?

브랜치는 하나의 코드 관리 흐름으로 나뭇가지라는 뜻을 가지고 있습니다.

제일 처음에 하는 커밋을 루트 커밋(root commit)이라고 하며, 커밋의 전체적인 모습은 루트 커밋을 시작으로 가지가 갈라지는 나무 모양을 하고 있습니다.

이 때의 가지 하나하나를 브랜치라고 합니다.

원본 → 1차 버전 → 2차 버전 → … → n차 수정 과정에서 하나의 코드 관리 흐름이 생기게 되고 이를 브랜치라고 합니다.

 

프로젝트를 진행하다가 다른 흐름의 코드 작성을 진행하고 싶다면 또다른 브랜치를 만들어 기존 브랜치의 흐름과 다른 흐름의 코드를 만들고 관리할 수 있습니다.

이러한 경우에 git branch [브랜치명] 커맨드를 사용해 새로운 브랜치를 만들고 git checkout [브랜치명] 커맨드를 사용해 브랜치를 전환하고 새로운 코드 작성 및 관리를 할 수 있는데 이에 대한 자세한 내용은 밑에서 설명하겠습니다.


master 브랜치와 main 브랜치

  • master(주인, 달인, 통달하다)
  • main(가장 중요한, 주된)

최근 개발 커뮤니티에서는 master 대신에 main이라는 이름을 기본 브랜치로 사용하는 추세입니다.

main이 기본 브랜치를 훨씬 명확하게 설명하기도 하고, main은 영어 말고 다른 나라 말로 번역했을 때도 훨씬 매끄럽기 때문입니다.

즉, 두 브랜치의 이름만 다르고 기능은 동일합니다.


브랜치 다루기 (브랜치를 간단하게 다루는 커맨드들)

  • 현재 branch와 branch목록을 확인하기 : git branch
  • 브랜치 생성하기 : git branch [원하는 브랜치명]
  • 브랜치 전환하기 : git checkout [브랜치명]
  • 브랜치 삭제하기 : git branch -d [브랜치명]
  • 브랜치를 생성하는 동시에 생성한 브랜치로 전환하기 : git ckeckout -b [브랜치명]

브랜치 merge하기 : git merge

브랜치에서 작업한 커밋을 다른 브랜치에도 적용하고 싶을 때 git merge [브랜치명] 커맨드를 사용할 수 있습니다.

git merge 커맨드를 사용할 경우 창이 하나 팝업되는데 이는 머지 커밋이라고 하는 커밋 메시지를 작성하는 창입니다.

커밋할 때와 같이 텍스트 에디터에 내용을 작성하고 :wq를 통해 나온다면 merge를 완료할 수 있습니다.

현재 1번 브랜치에 있을 때 2번 브랜치를 합치고 싶다면 git merge 2 를 해야한다고 이해하시면 됩니다. (현재 위치인 1번 브랜치에 2번 브랜치를 합치겠다.)


merge과정 중에 충돌이 발생하는 경우

merge 과정 중에 충돌이 발생하는 경우를 conflict 가 발생했다고 합니다.

두개의 브랜치에서 각각 파일에 다른 작업을 해주고나서 브랜치를 위 방법처럼 merge를 하는 경우, 내용에 겹치는 부분이 있다면 git은 어떤 부분을 반영해야할지 판단할 수 없어 충돌이 발생합니다.

충돌이 발생한 경우에는

  1. 충돌이 발생한 파일을 열어 충돌이 난 부분을 제거하고 merge의 결과가 되었으면 하는 모습대로 코드를 수정하고 add와 commit을 합니다.

충돌이 발생했을 때, merge를 취소하기

충돌은 머지(merge)를 하는 과정에서 발생하며, 위 방법과 달리 Conflict를 해결하지 않고, 일단 merge 자체를 취소하는 방법도 있습니다

머지를 시도하기 이전의 상태로 돌아가고 싶다면 그냥 머지 자체를 취소하는 방법도 있으며, 이 머지 작업을 취소하는 방법은 git merge --abort 커맨드를 사용하면 됩니다.

만약 꼭 머지를 해야하는 상황이라면 이전 방법의 충돌이 일어난 파일을 직접 해결하고 커밋을 하는 게 정석입니다.

하지만 Conflict가 발생한 파일들이 너무 많아서 Conflict를 최소화할 수 있는 방식으로 파일들을 다시 수정하고 커밋한 다음에 머지를 하고 싶다거나 그냥 좀더 나중에 머지하고 싶을 때라면 이 방법처럼 그냥 머지 자체를 취소해도 됩니다.


여러 파일에서 충돌이 발생하는 경우

지금까지는 파일 하나에서 conflict가 발생하는 상황을 해결해왔습니다.

하지만 개발 실무에서는 파일 여러 개를 수정하는 경우가 많다보니 머지할 때 conflict도 파일 여러 개에서 나는 경우가 많습니다.

이련 경우에 해결방법에 대한 원리는 파일 하나일 때와 같습니다.

파일 여러개가 conflict가 났을 때는 파일 하나씩 conflict를 해결하고 git add [파일 이름] 커맨드로 하나씩 staging area에 올리거나(중간중간에 git status 커맨드로 현재 상태 확인하면서) 모든 파일들의 conflict를 다 해결하고, git add . 커맨드로 한번에 staging area에 올리고 커밋을 하면 됩니다.


Remote Repositoy의 브랜치

위에서 설명한 내용 중에 Local Repository의 내용을 Remote Repository로 보내기가 있었는데, 이 파트에서 GitHub에서 Math_Box라는 리모트 레포지토리(remote repository)를 만들고 로컬 레포지토리(local repository)의 내용을 그 리모트 레포지토리에 보내기위해 아래와 같은 커맨드 2개를 실행한 적이 있습니다.

git remote add origin [리모트 레포지트리 주소]

git push -u origin main

이번에 위 커맨드의 의미를 설명해보겠습니다.

origin이란?

git remote add origin에서 remote는 리모트 레포지토리에 관한 작업을 할 때 쓰는 커맨드입니다. 그리고 그 뒤의 add는 새로운 리모트 레포지토리를 등록하겠다는 뜻입니다. 그 다음의 origin [리모트 레포지토리 주소]는 리모트 레포지토리를 origin이라는 이름으로 등록하겠다는 뜻입니다.

결국 이 커맨드를 실행하고 나면 리모트 레포지토리 주소를 origin으로 간단하게 나타낼 수 있게 됩니다.

그런데 origin이라고 하는 이유는 리모트 레포지토리를 최초로 추가할 때 origin이라는 이름으로 가리키는 것이 관례화되어 있기 때문입니다.

아마도 다른 사람의 리모트 레포지토리를 자신의 컴퓨터로 가져와서 작업을 하는 사람의 입장에서는 리모트 레포지토리가 프로젝트의 근원이 되는 존재이기 때문에 그런 관습이 생긴 것으로 추측된다고 합니다.

git push -u origin main

위 커맨드는 현재 로컬 레포지토리에 있는 main 브랜치의 내용(=main 브랜치와 관계된 모든 커밋들)을 origin이라는 리모트 레포지토리로 보낸다는 뜻입니다.

이때 같은 이름의 브랜치로 전송하게 되는데 만약 origin이라는 리모트 레포지토리에 main 브랜치가 없으면 main 브랜치를 새로 생성하고 푸시합니다.

  • -u 옵션은 --set-upstream이라는 옵션의 약자이며, 로컬 레포지토리에 있는 main 브랜치가 origin에 있는 main 브랜치를 **tracking(추적)**하는 걸로 설정됩니다.

tracking이라는 것은 로컬 레포지토리의 한 브랜치가 리모트 레포지토리의 한 브랜치와 연결되어 그것을 계속 바라보는 상태가 되는 것이라고 할 수 있습니다. 이러한 연결 상태를 tracking connection이라고 합니다.

origin/main의 의미

위의 내용을 통해 로컬 레포지토리의 main 브랜치와 리모트 레포지토리의 main 브랜치가 서로 다른 2개의 브랜치라는 것을 인지할 수 있습니다.

커밋 히스토리를 보면, 로컬 레포지토리와 리모트 레포지토리의 main 브랜치가 무엇을 보여주고 있는지 확인할 수 있습니다.

main이 로컬 레포지토리의 main 브랜치를 나타내고 origin/main이 리모트 레포지토리의 main 브랜치를 나타내는 것으로 이해하면 됩니다.


HEAD와 브랜치의 관계

앞에서 설명한 내용이지만, 다시 설명하면 HEAD는 어떤 커밋을 가리키는 존재이고, 브랜치는 하나의 코드 관리 흐름이지만 어떤 커밋을 가리키는 존재(포인터)이기도 합니다.

HEAD는 일반적으로 브랜치를 가리키고 있으며, 브랜치가 커밋을 가리키고 있기 때문에 HEAD가 커밋을 가리킨다고 얘기할 수 있는데 이러한 관계가 HEAD와 브랜치의 관계입니다.(HEAD는 main 브랜치를 통해 간접적으로 커밋을 가리킵니다.)

다른 브랜치가 생성되어 각각의 브랜치에서 코드를 작성했을 때, 각각의 코드 관리 흐름이 생기게 될 것이고, HEAD는 계속 코드를 작성하고 커밋을 하고있는 해당 브랜치를 가리키고 있습니다.

그리고 브랜치의 내용들을 머지하고 싶을 때, 브랜치를 머지하게 되면 머지 커밋이 생기게 됩니다.

머지 커밋은 헤드가 가리키던 커밋에 다른 브랜치가 가리키던 커밋을 합쳐서 새로운 커밋을 만드는 작업입니다.


git reset의 비밀

git reset을 할 때 HEAD의 변화는 HEAD는 여전히 같은 브랜치를 가리키고, HEAD가 가리키는 브랜치가 다른 특정 커밋을 가리키게 됩니다.

이 때문에 결국 HEAD가 간접적으로 가리키던 커밋도 바뀌게 됩니다.

이러한 과정을 통해 git reset을 했을 때 HEAD가 가리키던 커밋이 바뀐다고 말할 수 있습니다.

 

그리고 git reset을 한다고 그 이후의 커밋이 사라지는 건 아닙니다.

일반적으로 git reset을 한다고 하면 그 이후의 커밋이 삭제되는 것으로 착각할 수 있습니다.

사실 과거의 커밋으로 git reset을 한다고 그 이후의 커밋들이 삭제되지 않고 계속 남아있으며, git reset은 과거의 커밋뿐만 아니라 현재 HEAD가 가리키는 커밋 이후의 커밋으로도 할 수 있습니다.(지정한 부분만 보이는 것으로 삭제된 것처럼 보입니다.)

 

이러한 사실들을 모두 알고 있다면, git reset을 사용해서 커밋 사이를 자유자재로 이동할 수 있습니다.

git reset과 git checkout

git reset git checkout
HEAD가 가리키던 브랜치가 다른 커밋을 가리키도록 합니다. HEAD 자체가 다른 커밋이나 브랜치를 가리키도록 합니다.
HEAD도 결국 간접적으로 다른 커밋을 가리키게되는 효과가 생깁니다. 브랜치를 통하지 않고, 커밋을 직접적으로 가리키는 HEAD를 Detached HEAD라고 합니다.

브랜치를 다루는 주요 커맨드 리스트

git branch [새 브랜치 이름] 새로운 브랜치를 생성합니다.
git checkout -b [새 브랜치 이름] 새로운 브랜치를 생성하고 그 브랜치로 바로 이동합니다.
git branch -d [기존 브랜치 이름] 브랜치를 삭제합니다.
git checkout [기존 브랜치 이름] 그 브랜치로 이동합니다.
git merge [기존 브랜치 이름] 현재 브랜치에 다른 브랜치를 병합합니다.
git merge --abort 병합을 하다가 conflict가 발생했을 때, 일단은 병합 작업을 취소하고 이전 상태로 돌아갑니다.

Git 협업하기

이번 목차에서는 실제로 개발 환경에서 Git을 쓸 때 자주 만나게 되는 상황들과 그 상황에서 써야할 커맨드들을 설명하겠습니다.


git push 전에 git pull을 해야하는 경우 

이전에 배운 내용인 git push는 로컬 레포지토리에 있는 브랜치의 내용을 리모트 레포지토리의 브랜치에 보내는 것입니다.

그에 반해 git pull은 리모트 레포지토리에 있는 브랜치의 내용을 로컬 레포지토리의 브랜치로 가져오는 것입니다.

리모트 레포지토리를 중심으로 협업을 진행하는 경우 협업자 중 누군가 리모트 레포지토리로 git push를 하는 상황이 발생할 것입니다.

그러면 리모트 레포지토리는 제 로컬 레포지토리의 브랜치와 다르게 브랜치에 새로운 내용이 반영되어 있습니다.

만약 이 경우에 그냥 git push를 한다면 오류가 발생합니다. 그 이유는 로컬 레포지토리를 수정하는 동안 리모트 레포지토리에 변화가 생겼기 때문입니다.

이러한 문제를 해결할 때는 간단하게 먼저 git pull를 하면 됩니다.

만약 git pull를 진행하던 와중에 병합이 이루어지면서 같은 영역에 병합이 일어나면 내용 간에 충돌이 일어날 수 있는데 충돌이 발생한 경우 이전에 충돌을 해결했던 방법처럼 파일에 직접 들어가 파일을 수정합니다.

파일을 수정하고 나면 저장한 뒤 이제 git add와 git commit을 하고 git push를 한다면 수정한 내용을 리모트 레포지토리에 성공적으로 반영할 수 있습니다.

 

예시를 든다면, 만약 개발자 B가 먼저 git push를 했다는 가정 하에 개발자 A는 git pull을 한 뒤에 merge되었을 때 충돌이 발생할 것이고 충돌을 해결 하고나면 다시 커밋하고나서 git push를 하면됩니다.


git pull말고 git fetch 하기

위 내용에서 Git으로 다른 개발자와 협업을 하는 경우, git push를 하기 전에 git pull를 해야하는 경우가 많다는 내용을 설명했습니다.

그리고 git pull은 리모트 레포지토리에 있는 브랜치를 가져와서 현재 브랜치에 merge하는 커맨드라고도 했습니다.

이 때, 브랜치를 가져온다는 것은 브랜치가 가리키고 있는 커밋 이전에 이루어진 모든 커밋들을 가져온다는 의미입니다.

 

그런데 여기서 파일을 가져오기만 하고 merge는 하지 않는 커맨드가 있습니다.

이 커맨드는 git fetch라는 커맨드로, git fetch는 리모트 레포지토리에 있는 브랜치의 내용을 일단 가져와서 살펴본 후에 merge하고 싶을 때 사용합니다.

git fetch를 하게되면, 로컬 레포지토리에서 현재 HEAD가 가리키는 브랜치의 업스트림(upstream) 브랜치로부터 최신 커밋들을 가져오고 병합은 하지 않습니다.

git fetch 커맨드의 과정은 git fetch 커맨드를 입력하면 내용을 가져오며, 가져온 내용을 git diff 커맨드(예)git diff main origin/main)를 통해 두 브랜치간의 차이를 확인합니다. (git diff는 두 커밋 간의 차이 뿐만 아니라 두 브랜치간의 차이를 확인한다.)

 

만약 내용을 확인하고 리모트 레포지토리의 브랜치에 문제가 있을 때 해결 방법

  1. 잘못된 코드를 추가한 개발자에게 함수를 지우고 다시 리모트 레포지토리에 올려달라고 합니다.
  2. 잘못된 부분을 알아서 해결하고 다시 git push합니다.

정리하면, git fetch는 리모트 레포지토리에서 가져온 브랜치의 내용을 머지하기 전에 점검해야 할 필요가 있을 때 사용하거나 리모트 레포지토리에 있는 브랜치의 내용과 내가 작성한 코드를 비교해서 잘못된 부분이 없는지 검토해야 할 때 사용합니다.
(git pull = git fetch + merge)

git pull과 git fetch를 사용하는 경우

git pull : 리모트 레포지토리의 브랜치를 검토할 필요없이 바로 합치고 싶을 때 사용합니다.

git fetch : 리모트 레포지토리의 브랜치를 검토해야 할 때 사용합니다.


코드를 누가 작성했는지 알고 싶은 경우

어떤 파일의 특정 코드를 누가 작성했는지 찾아내고 싶은 경우 활용하는 커맨드는 git blame입니다.

코드를 누가 작성했는지 알고 싶을 때 git blame [파일명]을 통해 파일에 작성된 코드에 관련된 내용들(커밋 아이디, 수정자, 날짜 등)을 출력하고 git show [커밋 아이디 4글자] 로 자세한 내용을 볼 수 있습니다.


이미 Remote Repositry에 올라간 커밋을 취소해야 하는 경우

코드를 작성하거나 파일을 수정한 내용을 커밋하고 push까지 완료한 후, 리모트 레포지토리에 올라간 커밋을 취소해야 하는 경우가 발생할 수 있습니다. 이러한 경우, 수동으로 내용을 원래대로 돌려놓고 다시 커밋할 필요 없이, 한 번에 작업을 수행해주는 커맨드를 사용할 수 있습니다.

이미 Remote Repositry에 올라간 커밋을 취소하고 싶다면 먼저, git log나 git log --pretty=oneline 커맨드를 사용하여 커밋 히스토리와 각 커밋의 아이디를 확인합니다.

커밋 히스토리를 통해 취소하고자 하는 커밋의 ID를 확인한 후, git revert [커밋 ID] 커맨드를 입력합니다.

이 커맨드를 입력하면 메시지 작성 팝업창이 뜨게 되고, 원하는 대로 메시지를 작성할 수 있습니다. 메시지 작성을 완료하고 저장 후 종료합니다.

이제 로컬에서 변경된 내용을 리모트 레포지토리로 푸시합니다. git push를 사용하여 변경 사항을 푸시합니다.

이렇게 하면 성공적으로 올라간 커밋을 취소할 수 있습니다.

주의 사항

  • git revert 커맨드는 기존 커밋을 취소하는 새로운 커밋을 생성합니다. 즉, 커밋 히스토리에는 취소된 커밋과 그 커밋을 취소하는 새로운 커밋이 모두 남게 됩니다.
  • 만약 커밋 히스토리 자체를 변경하고 싶다면, git rebase나 git reset 커맨드를 사용할 수 있지만, 이 경우 협업하는 다른 개발자들과의 충돌을 방지하기 위해 주의가 필요합니다.

여러 커밋 취소하기

Git에서는 최근 커밋보다 한참 전의 커밋을 대상으로나 여러개의 커밋을 대상으로도 revert를 하는 것이 가능합니다.

여러 커밋을 되돌리기 위해서는 되돌리려는 커밋의 범위를 지정해야 합니다. 범위를 지정할 때는 가장 오래된 커밋의 해시와 최신 커밋의 해시를 사용합니다.

예시를 통해 설명하면, 지정한 커밋 범위를 이용해 git revert 명령어를 실행하여 범위 내 모든 커밋을 되돌릴 수 있습니다.

git revert a1b2c3d4..e5f6g7h8
# git revert [커밋아이디1..커밋아이디2] ← 커밋아이디1 다음부터 커밋아이디2까지

이 명령어는 a1b2c3d4 이후부터 e5f6g7h8까지 포함된 모든 커밋의 변경 사항을 역으로 적용하여 새로운 커밋을 만드는 커맨드입니다.

이로써 히스토리를 보존하면서도 변경 사항을 되돌릴 수 있습니다.


Git에서 협업할 때 주로 사용하는 커맨드 리스트

git fetch 로컬 레포지토리에서 현재 HEAD가 가리키는 브랜치의 업스트림(upstream) 브랜치로부터 최신 커밋들을 가져오고 병합은 하지 않습니다.
git blame 특정 파일의 내용 한줄한줄이 어떤 커밋에 의해 생긴 것인지를 출력합니다.
git revert 특정 커밋에서 이루어진 작업을 되돌리는(취소하는) 커밋을 새로 생성합니다.

이번 글에서는 Git에서 branch를 다루는 법과 Git에서 협업하는 방법들을 설명했습니다.

이번 글에 이어 다음 글에서 마지막으로 Git을 자유자재로 활용하는 방법에 대한 내용을 다룰 예정입니다.

Git과 Github은 개발자라면 사용할 일이 있고 필수적으로 사용할 수 있는 프로그램으로 내용을 숙지하거나 이번 기회를 통해 알아가면 좋을 것 같습니다.

글 읽어주셔서 감사합니다.


출처 및 참고자료 : 코드잇 사이트 강의 'Git' https://www.codeit.kr/topics/git

728x90