강의 링크: https://www.udemy.com/home/my-courses/learning/
0. Git
Git은 정말 많이 사용합니다. 그러나 가끔씩 git의 원리라던지, 급하게 필요할 때 확신이 서지 않는 경우가 종종 있었습니다. 이 강의를 통해 "이것 아는건데?" 하고 넘어갈 수도 있겠지만, "아, 이건 내가 놓치고 있었구나" 하고 스스로를 점검할 수 있는 계기가 될 것 같아서 강의를 신청해보았습니다.
1. Git의 기초
Git은 가장 유명한 Version Control 시스템입니다.
Version Control이란?
- 버전관리는 파일의 변화를 시간에 따라 추적하고 관리하는데 도움을 주는 소프트웨어
- 대부분의 버전관리시스템은 이전 버전의 파일을 다시 볼 수 있게 해주고 버전들 간의 또는 파일들 간의 변화를 비교한다거나, 변화를 되돌리고, 다른 작업자들과 그 변화를 비교할 수 있게 해준다.
- 게임을 예로 들면 세이브 파일을 저장하고, 되돌리는 과정
1-1. Git 기본
git repository
- 각각의 독립된 Git 이력과 저장소를 갖는다.
git status
- git의 상태를 조회한다.
git init
- 어느 디렉토리에 있든 새 저장소의 홈이 된다.
- .git 디렉토리를 생성한다. git 이력이 저장되는 디렉토리이다.
git은 top down식으로 이력을 추적한다.
- 어떤 프로젝트를 위해 이 디렉토리에서 변경하는 파일이나 폴더는 깃에 의해 추적됩니다
git log
- git log는 git commit을 실행했을 때 벌어지는 일들을 보여주는 것
1-2. Git에서 변경사항 적용되는 과정
제 물리적인 영역이 아닌 3개의 다른 영역이 있습니다. Working Directory, Staging Area, repository로 구성
- Working Directory: 프로젝트에서 사용자가 실제로 작업하는 공간
- Staging Area: 커밋하기 전에 변경사항들을 등록하는 곳
- repository: .git 폴더가 저장되어있는 공간. 커밋을 하게되면 git commit은 .git 안에 있는 것들을 변경해서 새 커밋을 폴더에 등록한다. 커밋하면 그 폴더, 사실은 깃 저장소를 업데이트 하는 것입니다.
git에서 변경사항 적용되는 과정
- 작업자가 파일을 수정
- git add : 작업자가 변경사항을 staging 영역을 올림
- git add 파일1.txt 파일2.txt : space로 구분하여 파일을 올릴 수 있다
- git commit : staging에 올라간 git 변경사항(group화된 변경사항)을 git 저장소에 저장
1-3. git commit
git은 차이점만 저장하는 것이 아니라 전체를 Snapshot으로 저장합니다.
차이점만 저장하는 것이 훨씬 용량도 작고 빠를것 같지만, 조금 더 생각해보면 차이점만 저장하는 방식은 버전을 보여줄때 파일이 만들어졌던 맨 처음까지 거슬러 올라가며 바뀐점을 모두 반영해야하는 계산을 해야합니다. ex) README.txt가 100번 바뀌었으면 100번의 계산을 모두 해야합니다.
하지만 파일의 스냡샷을 저장하는 git은 계산이 필요 없습니다. 바로 앞에서 바뀐 커밋이랑 비교 연산 한번만 하면 되기 때문이다. 그리고 바뀌지 않은 파일은 이전 파일의 링크만 저장하기 때문에 용량도 적고 계산도 하지 않아도 됩니다.
2. Git branch
2-1. Git branch 개념
커밋을 하면 각 커밋은 숫자와 문자가 연속적으로 조합된 이런 특이한 해시를 갖습니다. 모든 커밋은 독특한 해시를 갖고 있으며 이 커밋은 커밋 내용에 부합해야 하고 적어도 그 이전에 있었던 부모 커밋 하나를 참조합니다.
한 커밋은 다음 커밋으로 연결되고 그 다음으로 연결되는 식의 직선형 이력을 갖습니다. 하지만 실제로 프로젝트에서 작업할 때 가끔, 여러 상황에서 동시에 작업하는 경우가 있다. 순차적으로하면 코드가 엉망이 되기 때문에 그러한 작업은 별도로 이뤄집니다. 이 때 사용하는 것이 Git branch입니다.
Git branch: 우리가 원할 때마다 별도의 콘텍스트를 생성할 수 있게 해줍니다. 우리가 브랜치에서 어떤 작업을 하든 다른 브랜치에는 영향을 미치지 않습니다.
- 한 브랜치에서 작업을 하다가 갈라져 다른 작업을 시도합니다.
- 또 다른 브랜치에서는 다른 작업을 시도합니다
- 브랜치에서 한 단계 더 나아간 브랜치를 만들 수도 있습니다
- 아주 중요한 것은 적당한 때에 브랜치를 합쳐서 병합할 수 있습니다.
2-2. HEAD
특히 저장소에서 현재 우리의 위치를 가리키는 포인터입니다. 그리고 브랜치 레퍼런스를 가리킵니다. 브랜치 레퍼런스: 브랜치를 책의 책갈피라고 생각할 때 우리는 책의 여러 위치에 다양한 북마크를 가질 수 있습니다. 책은 어느 시점에서든 이것들 중에서 단 하나만 펼쳐질 수 있습니다.
HEAD는 보거나 확인하고 있는 현재 위치를 말하는 포인터 브랜치를 checkout한다면 HEAD는 보고있는 브랜치를 가리킵니다.
2-3. branch와 HEAD의 상관관계
- git branch master
- commit 93edb6a56f52b7fd2b2a798c2db60121934e4164 (HEAD -> master)
- git branch를 통해 브랜치를 만들면 현재 커밋을 가리키는 HEAD를 기준으로 생성됩니다. HEAD는 같은 곳을 가리키고 있다. 하지만 이제 새 브랜치가 생겼고, 그 브랜치로 이동하지 않았기 때문에 아직 master에 있습니다. master와 동일한 커밋에 있으며, 이 2개의 브랜치 레퍼런스는 같은 것을 가리키고 있습니다
- commit 93edb6a56f52b7fd2b2a798c2db60121934e4164 (HEAD -> master, oldies)
- git checkout을 한 이후 작업을 해서 커밋을 한다 이렇게 되면 HEAD와 master 브랜치가 분리가 된다.
- commit a99949b0dc8b003434d04ebe74f68ed2d76b963d (HEAD -> oldies)
- commit 93edb6a56f52b7fd2b2a798c2db60121934e4164 (master)
3. Git merge
병합은 간단히 말해서 두 Version의 합집합을 구하는 것입니다. git branch를 다른 branch로 합치는 과정을 merge라 합니다. 브랜치간 merging을 할 때 새로운 commit이 생겨날 수도 있고, 그렇지 않을 수도 있는데 다음을 살펴보자
Git의 merge 방법에는 크게 2가지가 있습니다.
- Fast Forward Merge
- 3-way merge
3-1. Fast Forward Merge
가장 기본적인 merge는 바로 Fast Forward Merge입니다. Fast Forward Merge는 현재 브랜치의 HEAD가 대상 브랜치의 HEAD까지 로 옮기는 merge입니다. Fast Forward Merge는 다음 명령어를 통해 가능합니다.
git switch [현재 브랜치]
git merge [대상 브랜치]
Before
master 브랜치에선 작업이 없었기 때문에 여전히 hotfix 브랜치와 동일 선상에 있습니다.
이때 merge를 하면 master 브랜치의 HEAD가 hotfix 브랜치의 HEAD로 이동합니다.
마치 Fast forward(빨리 감기) 되듯이 이동합니다
After
Fast forward는 별도의 merge 커밋이 생성되지 않습니다. 브랜치를 비교할 필요가 없기 때문에 충돌이 발생할 일도 없습니다.
뒤에 쳐진 브랜치 (여기서는 master)의 참조 개체가 앞서있는 브랜치가 가리키는 개체를 참조하도록 이동할 뿐입니다.
Fast Forward 정리
- 특정 커밋이 아니라 브랜치를 병합하는 것
- 현재 우리가 있는 위치에, HEAD가 가리키는 위치에 병합합니다
- ex) master에 병합을 하려면 feature → master로 checkout
- git merge
- master 브랜치는 빨리감기 되며 포인터를 앞으로 옮기며 커밋을 가리킨다
Fast Forward Merge의 한계점
- Fast Forward Merge는 중간에 변경이 없을 때만 동작합니다
- 만약 중간에 다른 커밋이 있고, 해당 커밋이 같은 부분을 수정했다면 conflict이 일어나 제대로 동작하지 않습니다
3-2. 3 Way Merge(recursive 전략)
3-way merge는 대부분의 협업에서 발생하게 되는 merge 방식입니다.
아래 그림처럼 두 브랜치가 동일 선상이 아닐 때 3-way merge가 발생합니다.
Before
먼저 두 브랜치가 분할되는 기점에서 공통 조상을 찾습니다.
$ git checkout master
$ git merge iss53
Merge made by the 'recursive' strategy.
이후 3-way merge를 진행합니다.
이 과정에서 두 브랜치의 커밋과 공통 조상의 커밋, 총 3개의 커밋이 관여하기 때문에 3-way라 불리게 됩니다.
After
3-way merge가 완료되면 그 결과를 담고 있는 merge 커밋이 생성됩니다.
이 merge 커밋은 가리키고 있는 부모가 여러 개라는 특징이 있습니다.
3 way merge 원리
2-Way merge와 3-way merge는 차이점을 가지고 있습니다. 바로 공통 조상(Base) 이라는 개념입니다.
먼저 2-Way merge 방식을 보겠습니다. 2-way merge는 단순히 두 브랜치만 비교하는 방식입니다. 비교 기준인 Base가 없기 때문에 두 브랜치의 다른 부분들은 충돌로 판단하게 됩니다.
다음으로 3-way merge 방식을 보겠습니다. Base를 기준으로, 변화가 발생했다면 이를 merge 결과로 채택합니다. 이때 만약 두 브랜치가 각기 다른 변화를 발생시켰다면 이를 충돌로 판단하게 됩니다. 이 충돌을 해결한 뒤에 merge를 하면 3-way merge가 완료됩니다.
4. Git checkout
git checkout은 기본적으로 이전에 커밋했던 때로 돌아가는 것입니다.
4-1. git detached Head
일반적으로 헤드는 특정 브랜치 참조를 가르킵니다. 이전 커밋을 체크아웃할 때, 실제로 하는 일은 헤드가 커밋을 참조하도록 바꾸는 것입니다. 아래 그림을 통해 살펴봅시다.
Before
일반적으로 헤드는 특정 브랜치 참조를 가르킵니다.
이전 커밋을 체크아웃할 때, 실제로 하는 일은 헤드가 커밋을 참조하도록 바꾸는 것입니다.
git checkout [커밋해쉬값]
After
Detached Head란, 말 그대로 head가 (branch로부터) 떨어져있는 상태를 뜻합니다.
- HEAD가 특정 branch가 아닌 특정 commit을 직접 참조하고 있는 상태를 말합니다.
- Detached HEAD 상태에서도 모든 Git 조작을 수행할 수 있습니다.
일반적으로 git에서 보통의 경우엔 head-> branch -> commit 의 참조순서를 가집니다.
하지만 이 때, check out 명령어로 특정 커밋으로 check out할 경우, 깃은 참조하던 branch를 잃어버리고, 직접 특정 commit을 참조하는 detached head 상태가 됩니다.
4-2. git checkout을 이용한 특정 파일 되돌리기
특정 파일만 마지막 커밋까지 되돌리고 싶을 때
git checkout HEAD [파일명]
파일을 다시 원래버젼으로 코드를 재작성하는 방법도 있지만 해당 방법 대신 마지막 커밋 이후 a.txt 파일과 b.txt 파일을 수정했습니다. 그런데 a.txt 파일의 코드가 엉망이라 그냥 마지막 커밋지점으로 되돌리고 싶습니다.
파일을 다시 원래 Version으로 코드를 재작성하는 방법도 있지만 해당 방법 대신 이때 git checkout HEAD a.txt 를 사용하면 a.txt 만 마지막 커밋의 코드로 되돌릴 수 있습니다. 또는 git checkout -- a.txt 를 사용할 수 있습니다. (HEAD대신 -- 사용)
5. git restore
5-1. git restore
git checkout 이 여러 기능을 하는 상태에서 git switch와 git restore가 도입되어 git checkout 의 기능을 일부 대신하게 되었습니다. git checkout HEAD a.txt 대신 git restore a.txt 사용할 수 있습니다.
git restore의 역할은 크게 2가지입니다.
- 수정된 사항을 취소하기
- 변경사항 스테이징 취소하기
수정된 사항을 취소하기
git restore [파일명]
파일을 수정하다가 git restore [파일 이름] 을 쓰면, 파일을 원래 모습으로 복원합니다, 헤드에서 보여지는 상태대로 복구 할 수 있습니다.
git restore --source HEAD~[복구하고 싶은 헤드] [파일명]
특정 커밋을 참조해서 커밋 n개의 전의 모습으로 복원할 수 있습니다.
변경사항 스테이징 취소하기
git restore --staged [파일명]
실수로 어떤 파일을 스테이지해서 다음 커밋에 포함될 건데 취소하고 싶은 경우 git restore를 사용하여 언스테이징할 수 있습니다.
git status를 이용하면 restore을 어떻게 사용해야하는지 알려줍니다.
6. git fetch, git pull
git fetch [origin-branch 이름]
깃허브 저장소에서 항목들을 로컬 저장소로 가져옵니다. 깃허브에서 최신 정보를 가져오지만 현재 작업 중인 것을 디렉토리에는 영향을 받지 않는 것입니다.
즉, 무엇이 변경되었는지, 오리진이나 다른 원격에서는 무엇이 새로워졌는지 확인할 수 있습니다.
제가 이 노란색 작업을 하고 있는 동안, 다른 개발자가 초록색으로 표시된 새 커밋 3개를 추가했다고 가정해 봅시다.
그것들은 깃허브 마스터 브랜치에 있지만, 제 로컬에는 없습니다. 저도 새 커밋이 있지만, 다른 개발자가 만든 새로운 커밋은 없습니다.
git fetch를 통해 깃허브 저장소에서 항목들을 로컬 저장소로 가져옵니다. (워킹 디렉토리가 아닙니다)
fetch 사용하면 원격 저장소에서 변경 사항을 다운로드할 수 있지만, 이러한 변경 사항은 작업 파일과 통합되지 않습니다.
6-2. git pull
git pull은 fetch와 유사하지만 헤드 브랜치를 업데이트하고 워킹 디렉토리를 업데이트합니다.
원격 저장소에 가서 가장 최근 커밋을 다운받아서 변경사항들을 내 워킹 디렉토리에 업데이트 합니다.
즉, git fetch + git merge가 합쳐진 것입니다.
git pull [remote] [branch명]
- 이 구문을 어디서 실행하는지가 중요하다는 것입니다. 내가 어떤 브랜치에 있든, 내가 pulling 다운하는 곳으로 변경 사항은 병합됩니다
- git pull을 할 때도 merge가 들어가기 때문에 동일한 파일을 변경하는 경우 충돌할 수 있습니다.
7. git rebase
제 피처 브랜치가 오랜 시간 마스터 브랜치와 다른 상태인 것이 싫어서 최신 버전을 갖고 싶어서 마스터 브랜치에 반영된 내용을 어떻게 제 feature 브랜치로 가져옵니다. 다음날 피처 브랜치에서 작업하는 중 마스터 브랜치에 다른 코드가 또 추가되어 가져왔고 feature 브랜치에 머지 커밋만 주렁주렁 달리게 되고 작업과 전혀 관련이 없는 커밋들이 늘어나게 되는 상황이 있습니다.
이런 문제를 rebase가 해결해줍니다.
7-1. Git rebase
git switch feature
git rebase master
Git rebase 는 두 개의 공통 Base를 가진 Branch에서 한 Branch의 Base를 다른 Branch의 최신 커밋으로 branch의 base를 옮기는 작업입니다. 용어 그대로 베이스를 다시 설정하는 작업입니다.
merge 커밋을 생성하는 대신에 feature 브랜치에 생성돼 있던 커밋을 하나씩 재생성합니다. master 브랜치엔 영향이 없고 대신 어떻게 히스토리를 재배치하느냐, 제 피처 브랜치에 있는 커밋을 그대로 다시 생성하면서 마스터 브랜치의 끝 지점에서부터 배치합니다.
Before
위 그림을 보면 topic 브랜치에서 작업을 진행하고 있고, master 브랜치는 이미 상당부분 진행되었습니다.
이때 최신 master 브랜치의 작업 내용을 본인이 현재 작업 중인 feature 브랜치에도 적용하고 싶을 때 Rebase 명령어를 사용합니다.
After
git rebase master
git rebase master topic
위 명령어를 실행하면 git log가 아래와 같아집니다.
master 뒤에 topic의 commit들이 재생성되면서 기존 E에서 갈라진 브랜치가 변경되었습니다.
이렇게 리베이스를 사용하면 커밋 히스토리를 간결하게 유지할 수 있습니다.
- 공유 branch의 최신 변경사항을 즉각 반영할 수 있습니다 Git rebase 를 사용한다면, 동료 개발자들이 올린 Commit들의 수정사항을 내가 작업하고 있는 branch에 즉각 반영할 수 있습니다. 즉, 공유 branch에 대한 최신 commit을 반영하면서 작업을 해야할 때 git rebase를 사용한다면 작업 branch에서 항상 최신 변경사항을 적용한 commit을 유지할 수 있습니다.
- rebase는 커밋이력을 남기지 않으므로 commit History가 깔끔해진다. git merge를 사용하여 최신 이력을 가져올 경우, 복잡하고 어지러운 커밋 History가 됩니다. 반면, git rebase로 만들어진 History는 직관적인 History를 가질 수 있습니다.
총평
1. 강의가 무려 17시간 분량으로, 세세하게 다양한 명령어를 체험해볼 수 있었습니다.
2. 다양한 연습문제를 통해 문제를 풀어볼 수 있어 헷갈리는 개념을 정리하는데 좋았습니다.
3. 다양한 시각자료와 예시를 통해 이해를 하는데 도움이 되었습니다.
4. 깃 공식문서를 통해 약간 부족한 부분을 채워 학습해나간다면 개념을 정리하는데 큰 도움이 될 것 같았습니다.
해당 컨텐츠는 유데미로부터 강의 쿠폰을 제공받아 작성되었습니다.
댓글