티스토리 뷰

반응형

이번에 인턴을 진행하게된 곳에서 Git 에 대한 교육을 받게 됐는데 해당 교육에서 정말 많은 것을 배울 수 있어 블로그에 정리해보려 한다. 또, 내가 그동안 사용했던 Git 은 그저 백업의 목적만을 위한 것이였으며 구글 클라우드랑 다를바가 없었다는 것을 깨닫게 되었다.

 

이번 강의는 두 가지 포스팅을 통해서 정리할 것이며 첫 번째 포스팅에서는 Git 에 대한 간단한 설명과 왜 사용하는지, 그리고 Git 에 존재하는 여러가지 명령어들에 대해서 설명하려고 한다.

 

물론, 많은 사람들이 아는 기본적인 내용이겠지만 나한테는 정말 유익했고 꼭 다시한번 상기시키고 싶어서 정리를 하게되었다.

VCS(Version Control System)


자세한 내용에 들어가기에 앞서 Version Control System 이 무엇인지 간단하게 이야기해보자. VCS 가 어떤건지에 대한 자세한 설명은 넘어가고 늘 그랬듯이 왜 사용하는지(WHY) 에 대해서 이야기 해보자.

  1. 변경점 관리
  2. 버전 관리  - 프로젝트가 어떻게 시작되었고 어떤 변화과정들을 거쳤는지 History를 관리하기 위함이다.
  3. 백업 & 복구 관리 - 내가 지금까지 Git 을 사용한 목적이다.... (가장 기본적인 이유)
  4. 협업

이러한 VCS 에는 대표적으로 Centralized VCS, Distributed VCS 가 있다. 

Centralized VCS?

대표적인 프로그램으로 SubVersion이 있고 5년 전까지만해도 이게 대세였다고 한다. 

 

기본적인 형태는 위 이미지와 같이 구성되어 있다. 중앙 서버에 모든 소스코드에 대한 정보(소스코드, 버전, ...)가 저장되어 있고, 이를 사용하는 사람들이 원하는 내용(특정 버전)을 가져다 사용하는 것이다.

 

하지만 이러한 문제에는 여러가지 단점이 존재한다.

  1. 중앙 서버에 문제가 발생하면(서버 장애 등) 해당 소스코드가 날라간다.
    따라서 별도의 백업이 필요하다.
  2. 협업 상황에서 충돌이 발생할 수 밖에 없다. (중앙 서버에 존재하는 소스코드들은 Race Condition에 놓인다.)
    즉, 여러 개발자가 Commit 하는 상황에서 누가 먼저 Commit 하느냐에 대한 문제가 발생할 수 있고, 정치적인 이유로 Commit 의 순서가 결정될 수 있다.
    (ex. 팀장님이 먼저 Commit 하세요.. 제가 나중에 Commit 하고 충돌문제 해결하겠습니다.)

Distributed VCS?

대표적인 프로그램으로 Git 이 있고 최근에 가장 많이 사용하는 VCS 프로그램이다. Centralized VCS 와의 가장 큰 차이점은 레포지토리에서 원하는 버전만 가져오는 것이 아니라 레포지토리 전체를 복사해온다는 것이다. 우선 여기서 Centralized VCS 에서 발생했던 첫 번째 문제가 해결되는 것을 알 수 있다.

 

중앙 저장소에 문제가 발생하더라고 해당 레포지토리를 가져갔던 다른 개발자에게 해당 내용이 존재하기 때문에 손실을 최소화할 수 있다.

 

이렇게 Remote 저장소에 Commit 하는 것이 아니라 Local 로 가져온 저장소에 Commit 하는 것이기 때문에 충돌이 발생하지 않는다. 하지만 결국 Local 에 존재하는 저장소를 Remote 에 Push 해야하는데 이때 충돌이 발생할 수 있다. Git 은 이러한 문제를 해결하기 위해 Merge 와 같은 방법을 지원한다.

Git


Git 을 사용함으로써 얻을 수 있는 장점은 정말 많다. 하지만 내가 인상깊게 들었던 내용은 "비선형적인 개발을 가능하게 한다" 였다. 수 천개의 Branch 를 만들어 개별적으로 개발이 가능하기 때문에 협업에 있어서 엄청난 효율을 만들어 낼 수 있다는 것이였다.

 

그럼 Git 을 다루기 위한 명령어에는 어떠한 것들이 있고 각자 맡은 역할이 뭔지 살펴보자.

