CS

깃의 자료구조에 대하여 (그동안의 무지를 반성하며)

 

부끄러운 일이지만 git은 정말 간략하게만 알았던 것 같다. 

깃을 대충 알면 어뜨케 되느냐,,,

급하고 안 되니까 구글링해서 쉘 커맨드 복붙,,,

충돌나면 깔끔하게 날리고 다시 받아버리기,,,

다른 사람한테 zip 파일로 코드 받아서 복붙하기,,,

--> 강의에 나온 이야기인데 놀랍게도 한 번씩 다 해봤던 일;;

 

이런 짓을 몇 번 하다보면 아 정말 깃을 다시 배워야겠다 라는 생각이 드는데 

오늘이 바로 그날. 

영상 후반부는 많이 아는 내용이었는데, 의외로 초반부가 많이 생소했다.

깃의 데이터구조에 대해서 한 번도 고민해보지 않았기 때문.

오히려 기초를 알고 나니까 헷갈리던 것들이 좀 더 명확히 이해가 되었다.

 

깃은 형상관리 툴 중의 하나로 내 프로젝트의 히스토리를 관리할 수 있다. 

다르게 말하자면 변경될 때마다 해당 버전을 snapshot 찍듯이 저장해둘 수 있다. 

파워포인트 작업을 하다보면 _최종.pptx _진짜진짜최종.pptx가 카톡을 이리저리 왔다리갔다리 하는데 

git에서는 파워포인트처럼 선형적으로 스냅샷을 저장하고 관리할 수 있는 게 아니라 

병렬적으로 관리가 가능하다.

 

그러니까 일반적인 문서작업을 할 때는 A가 수정하고 B가 수정한 버전을 우리가 시간 순서대로 받아서 

일일히 복붙해서 그 자리에 끼워넣어야한다. 하지만 깃은 해당 버전의 차이를 확인하고 자동으로 합칠 수 있다.

그렇다고 완전히 새롭고 다른 두 파일의 차이까지 합치지는 못한다. 왜? 기준이 없으니까.

뭘 기준으로 변경되었는지 알아야지, A가 변경한 거랑 B가 변경한 거랑 차이를 알 거니까

그래서

 

깃은 이를 위한 자료구조로 

DAG(Directed Acyclic Graph) 자료구조를 채택한다.

해당 자료구조는 부모와 자식이 있으면서 자기자신에게 돌아올 수 없는 비순환적 그래프(혹은 트리)이다.

자식은 여러개일 수 있다.

 

먼저 깃의 데이터 모델부터 배우는 게 흥미로웠다.

깃에서의 오브젝트란 blob(file), tree(folder), commit가 있다.

// a file is a bunch of bytes
type blob = array<byte>

// a directory contains named files and directories
type tree = map<string, tree | blob>

// a commit has parents, metadata, and the top-level tree
type commit = struct {
    parent: array<commit>
    author: string
    message: string
    snapshot: tree
}
type object = blob | tree | commit

오브젝트들은 모두 sha-1(hash function)으로 도출되는 id 값으로 저장/로딩된다. 

-> 나는 깃오브젝트로 커밋 정도만 생각했다. 또 커밋 정도만 아이디가 있다고 생각했다! git add나 git commit 할 때 옆에 보이니까! 근데 파일이나 폴더도 다 깃 오브젝트로 관리되었고, 관리를 위한 아이디가 있었군

-> 커밋이 스냅샷으로 갖고 있는 건 바로 트리! 커밋을 조작하는 건 바로 깃의 자료구조(dag)를 조작하는 것을 말한다.

objects = map<string, object>

def store(object):
    id = sha1(object) // hash function
    objects[id] = object 

def load(id): // object는 이 id값으로 구별된다.
    return objects[id]

sha-1 해쉬값을 통해 나오는 값은 인간이 읽기 힘든 40여개의 16진수 문자열이기 때문에 references를 사용한다.

유명한 레퍼런스는 HEAD 되시겠다. 현재 위치를 가리키는 특수한 REFERENCE이다.

references = map<string, string>

def update_reference(name, id):
    references[name] = id

def read_reference(name):
    return references[name]

def load_reference(name_or_id):
    if name_or_id in references:
        return load(references[name_or_id])
    else:
        return load(name_or_id)

또 깃 명령어들은 모두 이 레퍼런스를 변경하거나 혹은 오브젝트의 데이터를 변경하는 일이다.

이게 큰 깨달음이었는데 예를 들어

난 git checkout <branch>가 익숙해지면 git checkout <revision>을 생각 못할 때가 있다.

branch도 reference이고 revision도 reference라는 것을 알면 덜 헷갈린다.