Git은 분산 버전 관리 시스템으로 리모트 서버에 있는 소스를 수정하려면 로컬 환경으로 소스를 클론(Clone)하는 과정이 필요하고 이후, Git은 로컬 환경의 파일을 추적하고 있다가 사용자가 소스를 수정하면 그 변경 사항을 감지한다.
사용자는 자신이 리모트 서버에 변경 사항을 반영하고 싶은 파일이나 소스 코드의 라인을 고른 뒤 리모트 서버에 업로드한다.
Remote: 리모트 서버 자체를 의미
리모트 서버라는 개념은 우리가 자주 사용하는 구글 드라이브나 N드라이브와 같은 클라우드 스토리지와 같이 전 세계 어딘가에 있는 서버에 우리의 소스를 저장하는 것
Github, Bitbucket, GitLab, etc...: 서버를 제공해주는 대표적인 업체
Git이라는 시스템에 필요한 리모트 서버와 Git을 좀 더 편리하게 사용할 수 있는 기능들을 제공
Origin: 내가 사용하는 리모트 서버의 이름
어떤 리모트 서버에 변경 사항을 업로드 할 것인지 정할 때, 반드시 하나의 리모트 서버만 사용할 수 있는 것이 아니기 때문에 내가 사용하는 리모트 서버의 이름을 정하기위해 사용하며 보통은 한 개의 리모트 서버만 운용하는 경우가 대다수이기 때문에 많은 사람들이 Remote와 Origin을 혼용해서 사용
저장소, 리모트 서버 내에서 구분되는 프로젝트 단위
구글 드라이브를 사용할 때도 하나의 디렉토리에 모든 파일을 다 때려넣지않고 몇 개의 디렉토리를 만들고 용도에 따라 파일을 나눠서 구분하는 것과 동일
일반적으로 한 개의 레파지토리는 하나의 프로젝트를 의미하지만 경우에 따라서 레파지토리 하나에 여러 개의 프로젝트를 구성하기도 함
레파지토리를 클론받을 때는 해당 레파지토리를 가리키는 URL이 필요하며 레파지토리의 이름은 URL의 맨 마지막에 .git 확장자를 가지는 방식으로 표현
https://github.com/user/repository.git
https://user@bitbucket.org/group-name/repository.git
혼자서만 프로젝트의 버전 관리를 한다면 단순히 리모트 서버의 레파지토리에서 소스를 받아와서 변경한 후 다시 리모트 서버로 업로드하는 과정만으로도 프로젝트를 진행하는데는 문제가 없지만 Git은 여럿이서 함께 소스를 수정하며 개발하는 협업 상황을 상정하고 만들었기 때문에 협업에서 발생할 수 있는 여러가지 곤란한 상황들을 타파하기 위한 많은 기능을 가지고 있다.
Git은 기본적으로 CLI(Command Line Tools)을 통해 사용하고 commit, fetch, branch와 같은 여러가지 명령어를 사용하여 이 기능들을 사용할 수 있게 해준다.
Github과 같은 리모트 서버 제공업체들은 레파지토리를 쉽게 클론할 수 있도록 눈에 잘 띄는 버튼을 만들어 놓고 해당 레파지토리의 URL을 제공하는 경우가 많으며 사용자는 URL을 복사한 후, Git의 clone 명령어를 사용해서 레파지토리를 클론하기만 하면 된다.
$ cd ~/dev/evan # 원하는 작업 디렉토리로 이동
$ git clone https://github.com/evan-moon/test-repo.git
$ git pull # 현재 내 로컬 브랜치와 같은 이름을 가진 리모트 서버 브랜치가 타겟
$ git pull origin master # origin 리모트 서버의 master 브랜치가 타겟
$ git fetch
#!/bin/bash
git fetch --all -p; git branch -vv | grep ": gone]" | awk '{ print $1 }' | xargs -n 1 git branch -d
위의 쉘은 fetch를 통해 리모트 서버의 최신 내용을 받아온 뒤, branch 명령어를 사용하여 리모트 서버에서는 삭제되었지만 로컬에는 남아있는 브랜치를 찾아서 싹 다 지워주는 스크립트이며 로컬에는 있지만 리모트에서 삭제된 브랜치는 브랜치 이름 뒤에 : gone이라는 문구가 붙어있기 때문에 구분이 가능하다.
$ git branch -vv
- master fa0cec5 [origin/master] 마스터 브랜치에욤
test 1f3578f [origin/test: gone] 리모트에선 죽은 브랜치
test2 fa0cec5 로컬에서 만들어지고 리모트에 업데이트는 안된 브랜치
$ git add . # 현재 디렉토리의 모든 변경사항을 스테이지에 올린다
$ git add ./src/components # components 디렉토리의 모든 변경사항을 스테이지에 올린다
$ git add ./src/components/Test.vue # 특정 파일의 변경사항만 스테이지에 올린다
$ git add -p # 변경된 사항을 하나하나 살펴보면서 스테이지에 올린다
선택된 변경 사항들은 스테이지(Stage)라고 불리는 공간으로 이동
스테이지는 박스와 같은 역할을 한다.
git add <경로> 명령어 - 해당 경로 안에 있는 변경 사항을 전부 스테이지로 이동
이 과정이 불안한 사람은 -p 옵션을 줌으로써 변경 사항을 하나하나 확인하면서 스테이지에 올릴 수도 있다.
스테이지에 담긴 변경 사항들은 git status 명령어를 사용하여 확인 가능
status 명령어에 추가적으로 -v 옵션을 사용하면 어떤 파일의 어떤 부분이 변경되었는지도 함께 볼 수 있다.
$ git add ./soruce
$ git statusOn branch master
Your branch is up to date with 'origin/master'.Changes to be committed:
(use "git reset HEAD ..." to unstage)modified: source/_drafts/git-tutorial.md
git commit -m "Add: test.py 파일 추가"
$ git log --graph
- commit 20f1ea9 (HEAD -> master, origin/master, origin/HEAD)
| Author: Evan Moon bboydart91@gmail.com
|
| 회원가입 기능 개발 끝!
|- commit ca693fd
| Author: Evan Moon bboydart91@gmail.com
|
| 회원가입 비밀번호 입력 폼 추가
|- commit f9b6e2d
| Author: Evan Moon bboydart91@gmail.com
|
| 회원가입 이메일 입력 폼 추가
|
HEAD의 위치로 현재 커밋 상태를 확인할 수 있음
위의 그래프 상에서 HEAD가 20f1ea9 회원가입 기능 개발 끝! 커밋에 위치해 있으므로 현재 필자의 어플리케이션은 회원가입 기능까지 개발이 완료된 상태라는 것을 알 수 있다.
해쉬 값을 사용하여 어떠한 커밋으로든 자유자재로 이동 가능
그래프를 보면 각각의 커밋들은 20f1ea9과 같은 고유한 해쉬 값을 가지고 있는데, 이 해쉬 값을 사용하여 어떠한 커밋으로든 자유자재로 이동할 수 있다. 예를 들면 회원가입 비밀번호 입력 폼 추가 커밋의 해쉬 값을 사용하여 git checkout ca693fd 명령어로 회원가입 비밀번호 입력 폼이 추가된 시점으로 이동할 수 있다는 것이다.
커밋은 반드시 실행 가능한 단위로 설정해야 함
특정 커밋으로 버전을 변경했을 때 어플리케이션이 제대로 실행되지 않고 에러가 발생하면 안된다.
커밋에는 메세지를 담을 수 있음
메세지는 커밋으로 인한 변경 사항이 무엇인지 직접적으로 표현해주는 유일한 수단이므로 좋은 커밋 메세지를 작성하기 위한 고민은 필수이며 커밋 메세지는 꼭 영어여야 할 필요는 없다.
커밋은 아직 리모트 서버에 파일을 전송하는 것이 아니라 사용자의 클라이언트 내에서 수행되는 과정이므로 인터넷에 연결이 되어 있지 않아도 변경 사항을 커밋하는 것이 가능
$ git push origin master # origin 리모트 서버의 master 브랜치로 푸쉬해줘!
$ git push --set-upstream origin master
Example
7월 25일 지각자 명단
나연
채영
사나
쯔위
From https://github.com/evan-moon/conflict-test
* branch test -> FETCH_HEAD
Auto-merging 지각자.txt
CONFLICT (content): Merge conflict in 지각자.txt
Automatic merge failed; fix conflicts and then commit the result.
7월 25일 지각자 명단
나연
채영
<<<<<<< HEAD
지효
=======
미나
>>>>>>> 35058b46325bb61112efd52f4019f907c561328d
쯔위
<<< HEAD와 ===사이에 들어있는 상단 부분이 현재 브랜치에서 내가 수정한 내용
영희는 사나를 지효로 수정했기 때문에 해당 부분에 지효라는 이름이 들어가 있다.
===부터 >>> 커밋 해쉬사이의 내용은 어떤 커밋에서 수정된 내용과 충돌이 발생했는지 알려줌
이 예시에서는 철수가 사나를 미나로 수정한 부분이 될 것이다.
Git은 그냥 버전 관리만 해주므로 비즈니스 히스토리를 모르기 때문에 둘 중에 어떤 것이 맞는 소스인지도 모르고 사용자에게 선택을 맡기며 이 상황에서 영희는 다음 세가지 선택지를 가질 수 있음
철수의 변경 사항을 무시
자신의 변경 사항을 무시
두 변경 사항 모두 반영
핫픽스
$ git checkout master
$ git merge feature
해당 브랜치의 커밋 전체를 통합한 커밋을 타겟 브랜치에 머지하는 옵션
일반 머지는 머지가 되는 대상 브랜치의 모든 커밋이 남아있는 상태에서 타겟 브랜치로 합쳐지지만 머지 스쿼시는 대상 브랜치의 모든 커밋을 모아서 하나의 커밋으로 합치고 타겟 브랜치에 머지하는 방식이다.
$ git checkout master
$ git merge --squash feature
스쿼시도 머지와 같이 독립된 하나의 개념으로 스쿼시는 커밋을 여러 개 합친다는 개념
하단에 후술할 rebase 명령어와 함께 사용하여 현재 브랜치의 커밋을 합칠 때도 사용한다.
$ git rebase -i HEAD~~
위 명령어는 HEAD부터 HEAD의 ~~(전전) 커밋까지의 히스토리를 변경하겠다는 의미이다. 이 명령어를 입력하면 vim이 실행되고 아래와 같은 내용이 표시된다.
pick 9a54fd4 commit의 설명 추가
pick 0d4a808 pull의 설명을 추가
# Rebase 326fc9f..0d4a808 onto d286baa
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
Merge
$ git checkout feature
$ git merge master
$ git checkout feature
$ git rebase master
장점:깔끔한 커밋 히스토리를 만들어 준다는 것
머지 커밋이 남지 않고 애초에 master에서 수정한 것 마냥 히스토리가 남기 때문에 깔끔하게 일자로 쭉 떨어지는 이쁜 히스토리를 볼 수 있다.
단점: 커밋 끼워넣기 때문에 발생하는 문제
feature 브랜치를 master로 리베이스했다고 가정하면 feature 브랜치를 생성한 이후에 master에 반영된 커밋들은 모두 맨 끝으로 이동하고 중간에 feature 브랜치의 커밋들을 끼워넣게 되며 사용자가 보고 있는 master의 상태는 feature의 변경 사항들이 반영되어 있는 히스토리를 가지고 있지만 다른 사람의 master는 아직 예전 master의 히스토리와 함께 일하고 있다는 것이다.
머지는 머지 커밋을 발생시키며 히스토리가 미래로 나아가기 때문에 이런 문제가 발생할 확률이 적지만, 리베이스는 과거를 변경하는 것이기 때문에 문제가 생기기 쉬움
다른 브랜치에서 어떤 하나의 커밋만 내 브랜치로 가져오는 기능
체리픽이 하는 일을 보면 대상 브랜치의 커밋 하나를 가져와서 현재 브랜치에 병합하는 행위라고 느껴지지만 히스토리를 보면 병합되는 그림이 아니라 그냥 해당 커밋을 그대로 복사해와서 내 브랜치에 커밋되는 형태로 기록된다.
$ git checkout master
$ git cherry-pick 35058b4 # 가져올 커밋 해쉬
체리픽을 사용할 때도 현재 브랜치의 소스와 충돌이 날 가능성이 있음
Example
현재 작업 중인 변경 사항들을 잠시 스택에 저장할 수 있는 명령어
이 명령어는 아직 마무리되지 않은 작업이 있는데 다른 브랜치로 체크아웃 해야하는 경우에 유용하게 사용할 수 있다.
$ git stash # 현재 변경 사항들을 스택에 저장
$ git stash list # 스태쉬 목록을 확인
$ git stash apply # 가장 최근의 스태쉬를 다시 불러온다
직접 스태쉬 이름을 지정 가능
스태쉬의 이름을 지정하지 않으면 스택에 들어간 순서(First In Last out)대로만 스태쉬를 가져올 수 있다.
$ git stash branch-name # 스태쉬 이름을 branch-name으로 지정하고 스택에 저장
$ git stash apply branch-name # branch-name 이름을 가진 스태쉬를 불러온다
Example
* 19061e7 - 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다.
|
* e50aff9 - 여자친구가 맛이 없다고 한다.
|
* 2d57c29 - 알리오 올리오를 주문했다.
|
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!
$ git reset --hard c04f8f6
# 식당을 방문했을 때로 돌아갔다!
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!
reset 명령어의 3가지 옵션
$ git revert 35058b4 # 특정 커밋을 되돌린다
$ git revert 35058b4..c04f8f6 # 커밋의 범위를 지정하여 되돌린다
$ git revert HEAD # 현재 헤드가 위치한 커밋을 되돌린다
Example
* 35058b4 - Revert 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다. # 여친한테 차인 히스토리만 리벗하자
|
* 19061e7 - 맛없는 식당을 찾은 죄로 여자친구한테 이별 통보를 받았다.
|
* e50aff9 - 여자친구가 맛이 없다고 한다.
|
* 2d57c29 - 알리오 올리오를 주문했다.
|
* c04f8f6 - 찾아본 식당에 방문했다.
|
* 7d9d953 - 여자친구와 함께 갈 좋은 식당을 찾았다!
위의 예시에서 여자친구한테 차인 커밋을 다시 리벗했지만 히스토리 상에는 흑역사가 고스란히 남아있다.