Git 을 잘 다룬다는 것은 Git Tree 를 자유 자제로 다룰 수 있다는 것이라고 한다.

Branch

Branch 는 코드를 통째로 복사하고 원래 코드와 상관없이 개발할 수 있도록 Git Tree 의 자식노드를 하나 새롭게 만드는 것을 말한다. 여기서 중요하게 생각해야 할 것은 Git Repository 는 Local, Remote 가 별도의 것이라는 것이다.

 

즉, Branch 를 새롭게 만든다는 것은 특정 시점에서의 새로운 개발 흐름을 만든다는 것과 같다. 새롭게 만든 Branch 에서 새롭게 프로그래밍을 진행하더라도 기존 Branch 에는 영향을 미치지 않고, 이를 통해 수많은 개발자가 협업할 수 있게 되는 것이다.

 

위에서 Local과 Remote를 항상 분리해서 생각하자고 했는데, 아래 이미지를 보며 조금 구체적으로 생각해보자.

 

위 이미지에서 Branch 는 origin/main, origin/HEAD, main, origin/newb, newb, HEAD, newc 가 있다.

 

여기서 origin/ 이 붙은 것과 그렇지 않은 것으로 구분할 수 있는데, origin/이 붙은 것이 Remote Repository 에 존재하는 Branch이고, 붙지 않은 것이 Local Repository 에 존재하는 Branch 이다. 다시말해 newc 는 Remote 에는 존재하지 않기 때문에 다른 개발자들이 해당 Branch 에 대해서 알 수 없다.

 

이에 반해 newb 와 origin/newb 는 Local 과 Remote 에 모두 존재하는 Branch이다. 하지만 이 두 개의 Branch 가 연결되어 있다고 확신하기는 이르다. 이 두 개의 Branch 가 Branch Tracking 으로 연결되어 있어야 비로소 이 두 개의 Branch 가 연결되어있다고 생각할 수 있다.론 Branch Tracking 으로 연결되어 있는 두 개의 Branch 가 서로 다른 이름을 가질 수는 있지만 그럴 경우 큰 혼란이 빚어지기 때문에 일반적으로는 같은 이름을 사용한다.

 

 

main 과 origin/main 은 Branch Tracking 상태이며 위 이미지에서 보듯이 두 Branch 의 관계 (2개 앞)에 대한 정보도 확인할 수 있다. 이렇게 Branch Tracking 상태로 연결되어 있다면 Local Repository 를 Push 하면 별도의 설정이 없을 때 자동으로 Tracking 중인 Remote Repository 에 해당 변화가 적용된다.

 

 

특수한 Branch 이름으로 HEAD 와 origin/HEAD 가 있는데, Remote 에서의 HEAD 와 Local 에서의 HEAD 는 의미하는 것이 약간 다르다. Remote 에서의 HEAD 는 해당 Repository 의 Default Branch(별도의 설정이 없으면 main, 과거에는 master) 를 의미한다. 하지만, Local 에서의 HEAD 는 현재 위치하는 전체 Git Tree 에서 내가 작업하고 위치하고 있는 노드를 의미한다. Local 에서 HEAD 는 항상 존재하는 것이 아니라 작업하고 위치가 Git Tree 의 끝이 아닐 때만 임의로 생성된다. 즉, 위 이미지에서 newc Branch의 "Create 2.txt File" 가 현재 작업위치라면 Git Tree 의 끝이기 때문에 HEAD 가 생성되지 않는다.

Push

Push 명령어는 Local Branch 를 Remote 로 전송하는 명령어이다. Local Branch 에 Tracking Branch가 존재한다면 자동으로 해당 Remote Branch 로 전송한다.

 

git push <Repository> <Remote Branch Name>
git push origin newc

origin 은 연결되어 있는 Remote Repository 를 의미한다. git remote 명령어를 통해서 확인할 수 있으며 git remote <name> <URL> 을 통해서 새로운 Remote Repository 를 추가할 수도 있다.

 

위 명령어를 통해서 현재 Local Branch 를 origin 이라는 Remote Repository 의 newc Branch 에 전송한다. 이때 newc 라는 Remote Branch가 존재하지 않는다면 자동으로 생성한다.

(자동으로 Branch Tracking 을 연결해주지는 않는다. --set-upstream 옵션을 통해서 Branch Tracking 설정이 가능하다.)

 

Branch Tracking 이 되어있는 상태라면 간단하게 아래 명령어를 통해 Push 가 가능하다.

 

