information manage from hell - 리눅스 토발츠 (Linux & Git의 창시자)
git이 나오기 전에도 버전관리가 되고 있지 않았던 것은 아니다. 기본적으로 파일 혹은 문서 관리를 할 때, 항상 기존 파일을 이전의 형태로 수정하거나, 이전의 파일 및 문서를 저장해놓았던 행위는 계속되어 왔다.
가장 대표적인 예가 우리도 일상에서 사용해 왔던 다음과 같은 경우이다.
파일1.txt
파일2.txt
...
파일n.txt
파일_최종.txt
파일_진짜_최종.txt
...
위와 같이 파일을 복제하여, 새로운 버전으로 파일을 관리하는 형태를 수동 버전 관리라고 한다.
문제는 수동 버전 관리가 아래와 같이 특징을 가지고 있었다는 점이다.
따라서, 버전 관리를 자동화한 시스템이 등장하게 되었는데, 기존의 시스템이 유료화 되면서, 리눅스 토발츠가 만든 버전 관리 시스템이 git이다.
우리가 살펴볼git은 가장 대표적으로 사용되는 버전관리 시스템으로 아래와 같은 특징을 지닌다.
가장 대표적인 버전 관리의 목적은 아래와 같다.
오름차순으로 중요하며, (버전 관리가 가장 중요), 따라서 위의 순서에 따라 git을 사용하는 방법을 알아본다.
버전 관리의 가장 좋은 점은 우리가 찾지 못한 버그에 대해 이전 버전으로 돌아가, 어디서 버그가 발생했는지 판단할 수 있게 한다는 점이다. (사실 이렇게까지 돌아가는 경우는 많지 않지만, 한번 발생하면 해볼 만 함.)
➡️ 즉, 버전 관리의 가장 큰 핵심 가치는 디버깅이다. (문제 해결 시간이 최적화 됨.)
디버깅을 목적으로 하기에, 하나의 단위 작업이 홀로 실행 가능하고, 기능적으로 의존적이지 않도록 단위 작업 단위로 버전 관리를 하면 좋다. (그래야 디버깅이 유리)
아래는 git 버전 관리에서 많이 사용되는 용어들이다.
.git : repository (저장소)
working directory : 작업 공간 (저장소를 제외한 공간)
add : commit 대기 상태로 바꿈 (stage area에 추가)
commit 대기 상태 : stage area, cashe 등으로 통칭commit : stage area에 있는 것을 버전으로 바꾸어 repository에 저장
commit 시 email, name, message, time 등을 합하여 SHA1으로 hash 코드를 만들고, 그 hash 코드(40자)를 ID로 사용
분산 버전 관리 시스템이기에 hash 코드로 ID화 (해당 ID를 commit_ID라고 표현)
main에 이전 버전이 존재하는 경우, 기존 commit 과정에서 main 내용을 parent로 추가, hash코드화 해서 ID로 저장
commit_ID는 6자 정도만 사용해서 checkout 으로 해당 버전으로 이동할 수 있다.
main : 마지막 작업을 가리키는 branch
➡️ stage area는 변경 사항만을 반영하고, commit시 stage area의 전체를 버전화 한다.
commit 후에도 stage area는 계속해서 유지된다. (기존의 add 및 commit 한 내용들을 전부 기록하여 유지)
➡️ 즉, 각각의 version은 그 버전이 만들어진 시점의 스냅샷이다.
새로 생성된 commit ID는 head가 가리키는 곳에 저장되는데, 일반적으로 head는 main을 가리켜, main에 저장한다.
checkout 명령어롤 통해 head가 가리키는 버전을 바꿀 수 있다.main으로 checkout 해야 한다. git checkout main) 아니면, 새로운 commit 시 main은 그대로 두고, head만 이동main이 가리키는 버전을 바꾸고 싶으면, reset 명령어를 사용한다. (git reset은 head가 가리키는 브랜치(branch)의 위치를 바꾼다.)
head가atteched head states(브랜치를 가리키고 있는 상태 - ex. main을 가리킴)이면, branch를 타 버전을 가리키도록 이동deteched head states(head가 직접 가리킴)면, 그냥 checkout 처럼 작동.➡️ git은 불멸성을 가져 버전을 모두 저장한다. 따라서, 내가 main 이후에 무언가를 만들고, 다시 main으로 돌아와서, 해당 id로 다시 checkout하면 돌아갈 수 있다. (id를 기억해야 가능)
checkout을 통해 deteched head states 상태가 되려고 하면, git에서 경고를 보낸다. 이는 id를 통해 접근한 상태에서 정보의 수정 등이 발생하였을 경우, 해당 정보에 대해 저장하는 것을 보장하지 않기 때문이다.
그러나, 반대로 이러한 특성을 유용하게 사용할 수 있는 부분도 있다. 해당 경우는 아래와 같다.
head만 옳겨서 실험적인 작업을 하고, 다시 main으로 돌아오면 실패한 실험적인 작업을 제거)그러나, 이런 방식을 이용하는 대신 일반적으로는 branch를 따로 만들어, 작업을 진행하게 되게 된다.
[참고]
snipaste : 추천 프로그램 (캡쳐 및 고정 등 가능)
commit 메시지를 바꾸고 싶을 때,
git --amend -m 메시지로 바꿀 수 있다. (가장 최근 버전 메세지 바꿈)
- 만약 메시지를 바꾸면, 해시 값이 바뀌어서 메시지를 바꾼 버전과 원래 버전은 다른 ID를 가지며, 따라서 원래 버전으로 회복이 가능해진다 (원래 버전으로 돌아올 수 있음). (즉, commit-id가 매우 중요)
추가적으로, ID를 까먹었으면, reflag를 통해 버전을 돌릴 수 있다.
rebase는 일종의 복잡한merge를 일렬로 가짜로 펴서 한번에 통합merge하는 것이다.
(rebase와 reflag는 git의 고급 활용 기술중 하나로 나중에 정리한다.)
만약, ID를 사용하지 않고 checkout과 유사한 효과를 얻고 싶으면, branch를 만들면 된다. (branch명으로 접근 가능)
branch를 만들면, 다른 branch에서 commit해도 해당 branch의 내용은 사라지지 않는다. (branch가 가리키고 있으므로)
main이나, 다른 브랜치들은 모두 pointer 역할을 하며, ref라고 통칭한다. (head 포함)➡️ 브랜치를 통해 실패 시, 간단히 버릴 수 있어야 하며, 성공 시, 기존 데이터에 쉽게 합칠 수 있다.
git merge는 서로 다른 브랜치를 병합하는 것을 의미한다.
merge는 아래와 같은 특징을 갖는다.
head를 놓고, 병합해야 한다.merge는 아래와 같이 수행하는 것이 좋다.
main의 내용은 기능 작업 시(funcation branch)에 자주 merge하는 것이 좋다. (main의 내용은 공통의 부분이므로) main에서 기능(funcation branch)의 merge는 기능이 성공적으로 완성된 후, 한번 만 하는 것이 이상적이다. merge는 위와 같이 서로 병행적으로 작업한 코드를 쉽게 단일화할 수 있다는 매우 강력한 장점을 가지고 있다.
문제는 같은 파일의 같은 행을 수정한 경우, merge가 일어나면, 충돌 문제가 발생하며, 이는 쉽게 처리하기 어렵다는 것이다.(conflict)
만약 같은 파일의 같은 행의 내용이 다른 상황에서 병합이 일어난 경우, git은 3-way merge 방식으로 충돌을 처리한다.
merge의 충돌 처리 방법에는 2-way merge와 3-way merge가 있다.
2 way-merge의 경우, 병합 할 2개의 파일만을 대상으로, 서로 다른 부분은 병합해주지 못하고, 직접 수정하도록 유도한다.| c1 | b1 | merge(2-way) |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 2 | ? (1 또는 2 충돌) |
| 2 | 3 | ? (2 또는 3 충돌) |
3-way merge의 경우, 병합 할 2개의 파일의 공통 조상까지 대상으로하여, 공통 조상과 병합 할 2개의 파일을 비교하여, 병합 할 파일에서 둘 다 바뀌었을 경우에만 충돌처리하고, 한 쪽만 바뀌는 경우는 그냥 바꿔준다.| 공통 조상 | c1 | b1 | merge(3-way) |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 1 | 2 | 1 | 2 |
| 1 | 1 | 3 | 3 |
| 1 | 2 | 3 | ? (2 또는 3 충돌) |
충돌 결과는 2-way merge 처럼 보인다. (충돌 난 지점에서 충돌 대상만 보여주고, 조상의 원본 데이터는 나타내지 않음)
우리가 만든 파일을 백업하기 위해서는 원격 저장소를 만들고, 해당 파일을 원격 저장소에 올려면, 백업이 된다.
원격 저장소 중 가장 대표적인 원격 저장소가 github이다.
git자체는 다른 원격 저장소에 연동하여도 사용 가능하다.
원격 저장소와 자신의 지역 저장소를 연결하는remote 시 origin은 원격 저장소의 별명으로 아무거나 지어도 된다. (지역 저장소는 여러 개의 원격 저장소에 저장할 수 있으므로, 원격 저장소를 지정하기 위한 별칭이다.)
내가 어디까지 백업을 했는지를 기록하기 위해 git은 origin/main이라는 branch를 추가로 만든다. (remote tracking branch라고 한다.)
push 할 때 마다, 최신의 버전으로 원격 저장소에 저장하며, branch(origin/main)의 형식으로 반영된 버전까지 지역 저장소에 표기한다.협업을 위해서 우리가 원격 저장소에 올린 내용을 다른 사람이 받아서 사용할 수 있도록 하고, 반대로 상대방이 업데이트한 내용을 내가 받아서 사용할 수 있도록 구성하는 것이 git을 이용한 협업의 방식이다.
git을 이용한 협업 방식은 아래의 단계로 이루어진다.
clone한다.push를 통해 원격 저장소에 반영한다.push가 거절되며, 원격 저장소와 동기화를 위해서는 fetch와 pull이 가능하다.push하여 나의 코드를 원격 저장소에 반영한다.fetch : 원격 저장소를 동기화를 하여 가져오되, main은 기존의 버전을 가리키고 있는다. (나의 지역 저장소가 원격 저장소를 병합하지 않은 경우)
pull : fetch + merge origin/main로,main이 origin/main 위치로 이동한다. (기존의 merge와는 좀 달리, 같은 위치에서 이동)
merge와 동일하게, 원격저장소의 정보와 내 지역 저장소의 충돌 해결 및 일체화 과정으로 진행된다.➡️ 결국, push 와 pull을 반복하여, 동기화하며 작업하는 것이 협업하는 방식이다.
위에서 언급했듯, 동기화하는 과정에서도 merge과정이 있기에, 코드 수정이 발생할 경우, 3-way merge 방식으로 코드가 수정된다.
그냥 push하는 경우, 이미 먼저 원격 저장소에 업데이트가 있다면, error를 낸다. (원격 저장소와 현재 자신의 저장소가 맞지 않으므로)
push하면 된다.