본 Git으로 개발 생산성 올리기 시리즈는 현업에서 Git을 사용하며 가장 유용하게 사용했던 명령어 및 개발 생산성을 올릴 수 있었던 경험을 공유하기 위해 제작되었다.
0편에서는 본격적인 시리즈에 들어가기 전, 기본적인 Git에 대한 간단한 소개 및 동작원리, 그리고 Git에 관한 흥미로운 글을 소개해보려고 한다.
Git을 사용한 경험이 한번이라도 있다면, 충분히 이해하실 것으로 기대합니다 :)
Git은 분산 버전 관리 시스템(DVCS)으로, 소프트웨어 개발 프로젝트에서 소스 코드의 변경 사항을 추적하고 여러 사용자 간의 작업을 조율하는 데 사용되고 있다. 분산 버전 관리 시스템인 이유는, 원본 코드는 원격 레포지토리에 공유되어 있고, 각자의 로컬 환경에 로컬 코드가 분산되어 있기 때문이다.
참고로, Git은 Linux를 만든 리누스 토르발스에 의해 2005년에 개발되었다.
Git을 사용하는 주된 이유는 팀 내 또는 개인 프로젝트에서의 소스 코드 관리를 용이하게 하기 위함이다. Git은 변경 사항을 효율적으로 추적하고, 버전을 관리함으로써 여러 버전의 문서를 안전하게 보관하고 필요시 원하는 버전으로 쉽게 되돌아갈 수 있게 한다.
SVN은 중앙집중식 버전 관리 시스템(CVCS)이며, 모든 파일의 버전 관리 정보가 중앙 서버에 저장됩니다. 위에서 언급한 바와 같이 Git은 분산 버전 관리 시스템으로, 각 사용자가 전체 저장소의 복사본을 로컬에 보유하며, 이로 인해 네트워크 상태에 구애받지 않고 작업을 진행할 수 있다. 브랜칭이나 병합과 같은 유연성도 우수하다.
- 구성
- 동작원리
- Commit은 어떻게 이루어질까?
- 충돌이 왜 이러날까?
Working Directory: 실제 파일들이 위치한 디렉토리로, 사용자가 현재 작업하고 있는 공간이다.
Staging Area: 커밋하기 전의 준비 영역으로, Git에서 변경사항을 임시로 저장하는 곳이다.
ex) git add를 사용하여 파일 변화를 스테이지 위에 올린다
Local Repository: 사용자의 PC에 위치한 저장소로, 프로젝트의 모든 버전 정보를 포함합니다.
ex) git commit을 하여 스테이지 위의 파일 변화들을 로컬 레포지토리에 기록한다
HEAD: 현재 체크아웃된 커밋을 가리키는 포인터로, 가장 최근의 작업 상태를 나타낸다. 보통 커밋 기록을 볼 때, HEAD 0XEF56 와 같은 로그를 볼 수 있는데, 현재 0XEF56 라는 커밋을 HEAD가 가리키고 있고, 이가 최근 커밋이라는 것라고 생각하면 된다. 후에, cherry-pick이나 해당 커밋으로 돌아가기 위해서는 해당 값(0XEF56)을 기반으로 진행되게 된다.
Remote Repository: 로컬과 분리되어 있는 Git원격 저장소
ex) git push를 하여 원격저장소로 커밋 결과들을 올린다
사실 Git을 처음 사용하기 시작했을 때, 많은 고민을 하지 않았었다. add, commit, push, pull만 잘 동작하면, 크게 무리가 없을 것 같다고 생각했고 실제로도 필요가(?) 없었다. 큰 장벽을 만나기 전까지....
그래서 한번 쯤은, 하루 이틀 정도 Git의 작동원리나 사용사례에 대한 자료들을 찾아보며 깊게 이해해 보시길 추천드린다.
사실 기본적인 동작원리는 위 구성 파트에서 설명이 다 이루어졌다.
따라서, 동작원리와 별개로 Git을 이해하며 개인적으로 중요하게 생각했던 개념 몇 가지를 설명하려고 한다.
위에서는 하루 이틀이라고 칭했지만, 나는 사실 꽤 오랜기간 Git에 관하여 제대로 이해하지 못했다. 지금도 완벽하다고 생각하진 않고, 누가 설명을 요청하면 아직도 헷갈리는 개념이 많아 쉽지 않다. 하지만, 기본적인 이해로 넘어가는 허들을 극복하는 순간은 Commit에 대한 이해를 했을 때였다.
기본적으로, 버젼관리에서의 핵심은 파일 변화이다. 수정, 삭제, 추가 등의 행위를 말하는 것이다. Git에서는 이것을 측정 및 감지하는 단위가 Commit인 것이다.
앞서 말한 SVN은 이전 파일과의 차이를 추적하며 동작이 되는 구조인데, Git은 차이 뿐만 아니라 전체 코드에 대한 기록도 유지된다. 이는 용량적인 문제가 발생하는 것처럼 보이지만, SVN은 처음 작업이 시작할 때부터의 변경사항을 모두 추적해야 하므로 불필요한 연산이 발생한다. Git의 커밋 개념으로 비교하자면, 처음 커밋부터 모두 체크해야한다는 것이다. 허나, Git은 알다시피 직전 커밋과의 비교만 진행된다. 그리고, 파일 변화가 이루어지지 않은 작업물들은 일종의 심볼릭 링크 형태로 저장되기 때문에, 용량적인 문제를 야기하지 않는다.
정리하자면 Git의 버전관리는 이전 커밋과의 차이점만 비교하며 이루어지고, 일일이 코드의 차이점을 연산하지 않고 스냅샷(=커밋)을 찍어 비교한다.
무식(?)하지만 효율적인 방식으로, 오직 커밋 단위로 Git은 동작하기 때문에 이에 대한 이해가 필수적이라고 생각한다.
아마 Git이 우리의 머리를 아프게 하는 순간은 병합을 시도했을 때 충돌이 일어났을 때일 것이다.
충돌이 일어나는 이유는 간단하지만, 해결법은 다양할 것이다. 예를 들어, 커밋을 되돌리거나, 충돌난 부분을 직접 수정하거나, 극단적(?)으로 force 명령어를 사용하는 것이다. 개인적으로, force 명령어는 개인만이 코드를 관리하지 않는 이상 추천하지 않고 직접 수정하는 것을 추천한다 :)
일단 충돌이 일어나는 이유는, 쉽게 말하면 같은 코드를 수정했기 때문이다.
일반적으로, 병합을 시도할 때 3가지 경우의 수가 존재한다. 병합을 시도하는 브랜치를 A와 B로 설명하려고 한다.
충돌이 일어나는 경우는 3번째 경우이다.
A (변경사항 a) + B (변경사항 a') = 변경사항에 대한 코드가 겹치기 때문에 병합 못함
이런 생각을 할 수 있다. 코드 출처가 같긴 하지만, 서로의 코드에 영향이 가지 않고 기존 코드 수정 없이 코드가 추가 된 것인데도 충돌이 왜 발생해?
앞에서 설명했던 것과 같이 Git은 커밋을 단위로 변화를 감지하고 이는 스냅샷을 비교하는 것이다. 개행을 했든 주석을 추가했든, 변화는 변화일 뿐이다.
그러니, 침착하게(?) 충돌난 부분을 체크하고 직접 수정하는 것이 가장 빠른 해결책으로 생각된다 ㅎ
당연히, 충돌이 해결되면 2번의 경우와 같아지므로 Merge-commit이 발생한다.
아래는 Azure 기술 블로그에 병합 관련하여 포스팅된 글이다. 추가적으로 Git에 대한 이해를 더할 수 있는 좋은 글들이 많으니 참고 바랍니다 :)
https://learn.microsoft.com/ko-kr/azure/devops/repos/git/merging?view=azure-devops&tabs=visual-studio-2022
초기 Git 개발에 참여했던 Felipe Contreras가 Why is git pull broken?라는 자극적인 제목으로 작성한 글을 우연히 보게되었는데 인상깊어 소개합니다ㅎ
해당 글의 주된 내용은 git push와 git pull은 git 작동원리에서는 반대의 의미를 가지고 있지 않고, git pull의 지나친 사용을 지양해야 하는 이유를 상세하게 설명합니다. git pull을 습관적으로 사용하고 (바로 나..ㅎ) rebase 작동원리를 이해하고 싶은 분께 추천드립니다 :)
이전에 관련 세션을 준비했다가 삑난(?) 경험이 있어서 준비했던 내용에 대해 시간이 된다면 아래 포스팅을 추가로 올릴 예정입니다 :)
1. Git으로 개발 생산성 올리기 1편 (feat. git stash를 알고 삶의 질이 올라갔다?)
2. Git으로 개발 생산성 올리기 2편 (feat. 코딩 스타일 관리 자동화를 위한 Git Hook 도입기)
3. Git으로 개발 생산성 올리기 3편 (feat. Github Action with Terraform)
참고로, 관련해서 발표 준비를 위한 테스트 용도로 만들었던 git repository link는 다음과 같습니다. 후에 포스팅을 이어나간다면, repo는 cleanup해서 새로 생성해보겠습니다 ㅎ
https://github.com/idle-danie/git_session
우연히 찾게 되었는데, 전체적으로 독자 입장에서 글을 이해하기 쉽게 잘 쓰시네요. 마지막에 올려주신 Why is git pull broken? 링크 글은 참으로 흥미롭네요ㅎ 별 생각 없이 git pull을 습관적으로 하는 분들이 많을텐데, 많이들 읽어보시면 좋겠어요. 온보딩 콘텐츠로 넣어야 겠네용