git push

Remote Branch 와 현재 Local Branch 의 상태가 다를 경우 Push 가 실패하는데 이럴 경우 Pull 명령어를 통해 Remote Branch 의 상태를 가져온 후 Push 명령어를 실행해야 한다.

Fetch

Fetch 명령어는 Remote Repository 에는 존재하지만 Local 에는 존재하지 않는 경우 가져오는 명령어이다. 쉽게 말해서 새로고침이라고 생각하면 된다.

 

다른 사람이 Push 한 것을 감지하기 위해 습관적으로 사용하는 것이 좋다.

Pull

Pull 명령어는 Fetch 와 Merge 를 동시에 실행시켜주는 명령이다. 즉, Fetch 를 통해서 Remote Repository 의 변경사항을 감지하고 변경사항이 존재한다면 내 Local Branch 와 Merge 한다. (당연히 나와 연결되어있는 Remote Branch 와 Merge 한다.)

Merge

Merge 명령어는 Local Branch 와 Remote Branch 를 병합하는 명령어이다. A Branch 와 B Branch 를 병합하고 싶다면 아래 명령어를 사용하면 된다.

B~ git merge A
A~ git merge B
# 주체가 되는 Branch가 A, B 중에 어떤 것인지 정하는 것이 중요하다.

위에서 언급했듯이 주체가 되는 Branch를 정하는 것이 중요하다.

 

예를 들어 내가 작업하던 issue9469 라는 Branch 를 main Branch 에 Merge 하고 싶다면 main Branch 로 checkout 해서 git branch issue9469 명령어를 실행해야 한다. git branch main 명령어를 실행하게되면 내 Branch 에 main Branch 가 Merge 되고, main Branch 는 변하지 않고 그대로 유지된다. (이 차이를 기억하자)

Conflict

Merge, Rebase 등 Branch 를 다루다보면 충돌이 발생할 수 밖에 없다. 충돌이란 여러 개의 Commit 에서 동일한 파일의 동일한 라인을 수정한 경우 발생한다. 두 개의 수정 중에서 어떤 것이 올바른 것인지 Git 은 알 수가 없다. 따라서 Git 은 에러를 발생시키며 이를 개발자에게 판단을 맡긴다.

 

Conflict 는 별도의 명령어가 아니라 위에서 설명한 충돌을 의미한다. Conflict 가 발생하면 아래와 같이 표현된다.

 

Conflict 이 발생한 파일을 살펴보자.

 

위와같이 해당 파일에 Conflict 가 발생한 라인이 표시된다. 해당 파일을 수정한 후(Conflict 를 없앤 후!!!) 다시 add, commit, push, merge 하면 정상적으로 merge가 되는 것을 확인할 수 있다.

Reset

reset 명령어는 말 그대로 과거의 특정 시점으로 돌아가는 명령어이다. 주로 Commit 을 취소하기 위한 목적으로 사용되며, 과거로 돌아갔을 때 변경된 파일들은 옵션에 따라 다음과 같은 상태로 남는다.

 

Soft : Uncommited Change -> Staged

Mixed : Uncommited Change -> Unstaged

Hard : Delete (소스코드를 날리는 행위이기 때문에 반드시 주의를 기울여서 사용해야 한다.)

 

Soft와 Mixed는 굉장히 비슷하다. git add 명령어가 수행되기 전으로 가는지 후로 가는지의 차이이다. 따라서 일반적으로는 기본 값인 Mixed를 자주 사용한다고 한다.

Revert

reset 과 유사하게 과거를 바꾸기 위해서 사용되는 명령어이다. 하지만, reset 처럼 특정 시점으로 돌아가는 것이 아니라 특정 Commit 의 반대행를 하는 Commit 을 새로 만드는 명령어이다. 예를 들어 AAAAA 를 BBBBB 로 변경하는 Commit 에 대해서 revert 명령어를 수행하면 BBBBB 를 AAAAA 로 변경하는 Commit 이 새롭게 추가된다.

 

주로 과거의 코드는 수정하고 싶은데 해당 소스코드에 대한 History 는 남겨두고 싶을 때 사용한다.

 

정리하자면 특정 Commit 에서 발생한 일을 번복해주고, 번복한 내용을 통해 새로운 Commit 을 만들어준다고 생각하면 된다. 반드시 reset 명령어와의 차이점을 기억하는 것이 좋다.

Rebase

