VCS, git 내부 동작

HongBoogie·2024년 8월 8일

VCS

  • 버전 관리 시스템, 통칭 VCS는 파일의 변화를 시간에 따라 기록했다가 나중에 특정 시점의 버전을 다시 꺼내올 수 있는 시스템

특징

  • 각 파일을 이전 상태로 되돌릴 수 있음
  • 수정 내용을 비교해 볼 수 있음
  • 누가 문제를 일으켰는지 범인 색출 가능

Git

  • Git은 VCS의 한 종류로, 빠른 수행 속도에 중점을 둔다.
  • 어딘가 사용이 귀찮아보이는 git은 개발자에게 있어서 필수인데, 동료 개발자와의 협업에 있어 많은 편리성을 제공하기 때문이다.

객체 종류

  • Git은 버전 관리에 필요한 데이터를 주로 객체라는 개념으로 표현하고, .git/objects 디렉토리에서 관리한다.
  • Git은 Key-value Store로 이러한 객체들을 관리하는데, Git에서 Key는 객체의 내용에 대한 SHA-1 해시 값으로 만들어지고 Value는 객체의 내용이다.
  • Value 객체 만들기 흐름은 다음과 같다.
    • 객체 내용을 SHA-1 해시화한 값을 Key로 한다.
    • 객체를 zlib 압축 한 후 .git/objects/아래에 Key이름으로 파일로 저장
    • 검색 효율성을 위해 Key의 처음 2자로 서브 디렉토리를 자른다.
  • 종류로는 네가지가 있는데,
    • blob: 파일 정보가 들어 있는 객체(백업)
    • tree: 디렉토리 정보가 들어 있는 객체
    • commit : 커밋 정보가 들어 있는 객체
    • tag: annotated tag 정보가 들어 있는 객체
  • Git 객체는 객체의 종류나 크기 등의 메타 데이터가 먼저 기록되고 그 뒤에 컨텐츠가 계속된다.
  • | 구분 | 설명| | :---- | :--------------------------- | |개체 유형 | blob/tree/commit/tag | |객체 크기 | 숫자(단위는 byte) | |객체 내용 | 객체 유형에 따라 다름 |

blob 객체

  • blob 객체는 파일의 실제 백업에 해당하는 객체이다. blob 객체는 각 파일의 차이가 아닌, 어느 시점에서 파일의 내용 그 자체를 기록하고 있다.

tree 객체

  • tree 객체는 디렉터리의 정보를 보관 및 유지한다. tree 객체가 만들어지면 그 디렉토리에 존재하는 파일과 그 버전의 blob 해시값이 기록된다. 이 Key에서 해당 blob 객체에 액세스할 수 있다.
  • 루트 디렉터리의 트리 객체가 있다면, 거기부터 추적해 레포지토리의 모든 파일에 액세스가 가능하다.

Commit 객체

  • commit 객체가 포함하고 있는 값은
    • 리포지토리의 루트 디렉터리에서 tree 객체의 해시 값(Key)
    • 상위 commit 해시 값
    • committer와 author 타임 스탬프, 이름, 이메일 주소
    • 커밋 메시지

이 있다. 이 중 하나라도 변경하면 다른 commit 해시가 되므로 커밋은 고유하게 관리된다. 또한 부모 커밋의 해시 또한 포함하므로 변조에 강하다.

Commit 구조 실현하기

  • 커밋에는 크게 세 가지 단계가 있다.
    1. 코드 편집
    2. 편집한 코드를 추가
    3. commit

2단계에서 편집한 코드가 add(staging) 된다. 이때 Git 내부에서 커밋에 포함할 파일을 index에 등록한다. 인덱스의 정보는 .git/index에 기록되어 있고, 객체와 같이 봐야 한다.

