Git은 컴퓨터 파일의 변경사항을 추적하고 협업을 하기 위한 분산 버전 관리 시스템이다.
여기서 버전 관리 시스템이란 저장소의 파일 변화를 시간에 따라 기록하고, 후에 특정 시점의 버전을 다시 꺼내올 수 있는 시스템이다.
다음과 같은 작업을 수행할 수 있다.
이에 따라 Git은 주로 여러 명의 개발자가 하나의 소프트웨어 개발 프로젝트에 참여할 때 소스코드를 관리하고 협업하기 위해 사용된다.
Git 과 Github
Git은 코드의 버전을 관리하는 시스템이고,
Github는 Git 저장소를 관리하는 클라우드 기반 호스팅 서비스이다.
다시 말해, Git으로 관리되는 파일을 클라우드에 저장하고 관리할 수 있게 도와주는 서비스이다.
Github는 Git의 기본적인 버전 관리 기능에 더해 이슈(Issue) 트래킹, 풀 리퀘스트(Pull Request) 등 협업과 프로젝트 관리를 위한 여러 추가적인 기능들을 제공한다.
Github와 같은 서비스로는 GitLab, Bitbucket, Launchpad 가 있다.
Git은 버전 관리 시스템중에서도 분산 버전 관리 시스템이다.
"분산"이라는 단어가 버전 관리 시스템에 붙은 이유는 어떻게 데이터를 저장하고 관리하는지와 관련이 있다.
위 그림은 중앙 집중식 버전 관리 시스템(CVCS, Centered Version Control System)과 분산 버전 관리 시스템 (DVCS, Distributed Version Control System)에서 파일을 수정할 때의 작동방식이다.
중앙 집중식 버전 관리 시스템은 한 개의 중앙 서버에 모든 파일과 변경 이력을 저장한다.
따라서 저장소의 파일을 수정하려고 할 때는 중앙 서버에 연결된 상태에서 해당 파일의 최신 버전을 가져오고 파일을 수정한 후에 다시 중앙 서버에 수정 내용을 커밋(commit)하여 중앙 저장소에 반영한다.
분산 버전 관리 시스템은 여기서 좀 더 나아가서 중앙 서버를 둠과 동시에 각 작업자의 로컬 컴퓨터에도 그 중앙 서버의 저장소를 그대로 복제한 로컬 저장소를 둔다. 작업자는 이 로컬 저장소에서 파일을 수정할 수 있다.
이에 따라 다음과 같은 이점이 있다.
CVCS보다는 직관적으로 이해하기 힘들지만 유연하고 빠른 버전관리를 할 수 있어 소스코드 버전 관리 시스템의 표준으로 자리잡았다.
구성요소는 다음과 같다.
하나씩 자세하게 알아보자.
프로젝트의 파일과 각 파일의 변경 이력을 저장하는 공간이다. 주로 “repo”라고 줄여서 표현된다.
다음과 같이 두 종류로 나뉜다.
clone : 원격 저장소를 작업자의 로컬 컴퓨터로 복제하는 작업이다. 이렇게 되면 작업자의 컴퓨터에 클론한 원격 저장소가 등록된 로컬 저장소가 생성된다.
origin : 원격 저장소를 로컬에 clone 하면 생성되는 로컬 저장소에 연결된 원격 저장소의 기본 이름이다.
fetch **** : 원격 저장소에서 최신 변경 사항을 로컬 저장소로 가져오는 작업이다.
pull ** : 원격 저장소에서 최신 변경 사항을 로컬 저장소로 가져오는 fetch ** 작업을 함과 동시에 로컬 저장소의 브랜치와 병합하는 작업이다. 즉, fetch **** + merge 이다.
push ****: 로컬 저장소의 변경 사항을 원격 저장소에 업로드하는 작업이다.
커밋은 Git 저장소의 기록 단위로, 파일이나 폴더의 변경 사항을 저장하는 행위를 말한다.
각 커밋은 저장소의 변경된 내용을 스냅샷(Snapshot)으로 캡처하고, 이러한 스냅샷들이 모여 저장소의 변경 이력을 구성한다. 여기서 스냅샷은 다음 그림과 같이 차이점만을 기록하는 델타(Delta)방식과는 달리 기존 내용에 수정된 내용을 포함한 전체를 기록하는 것이다.
각 커밋은 고유한 ID를 가지며, 누가 언제 어떤 변경을 했는지의 정보를 가지고 있다.
이 커밋 ID는 커밋 객체에 포함된 다양한 데이터들(커밋 시 생성되는 Git 객체, 타임스탬프, 커밋메세지 등등)을 기반으로 SHA-1 해시 알고리즘을 사용하여 생성되어, 커밋의 내용이 조금이라도 달라지면 완전히 다른 값으로 변경되므로 다른 커밋들과 구분될 수 있다.
코드를 수정하고 커밋을 하게 되면, Git은 커밋 객체, 트리 객체, 블롭 객체를 생성한다.
예를 들어 초기 커밋(Initial commit)이 되어있고 README.md 파일이 있는 디렉토리에 같은 내용을 가진 hello.txt , world.txt 파일 2개를 생성하고 커밋한다면 다음과 같이 Git 객체가 3개 생성된다. (new! 표시된 객체)
hello.txt , world.txt 파일이 새로 생성되어 새로운 트리객체 92ec2 가 생성되었다.
변경사항이 없는 README.md 는 이전 블롭객체를 그대로 참조하고,
hello.txt , world.txt 는 각기 다른 이름으로 두 개의 항목을 가지지만 내용이 같으므로 같은 블롭 객체 5b1d3 을 참조한다.
이러한 커밋 저장 방식으로 Git은 각 커밋을 독립적인 스냅샷으로 처리하고,
데이터 무결성을 유지하면서도 저장되는 데이터의 크기를 최소화한다.
Git 저장소를 새로 생성하면 하나의 main 이라는 이름의 브랜치가 기본적으로 생성된다.
(기존 master 였던 기본 브랜치명은 노예제도를 연상시킨다는 이유로 main 으로 변경되었다. )
이 브랜치를 기본 작업 라인으로 삼고, 작업을 다른 방향으로 분기시키려고 할 때 새로운 브랜치를 생성하여 사용한다.
예를 들어, 기존 코드에서 기능을 추가해야 할 부분이 생기면 작업하던 브랜치에서 feature 브랜치를 생성하여 해당 브랜치에서 수정작업을 한다. 이렇게 분기되는 과정이 나무의 가지처럼 갈라진다고 하여 브랜치라고 한다.
위 그림과 같이 기본 브랜치는 main이며, 안정성을 위해 브랜치를 분리하거나 (dev), 새로운 기능 개발( feature )이나 버그 수정을 위해 별도의 브랜치를 생성할 수 있다. 작업자들은 이렇게 생성한 브랜치에서 기능 추가, 버그 수정, 테스트 등을 독립적으로 진행하여 커밋 한 후 main 브랜치로 다시 병합한다. 이렇게 브랜치를 생성하고 병합하는 워크플로를 통해서 유연하게 코드를 관리하면서 협업할 수 있다.
merge (병합): 두 브랜치를 하나의 브랜치로 결합하는 작업이다. 각 브랜치의 변경사항들이 합쳐진다.
위의 그림에서 feature 브랜치는 dev 브랜치로부터 분기되었고 수정을 거쳐 다시 dev 브랜치와 병합되었다.
checkout , switch : 현재 작업하고 있는 브랜치를 다른 브랜치로 전환하는 작업이다. 전환하고자 하는 브랜치의 작업 상태를 현재 작업하고 있는 작업 디렉토리에 적용한다.
예를 들어 dev브랜치에서 작업 중이고 feature 브랜치로 전환하고자 한다면 feature 브랜치로 checkout ****한다고 표현한다. 그리고 내가 작업 중이었던 작업 디렉토리는 feature 브랜치의 작업 상태가 반영된다.
stash : 현재 작업 중인 디렉토리의 수정된 파일과 스테이징된 변경 사항들, 즉 아직 커밋이 되지 않은 변경사항들을 임시로 보관하고, 작업 디렉토리를 마지막 커밋 상태로 되돌린다. 현재 작업을 커밋하고 싶지 않지만, 현재 상태를 잠시 보관해야 할 때 사용한다. 예를 들어 현재 작업중인 브랜치에서 다른 브랜치로 checkout 해야할 때 현재 작업중인 내용을 커밋해야 checkout 이 가능하므로, 작업중인 내용들을 stash 하고 브랜치를 전환한다.
태그는 작업 지점 중 특정 지점을 표시하는 데 사용된다.
이는 특정 커밋에 태그를 생성함으로써 이루어진다. 이렇게 생성된 태그의 태그명을 사용하여 특정 커밋으로 체크아웃할 수도 있다. (해당 커밋 시점의 파일과 코드 상태로 돌아감)
태그에는 다음과 같이 두 가지 유형이 있다.
태그명은 여러 내용이 올 수 있겠지만 일반적으로 배포 버전을 나타낸다.
버전을 나타낼 때 일반적으로 유의적 버전 방식을 사용한다. 이 방식은 버전 번호를 x.y.z-i+m 형식으로 표현한다.
각 자리가 의미하는 바를 자세히 알아보자.
x : Major(메이저) 버전으로, 구 버전을 지원하지 않는 수준의 API 변경이 있을 경우 올린다.
y : Minor(마이너) 버전으로, API의 기능이 추가되었지만 기존 버전을 여전히 지원하는 수준으로만 변경이 있을 경우 올린다.
z : Patch(패치) 버전으로, 기존 기능의 버그를 고쳤을 경우 올린다.
i : Pre-release identifier(프리 릴리즈 식별자)로, 정식 릴리스 전의 초기 버전을 나타내는 데 사용된다.
alpha , beta , rc (Release Candidate)와 같은 버전을 나타낼 때 사용된다.
다음과 같이 작성할 수 있다.
1.0.0-alpha, 1.0.0-beta.1 , 1.0.0-rc.3
프리릴리스 버전은 정식 릴리스보다 낮은 우선 순위를 가진다.
m: Build metadata(빌드 메타데이터)로, 버전에 대한 빌드 번호나 날짜와 같은 추가적인 정보를 제공한다.
버전 간의 우선순위를 판단하고자 할 때 무시된다. (메타데이터만 다른 두 버전의 우선순위는 같다.)
1.0.0-alpha+001, 1.0.0+20130313144700
유의적 버전 방식은 다음과 같은 규칙이 있다.
이 외에도 더 많은 규칙이 있다. https://semver.org/lang/ko/ 에서 자세한 내용을 확인할 수 있다.
이러한 유의적 버전을 사용하여 다음과 같이 태그명을 작성할 수 있다.
태그에 작성할 내용이 버전임을 명시하기 위해 버전 앞에 **v 접두어를 사용**한다.
변경사항을 커밋할 때, 다음과 같은 3가지 단계를 거치게 된다. (각 단계마다 불려지는 명칭이 여러 개 있다.)
➡️ 사용자가 특정 브랜치나 커밋을 체크아웃하면 Git은 해당 커밋에 해당하는 파일 상태를 깃 디렉토리의 데이터베이스에서 가져와 워킹 트리에 적용한다.
➡️ 워킹 트리에서 파일들을 수정하고 그 중 커밋할 파일을 스테이징 영역에 올린다.
➡️ 스테이징 영역에 있는 파일들을 커밋해서 깃 디렉토리에 스냅샷으로 저장한다.
스테이징 영역을 사용하는 이유
워킹 트리에서 파일들을 자유롭게 수정하고, 커밋 시에는 원하는 파일들만 스테이징 영역에 올려 커밋할 파일과 그렇지 않은 파일을 분리할 수 있다. 명확한 커밋을 위해서는 변경 사항을 커밋에 맞게 모아놓아야 하는데, 이 역할을 스테이징 영역이 해준다.
이러한 커밋 과정이 파일 관점에서는 다음과 같이 4가지 상태로 나뉜다.
➡️ 파일을 스테이징 영역에 추가하는 명령어를 사용하여 Staged 상태로 변환시킬 수 있다.
➡️ 파일 내용을 수정하면 Modified 상태로 변경된다.
➡️ 수정한 파일을 스테이징 영역에 올리면 Staged 상태로 변경된다.
➡️ 해당 영역에 있는 파일들을 커밋한다. 커밋에 포함된 파일들은 모두 Tracked 상태가 된다.
파일은 커밋되었기 때문에 최신 커밋에서 변경되지 않은 상태이므로 Unmodified 상태가 된다.
https://git-scm.com/book/ko/v2
https://blog.dnd.ac/types-of-git-branch/
https://www.nagarelab.com/en/3073
https://nozeroslope.tistory.com/186
https://www.oreilly.com/library/view/version-control-with/9781449345037/ch04.html
https://johngrib.github.io/wiki/semantic-versioning/