정말 중요하다고 여러차례 강조한 Rebase 명령어이다. Rebase 명령어는 쉽게 말해 Branch 의 시작지점을 잘라서 새로운 곳의 꼭대기에 붙이는 행위이다. Branch 의 시작점이란 Branch 가 처음으로 쪼개져나온 곳을 말한다.

 

Rebase 명령어는 위 그림과 같이 동작하며 Merge 명령어와의 차이점까지 기억해두는게 좋을 것 같다.

 

 

Rebase 명령어는 이외에도 Commit 을 깔끔하게 정리할 수 있는 기능을 제공한다. Rebase 명령어를 사용할 때 Interactive 하게 진행(-i 옵션)할 수 있고 squash, reword, drop 등을 통해 옮기려는 Branch 에 포함된 Commit 들을 정리할 수 있다.

 

Commit 에는 불필요한(화장실 다녀옴 등등..) 것들이 존재할 수 있고 여러 개의 Commit 을 통합(하나의 기능을 구현하는데 여러 개의 Commit 이 존재하는 경우)하고 싶을 수도 있다. Commit 을 정리하는게 뭐가 중요한지 잘 와닿지 않는다면 다음과 같은 상황을 생각해보자.

Login 기능에 대한 코드를 리뷰하려고 하는데 Login 기능을 구현하는 Commit 이 5개라고 생각해보자... 그러면 총 5개의 Commit 에 대해서 살펴봐야 하고 심지어 하나씩 보게될 경우 동일한 파일의 동일한 라인을 여러번 수정했을 수 있기 때문에 이러한 부분까지 고려해야 한다. 물론 이러한 일을 도와주는 툴이 존재하기는 하지만, 굉장히 번거로운 일임은 틀림없다. Login 기능에 대한 5개의 Commit 을 하나의 Commit 으로 정리해놓는다면 얼마나 편할까...

 

Rebase 에는 꽤 많은 부가적인 명령어를 지원하지만 대표적으로 자주 사용하는 pick, squash, reword, drop 에 대해서만 간단하게 살펴보자. pick 은 가장 기본적으로 설정되며 해당 Commit 을 유지하기 위한 명령어이다. squash 는 해당 Commit 을 바로 앞에 Commit 과 통합하고 Commit 메시지를 새롭게 수정해주는 명령어이다. reword 는 pick 명령어와 유사하지만 Commit 메시지를 수정할 기회가 주어지는 명령어이다. drop 명령어는 간단하게 해당 Commit 을 삭제하는 명령어이다.

 

 

이러한 Rebase 명령어는 굉장히 중요하고 다양한 상황에서 사용될 수 있는데 어떤 상황에서 사용될 수 있는지 살펴보자.

 

첫 번째로 다른 Branch 에서 작성된 코드가 마음에 들어서 해당 Branch 를 통째로 내 Branch 로 가져오고 싶을 때  사용할 수 있다. 내가 작업하고 있는 Branch 가 B Branch 이고, 가져오고 싶은 코드가 A Branch 에 있을 때 두 Branch 는 서로 다르기 때문에 접점이 없다. 하지만 Rebase 를 통해서 이러한 문제를 해결할 수 있다. 물론 충돌이 발생할 수 있겠지만 Merge, Rebase 와 같은 명령어를 사용할 때 충돌은 충분히 발생할 수 있는 문제이다.

(자세한 방법은 조금 더 정리한 후 추가하도록 한다.)

 

두 번째로 뒤에서 치고 올라오는 Merge 를 방지하기 위해서 사용할 수 있다. 과거 시점에서 따진 Branch 를 Merge 하려면 그동안 변경된 사항들 때문에 충돌이 날 확률이 매우 높다. 아니 무조건 난다. 하지만 일반적으로 Pull Request 를 사용하면 이 충돌을 해결해야 하는 사람이 관리자가 된다. 이 코드를 관리자가 작성한 것이 아니기 때문에 직접 이러한 충돌을 해결하기란 거의 불가능에 가깝다.

따라서 Pull Request 를 하기 전에 해당 Branch 를 main Branch 에 Rebase 함으로써 해당 Branch 를 제일 위로 끌어올릴 수 있고, 이 때 발견되는 충돌은 개발자가 직접 해결한 후 Pull Request 를 할 수 있다. 이렇게 하게되면 당연히 관리자가 Merge 를 받아들일 때는 충돌이 나지 않는다. (여러 개의 Pull Request 가 겹쳐있는 경우는 우선 배제한다)

 