git ls-files --stage
100644 30d1b6685b445e01849a96630bd9c956056af041 0   README.md
100644 b0da5ab945eb4b38ffad0ec1ebbee0f5db01ba97 0   desc/intro1.txt
100644 e65940cccf4aa6b5da4974d0105cb45aeaade255 0   desc/intro2.txt
100644 363cb224f29afec235f182ca49b3baac7a55168b 0   intro.txt
  • index에는 파일 종류 + 권한, blob 해시, 충돌 플래그, 파일 이름이 출력된다. index는 현재 참조하는 버전의 모든 파일에 대한 참조가 있다.

add

  • add 했을 때에 일어나는 내부 동작 방식은
    • 색인 업데이트
    • blob 객체 생성

가 있다. 이미 있는 파일을 편집한 경우에도 새 blob 객체가 생성되고 해시값으로 다시 변경된다.

commit

  • commit을 했을때 일어나는 내부 동작은 다음과 같다.
    • index에서 트리 객체 생성
    • commit 객체 생성
    • HEAD를 새로운 커밋 해시로 다시 작성
  1. 첫번째로 tree 객체를 생성한다. commit하면 차이 뿐만 아니라 리포지토리의 루트 디렉토리를 포함한 모든 디렉토리의 tree 객체를 구축한다. 다만 이 때, 인덱스에 변경이 있던 부분만이 새로운 blob 및 tree 객체에 재기록 되어 그 이외의 참조가 변하지 않는 부분은 그대로 이용한다.
  2. 다음은 Commit 객체를 작성한다. commit 객체는 리포지토리의 루트에 해당하는 트리 객체를 참조하므로 거기에서 전체 리포지토리를 따라갈 수 있으며 commit 시점의 리포지토리 상태를 재현할 수 있다. 또, 참조하고 있는 blob 객체(파일)도 차이 백업이 아니고, 변경이 있던 파일의 풀 백업이 관리된다. 즉, 커밋이란 특정 시점의 스냅샷이 관리된다고 볼 수 있다. 차이를 기록하고 있는 것이 아니기에, 어떤 커밋(버전)으로 이동할 때에, 하나하나 커밋을 거슬러 차이를 적용하는 것이 아니다. Git이 checkout 등에서 다른 커밋으로 넘어갔을 때 순식간에 그 상태를 복원할 수 있는 것이 이 때문이다. Git에서는 버전 전환할 때 걸리는 시간은 이력의 갯수에 의존하는 것이 아니라 변경된 파일 수에 의존하는 것이다.
  3. 마지막으로, 현재 참조하는 커밋을 가리키기 위해 .git/HEAD에 필요에 따라 다시 쓴다.
cat .git/HEAD
ref: refs/heads/master

HEAD가 특정 커밋의 해시가 아니라 위와 같이 브랜치를 참조하고 있으므로 .git/refs/heads/master가 참조하고 있는 커밋 해시를 다시 쓴다.

여기까지가 commit의 일련의 처리과정이다.

readFileSync

  • Node.js에서 파일을 읽는 함수
  • readFile(fileName, option, callback) ⇒ 비동기
  • readFileSync( 파일명(파일 경로), 옵션) ⇒ 동기

writeFileSync(file, data[, options])

  • fs파일에 동기적으로 데이터를 쓴다.
  • 파일이 존재하면 덮어쓰고, 없으면 만든다.

Path

path 모듈은 폴더와 파일의 경로를 지정해주는 모듈이다.

const path = require('path');

Path 모듈 메소드

path.join(경로, .. .): 여러 인자를 넣으면 하나의 경로로 합쳐준다.

path.resolve(경로, .. .): path.join()과 비슷하지만 차이가 있다.

path.parse(경로): 파일 경로를 root, dir, base, ext, name으로 분리한다.

path.format(객체): path.parse() 한 객체를 파일 경로로 합친다.

path.relative(기준경로, 비교경로): 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알려준다.

join 과 resolve 차이점

resolve 는 / 를 절대경로로 처리, join 은 상대경로로 처리한다.

profile
개발이 즐겁고 노는게 즐거워요

0개의 댓글