세 번째로 불필요한 Commit 을 정리하는데 사용될 수 있다. Rebase 에서 제공하는 Squash 를 통해서 불필요한 Commit 을 통합해 코드리뷰나 Cherry-Pick 을 위한 편의성을 제공할 수 있다. 하나의 기능을 개발한 Commit 이 복잡하고 여러개라면 이를 코드리뷰하기 위해 모두 조합해 확인해야 하고, 그 기능을 가져오기 위해서 수 많은 Commit 을 일일히 Cherry-Pick 해야할 수 있다.

 

 

물론 이외에도 다양한 활용방법들이 존재하겠지만 우선 이정도만 정리하고 넘어가도록 한다. 이후 추가로 알게되는 내용이 있다면 추가적으로 정리할 예정이다.

Cherry-Pick

Branch와 함께 매우 중요하다고 여러번 강조한 명령어이다. Cherry-Pick 명령어는 다른 Branch 의 특정 Commit 을 현재 내가 개발중인 Branch 에 가져오는 명령어이다. Merge, Rebase 를 사용해서도 다른 Branch 의 내용을 가져올 수는 있지만 이렇게 하면 해당 Branch 의 모든 내용(가져오려는 Branch 의 Tree 구조가 복잡하다면 매우 귀찮은 상황)을 가져오게 된다. 나는 단지 Commit 몇개만 가져오고 싶었을 뿐인데...

 

Cherry-Pick 은 굉장히 심플한 기능이지만 매우 강력하다. 모르면 굉장히 많이 돌아갈 수 있는 기능을 제공하기 때문에 반드시 알아두는 것이 좋다. Cherry-Pick 을 사용하는 몇 가지 상황에 대해서 살펴보자.

 

 

첫 번째로 다른 개발자가 구현한 특정 코드가 마음에 들 때 나의 Branch 로 가져오기 위해 사용할 수 있다. Cherry-Pick 의 가장 기본적인 사용방법으로 해당 개발자의 전체 Branch 를 가져오는 것이 아니라 특정 Commit 만 끌어올 수 있다는 점에서 매우 강력한 기능이다.

 

두 번째로 A - B - C - D 순서로 Commit 이 존재한다고 할 때 C 를 빼고 배포하기를 원하는 상황에서 사용할 수 있다. 이 때 Revert 를 통해서 C 를 날려버릴 순 있지만 Revert 가 쌓일 경우 관리가 어려워진다는 단점이 존재한다. 이럴 경우 B 에서 배포를 위한 새로운 Branch 를 만들고 D Commit 을 Cherry-Pick 하는 방법이 있다. 매우 유연하면서 강력한 방법이라고 생각한다.

 

 

Rebase 와 Cherry-Pick 은 굉장히 밀접한 관계를 가지고 있다.

 

Rebase 를 통해서 Commit 을 정리하지 않은 경우 하나의 기능을 위해서 굉장히 여러 번의 Cherry-Pick 을 해야하는 상황이 있을 수 있다. 반대로 하나의 Commit 에 여러 개의 기능이 변경된 경우(예를 들어 #1 Commit 에 Login 기능 구현 + 에러 수정을 수행한 경우와 같은 상황) Login 기능을 가져오기 위해서 해당 Commit 을 Cherry-Pick 했을 때 원하지 않은 에러 코드까지 가져와지는 문제가 발생할 수 있다.

 

실제로 위에서 소개한 상황에서 B 에서 Branch 를 만들고 D Commit 을 Cherry-Pick 을 하는 과정에서 D Commit 에 C Commit 에서 발생한 에러를 수정하는 내용이 추가되어 있다면, C 를 제외한 상황에서 큰 문제가 발생할 수 있다.

 

이러한 문제를 방지하기 위해서 하나의 Commit 은 단일 책임 원칙을 지켜는 것이 좋다. 즉, 하나의 Commit 에서는 독립적인 하나의 기능에 대한 코드만을 수정해야 한다. 이게 굉장히 어렵고,,, 경험을 통해서 얻어지는 것이라고 한다.

 

반응형

'개발 > Concept' 카테고리의 다른 글

[Concept] Configure Service  (0) 2021.07.07
[Git] Git 좀 세울줄 아는 남자-2  (0) 2021.07.05
[Concept] JWT(Json Web Token)?  (0) 2021.06.06
[Concept] MSA (Micro Service Architecture)  (0) 2021.06.05
[Web] SOAP vs REST  (0) 2021.04.04
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함