1. Git이란?

가장 대표적인 version control system으로써, 특정 시점에서의 파일들의 상태를, 저장하고 보여줄 수 있습니다.

version control system을 사용해야하는 이유는 아래 이미지 처럼 문서의 버전을 관리하지 않기 위해서 입니다.

image

위와 같이 버전을 관리하게 되면 관리가 매우 어렵게 됩니다. 문서가 아니라 거대한 프로젝트를 개발할 때는 더욱 버전 관리가 복잡해지는데, 버전 관리를 간단한? 명령어 혹은 클릭을 통해서 할 수 있도록 도와주는 시스템이라고 생각하면 좋을 것 같습니다.

1.1. 특징

  • 개별파일 또는 프로젝트 전체를 이전 상태로 되돌릴 수 있고,
  • 시간에 따른 변경 사항을 비교해 볼 수 있고,
  • 누가 문제를 일으켰는지 추적할 수 있고,
  • 누가 언제 만들어낸 이슈인지도 알 수 있습니다.

1.2. 장점

  • 빠르다
    • 다른 버전 관리 시스템과 다르게 데이터를 파일 시스템 스냅샷 형태로 취급하기 때문에 크기가 작습니다.
    • 파일이 달라지지 않았다면 성능을 위해 새로 저장하지 않습니다. 이전 상태의 파일에 대한 링크만 저장
  • 거의 모든 명령을 로컬에서 실행합니다.

    • 거의 모든 명령이 로컬의 파일과 데이터만 사용합니다. 프로젝트의 히스토리 조회 를 로컬에서 합니다.
  • 무결성
    • 파일을 이름으로 저장하지 않고 내용과 디렉토리 구조를 이용해서 체크섬과 같은 해시 형태로 저장합니다.

2. git 설치

뭔가를 배울 때 사전적인 정의만 공부하는 것보다 직접 해보는 것이 더 효율적이기 때 설치를 해보고 사용해보며 이해해보겠습니다.

2.1. 설치 확인

git --version 

2.2. 환경설정

git config --global user.name 아이디 
git config --global user.email 이메일 주소

2.3. 환경설정 확인

git config --list

2.4. zsh 설치

zsh는 터미널의 가독성을 높혀주고, tab키로 자동 완성 기능을 제공해줍니다.

  • brew 설치

    $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    
  • Zsh 설치

    brew install zsh 
    

    image

  • oh my zsh

    $ curl -L https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh | sh
    
  • iterm2

    brew cask install iterm2
    

3. 저장소 만들기

Git, 즉 버전 관리 시스템을 사용하기 위해선 우선 버전을 관리할 저장소를 만들어야합니다. 저장소를 만드는 방법에는 두가지가 있습니다.

  • 로컬 프로젝트를 git 저장소로 생성
  • 다른 서버에 있는 저장소를 clone하기

3.1 로컬 프로젝트를 git 저장소로 만들기

생성한 프로젝트를 git저장소로 만들기

$ mkdir git-study
$ cd git-study
$ git init 

로컬 저장소와 원격 저장소 연결

$ git remote add oriign <remote-url>

원격 저장소 url을 변경

$ git remote set-url origin <remote-url>

3.2 다른 서버에 있는 저장소 clone 하기

아래 명령어는 프로젝트 히스토리를 전부 받아옵니다.

$ git clone <remote-url>

clone을 했을 경우 해당 원격 저장소와 로컬 저장소가 자동으로 연결이 되어있습니다.


4. git 공간

로컬 저장소를 생성했고, 로컬 저장소와 원격 저장소를 연결했다면 git을 직접 사용해볼 수 있습니다. 하지만, 그 전에 git의 구조를 머릿속에 그릴 수 있도록 git이 어떻게 구성되어 있는지 알아보겠습니다.

4.1 Working Directory (작업 디렉토리)

  • 개발자가 파일을 추가/수정/삭제하는 공간
  • 쉽게말해 .git 파일을 제외한 프로젝트 디렉토리(로컬 저장소) 내의 모든 공간이 Working Directory입니다.

4.2 Staging Area (인덱스)

  • 보통 index라고 하며 repositoryWorking Directory 사이에 있는 공간
  • 파일이 커밋 되기 전에 모여있는 임시 저장 공간으로써 모든 파일은 이 공간을 거쳐 저장소로 옮겨지게 됩니다.

4.3 git directory (Repository)

  • git이 프로젝트의 메타데이터와 객체 대이터베이스를 저장하는 곳 (.git)
  • Staging Area 에 커밋 가능한 스냅샷 형태로 Stage 해서 Commit하면 영구적인 스냅샷으로 저장됩니다.

image

  1. Working Directory 에서 파일을 수정한다.
  2. Staging Area 에 파일을 Stage 해서 커밋 할 스냅샷을 만든다.
  3. Staging Area 에 있는 파일들을 커밋해서 영구적인 스냅샷으로 저장한다.

5. 파일의 상태

저장소에 존재하는 파일들에는 Working Directory 에 존재하는지, Stage가 되었는지, modified 되었는지 같은 각 파일마다의 상태가 존재합니다.

untracked(추적되지 않음)

  • 추적되지 않은 파일은 모두 작업 디렉토리(Working Directory)에 있는 상태이며, 인덱스 혹은 저장소에 한번도 들어간 적이 없거나 (ignored)된 상태의 파일입니다.

unmodified(수정되지 않음)

  • 저장소에 저장된 파일이 수정되지 않은 상태를 말합니다.

modified(수정됨)

  • 이미 커밋 되었던 파일이 수정됐음을 뜻합니다.

staged/indexed (인덱싱됨)

  • 수정된 파일이 인덱스에 포함됐다면(staging Area에 올라갔다면 ) staged 된 상태입니다.
  • 워킹 디렉토리의 모든 파일은 크게 Tracked(관리대상임)와 Untracked(관리대상이 아님)로 나뉩니다.
  • 처음 저장소를 clone 하면 모든 파일은 Tracked 이면서 Unmodified상태이다. 파일을 checkout 하고나서 아무것도 수정하지 않았기 때문입니다.
  • 마지막 커밋 이후, 어떤 파일을 수정했다면 그 파일은 Modified 상태가 됩니다. 실제 커밋을 하기 위해서는 수정한 파일을 Staged 상태로 만들고, Staged 상태의 파일을 커밋합니다. 이런 라이프 사이클을 계속 반복합니다.

image

  1. Untracked 에서 git add . 를 하게되면 Tracked되며, Staged 상태가 됩니다.
  2. Unmodified 에서 파일을 수정하면 Modified  상태가 됩니다.
  3. Modified 에서 stage  할 수 있고,
  4. Staged  에서 커밋을 하게 되면 더이상 수정한 것이 없기 때문에 Unmodified 로 가게됩니다.
  5. Unmodified 에서 파일을 삭제하면 Untracked 상태가 됩니다.

.gitignore 혹은 .git/info/exclude 에 설정되어 있는 패턴에 의해 파일 또는 폴더가 무시될 수 있습니다.

Mac 환경에서 폴더 생성시 자동 추가되는 .DS_store   파일을 .gitignore 에 추가해보겠습니다.

$ echo .DS_Store >> .gitignore

6. 파일 핸들링

6.1 status

  • 파일의 상태를 확인하는 명령어

    $ git status
    
  • README.md` 파일을 생성하고, status 명령어를 입력하여 파일의 상태를 확인

  • 파일을 만드는 명령어

    $ echo >> README.md
    
  • 파일의 상태를 확인해보면 README.md는 한번도 Staged된적 없기 때문에 Untracked인 것을 확인할 수 있습니다.

    $ git status
    On branch master
      
    Initial commit
      
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
      
            README.md
      
    nothing added to commit but untracked files present (use "git add" to track)
    

6.2 add

  • add 명령은 다음 커밋에 추가하기 위해 파일을 staged 상태로 만듭니다.

    # 특정 파일만 add
    $ git add <path/file-name>
      
    # 모든 파일을 add
    $ git add --all
    $ git add -A
    $ git add .
    
  • 아까 만든 README.md파일을 staged 상태로 만들어보면

    $ git add README.md
    $ git status
    On branch master
      
    Initial commit
      
    Changes to be committed:
      (use "git rm --cached <file>..." to unstage)
      
            new file:   README.md
    
  • Changes to be committed > 커밋될 변경 사항들에 README.md가 추가된 것을 볼 수 있습니다.

  • README.md 파일은 tracked 이면서 staged 상태입니다.

  • 아직 커밋 되지 않은 파일들이 staged 상태인지 명시적으로 확인하는 방법

    $ git ls-files --stage
    100644 15618ef3d06b41c09e9db5b5d0c9b8beee7183a4 0       README.md
    
  • README.md파일을 수정하고 다시 저장한 후에 확인해보면

    $ git ls-files --stage
    100644 501ead6f115ad5367ca9094e3dd43d05ae8df0a6 0       README.md
    
  • 해시가 변경된 것을 확인할 수 있다. git 의 특징인 무결성 때문입니다.

    • git 은 파일을 저장할 때 이름이 아닌 체크섬, sha와 같은 해시형태로 저장하기 때문입니다.

6.3 Commit

커밋은 파일, 폴더의 추가, 변경 사항을 저장소에 기록하는 것입니다.

커밋하기

$ git commit -m "커밋 로그 메세지 작성"

파일을 수정하고 add 와 commit 커밋 로그 메세지를 한번에 실행하면 아래와 같습니다.

$ git commit -a -m "커밋 로그 메세지 작성" 

조회하기

커밋이 만들어질 때 생기는 SHA-1 해시 값을 커밋 해시 혹은 커밋 id라고 부릅니다. 아래 명령어를 통해 현재까지의 커밋을 조회할 수 있습니다.

$ git log

위 명령어만 사용하면 가시적으로 복잡한 커밋 로그를 보여주기 때문에 아래처럼 여러 옵션을 사용하면 가시적으로 좋은 커밋 히스토리를 조회할 수 있습니다.

$ git log --all --oneline --decorate --graph -10
* 2af686a (HEAD -> master) README.md 파일 수정1
* d002a64 README.md 파일 추가
  • -all : 모든 브랜치
  • oneline : 한줄로
  • decorate : 어떤 커밋을 가르키는지
  • graph : 그래프 형태로
  • -10 : 최근 10개 이내로
  • 모든 브랜치를 한줄로 보며 커밋을 하이라이팅해주고 그래프 형태로 10개만 보여달라.

히스토리를 조회하는 것 외에도 diff명령어를 통해서 비교하는 것도 가능합니다.

인덱스와 작업 디렉토리 비교

수정하고 인덱스 하지 않은 변경 사항과 인덱스를 비교해줍니다. add명령어도 하기 전에

$ git diff 

저장소와 인덱스

add를 했다면 --cached 옵션을 통해 수정된 저장소와 인덱스를 비교할 수 있습니다.

$ git diff --cached

커밋과 커밋

$ git diff <커밋>.. <커밋>

6.4 파일 삭제

git rm명령은 작업 디렉토리와 인덱스로부터 파일을 제거해줍니다.

$ echo >> rm_test
$ git rm rm_test
fatal: pathspec ' rm_test' did not match any files

Untracked파일을 없애려고 하자 git에서 에러를 발생시켰다. 해당 파일은 작업 디렉토리에만 존재하는 파일이기 때문입니다.

image

추적하지 않는 (스테이징 이전) 파일을 삭제할 경우 git 명령어 없이 rm을 사용하면 됩니다.

$ rm rm_test

image

인덱스에 추가된 파일 삭제하기

$ git add rm_test
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   rm_test
$ git rm rm_test
error: the following file has changes staged in the index:
    rm_test
(use --cached to keep the file, or -f to force removal)

인덱스에는 추가 되었지만 커밋하지 않은 파일이기 때문에, 인덱스에서만 제거할 것이면 --cached

$ git rm --cached rm_test

인덱스와 작업 디렉토리에서 강제로 삭제를 진행할 것이면 -f 옵션을 사용하라는 메세지를 출력합니다.

$ git rm -f rm_test

**커밋한 파일 지우기 **

$ git commit -m"add rm_test"
[master fe05a8f] add rm_test
 1 file changed, 1 insertion(+)
 create mode 100644 rm_test
$ git rm rm_test
rm 'rm_test'
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    rm_test

6.5 파일 이름 변경하기

$ git mv <변경전 file-name> <변경 후 file-name>

파일 이름 변경 후 파일 히스토리 확인해보기

$ git commit -m"rename test => text"
[master 680dc7f] rename test => text
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename test => text (100%)
$ git log text
commit 680dc7ff5cebe918c96433b93f7ff73fd55a1eea
Author: joohee.moon <nt10547@nts-corp.com>
Date:   Wed Nov 30 15:37:51 2016 +0900

    rename test => text

이름 변경 전 히스토리 보기

$ git log --follow text
commit 680dc7ff5cebe918c96433b93f7ff73fd55a1eea
Author: joohee.moon <nt10547@nts-corp.com>
Date:   Wed Nov 30 15:37:51 2016 +0900

    rename test => text

commit bcd6501142014d4c0797b2e7f206a944ef91eff6
Author: joohee.moon <nt10547@nts-corp.com>
Date:   Wed Nov 30 15:35:36 2016 +0900

    test

7. 브랜치

7.1 브랜치란?

  • 커밋 그래프에 존재하는 수많은 커밋중에 하나를 가르키고 있는 포인터 같은 존재입니다.
  • 안전하게 격리된 상태에서 무언가를 만들 때 사용됩니다.
  • 활성 브랜치현재 브랜치를 나타냅니다.
  • 새로운 커밋이 발생하면 브랜치는 새로운 커밋을 향하게 되는데 이것이 브랜치의 끝입니다.

7.2 브랜치의 활용

  • 브랜치 만들기

    $ git branch <branch-name>
    
  • 브랜치 나열하기

    새로운 브랜치를 만들었지만 HEAD라는 포인터는 아직 master를 가르킵니다.

    $ git branch 
    develop
    * master
    
  • 브랜치 이동하기

    $ git checkout <branch-name>
    
  • 브랜치 만들면서 이동하기

    $ git checkout -b <branch-name>
    
  • 커밋하지 않은 상태에서 브랜치 이동하기

    develop에서 작업한 내용을 저장하고 mastercheckout하려고 할 때 실패합니다. 해당 경우는 이동하려고하는 브랜치와 현재 브랜치의 내용이 서로 다를 때 에러가 발생합니다. 서로 다르지 않다면 수정하고도 브랜치 이동이 가능합니다.

    $ git checkout develop
    Switched to branch 'develop'
    # newfile을 열어 수정1을 입력하고 저장한다.
    $ git checkout master
    error: Your local changes to the following files would be overwritten by checkout:
            newfile
    Please commit your changes or stash them before you switch branches.
    Aborting
    
  • 브랜치 삭제

    브랜치를 삭제할 때는 병합되지 않은 브랜치는 에러를 발생 시킵니다.

    $ git brnach -d <branch-name>
    
  • 병합되지 않은 브랜치 삭제

    $ git branch -D <branch-name>
    
  • 모든 브랜치 푸쉬

    $ git push origin --all
    

8. stash

stash 는 작업 디렉토리와 인덱스의 현재 상태를 안전하게 저장할 수 있는 명령어입니다.

  • checkout 명령어를 쓰고싶은데 수정한 부분이 있어서 불가능할 때 작성하던 코드를 안전한 스택에 쌓아두는 방법입니다.
  • 저장한 내용을 다른 브랜치로 옮기는 것도 충분히 가능합니다.
  • 다른 브랜치에서 작업을 한 후에 다시 돌아와 복구하여 작업을 이어갈 수 있습니다.

  • 추적중이지 않은 파일 stash

    $ echo >> newfile_1
    $ git stash
    No local changes to save
    $ git status
    On branch develop
    Untracked files:
    (use "git add <file>..." to include in what will be committed)
    	newfile_1
    nothing added to commit but untracked files present (use "git add" to track)
    

    stash는 기본적으로 tracked파일을 기반으로 하기 때문에, 제대로 동작하지 않은 것을 확인할 수 있습니다.

  • 추적중이지 않은 파일 강제 stash

    작업 디렉토리와 인덱스의 상태가 저장되었습니다.

    $ git stash –u
    Saved working directory and index state WIP on develop: c462496 add newfile
    HEAD is now at c462496 add newfile
    

stash list

  • stash area에 저장되어 있는 것을 목록으로 확인하기

    $ git stash list
    stash@{0}: WIP on develop: c462496 add newfile
    
  • stash에 이름을 붙여 저장하기

    $ git stash save <stash-name>
    

    New file_2를 만들고, 이름 붙여 저장하고 , stash area   확인해보기

    $ git stash save –a add newfile_2
    Saved working directory and index state On develop: add newfile_2
    $ git stash list
    stash@{0}: on develop: add newfile_2
    stash@{1}: WIP on develop: c462496 add newfile
    

    이름을 정하지 않으면 stash 시점의 마지막 커밋 메세지로 저장되기 때문에 이름을 정해주는 것이 좋습니다.

**stash show **

  • stash에 대한 자세한 내용 확인하기

    git show stash@{0}
    

stash pop

  • stash area(스택)의 Top값을 현재 브랜치에 적용한 뒤 목록에서 제거합니다.

    $ git stash pop
    On branch develop
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
      
            new file:   newfile_2
      
    Dropped refs/stash@{0} (5791ac1e23141b31e2e0413912f6ad5c03127d25)
    $ git stash list
    stash@{0}: WIP on develop: c462496 add newfile
    

stash apply

  • stash area 의 Top값을 목록에서 제거하지 않습니다.

    $ git stash apply
    
  • pop , apply를 사용할 때 충돌이 난다면 충돌을 해결해줘야 다른 동작을 할 수 있습니다.

**stash drop **

  • id를 입력해주면 해당하는 stash를 삭제합니다.

    $ git stash drop stash@{0}
    

**stash clear **

  • 전부 삭제

    $ git stash clear
    

9. 병합

참고 사이트

브랜치는 커밋을 가리키는 포인터입니다. 서로 다른 두개의 브랜치가 가르키고 있는 커밋을 합치는 것을 병합( merge)라고합니다.

9.1 3-way merge

  • master 브랜치에서 develop브랜치 만들기

    $ git checkout -b develop
    
  • 커밋하나 남기기

    $ git commit -m "develop commit"
    
  • mastercheckout

    $ git checkout master
    
  • feature/1 브랜치 만들기

    $ git checkout -b feature/1
    
  • 커밋하나 남기기

    $ git commit -m "feature/1 commit"
    
  • 병합 시키기

    $ git checkout develop
    $ git merge feature/1
    

image

image

image

9.2 fast-forward merge

image

  • 위와 같이 master에서 bugFixmerge할 경우 masterbugfix와 이어져 있기 때문에 앞으로 당겨오기만 하면 됩니다.

  • 위에서 3-way merge를 했을 때 feature/1develop과 이어져 있지만 뒤에 위치한 경우 feature/1에서 developmerge할 경우 그저 앞으로 당겨지기만 하면 되기 때문에 fast-forward방식 병합입니다.

    $ git checkout feature/1
    
    $ git merge develop
    
    git log --oneline --decorate --all --graph -10
    

image

**충돌 해결 **

  • 같은 파일을 두 브랜치에서 수정하고 merge하면, git은 해당 부분을 자동으로 merge하지 못하기 때문에 충돌이 발생합니다. 충돌이 발생하면 개발자가 직접 충돌을 해결한 후에 다시 add,commit하면 됩니다.

10. 히스토리 수정하기

Git은 버전 관리 시스템으로써 커밋을 통해 버전별 코드를 저장할 수 있습니다. 하지만 이외에도 아래와 같은 기능으로 버전 관리를 좀 더 효과적으로 할 수 있게 해줍니다.

  • 커밋은 순서, 커밋메세지, 커밋한 파일을 변경할 수 있습니다.
  • 여러개의 커밋을 하나로 합치거나(squash) 하나의 커밋을 여러개의 커밋으로 분리할 수도 있습니다.
  • 커밋 전체를 삭제할 수도 있습니다.
  • 해시 값이 변경되기 때문에 원격 저장소에 저장된 커밋은 건드리지 않는 것이 좋습니다.

amend

  • 마지막 커밋을 수정할 수 있는 명령어

    $ git commit --amend
    
  • vi에디터를 통해서 커밋메세지를 변경

10.1 reset

reset명령어는 HEAD를 이전 상태로 되돌리고 과거부터 시작합니다.

그렇다면 HEAD가 무엇인지 부터 알아야 합니다. HEAD는 현재 브랜치의 마지막 커밋의 스냅샷입니다. 더 간단히 생각하면 현재 브랜치의 최신 버전인 것 같습니다. 즉, reset은 최신버전에 문제가 생겨서 이전 버전로 되돌려야할 떄 사용하는 것입니다.

reset에는 3가지 mode가 존재합니다.

작업 디렉토리를 유지하며 되돌리기 (--mixed)

--mixed로 입력하지 않아도 기본으로 적용되는 모드이다. 작업디렉토리는 유지하면서 인덱스를 HEAD와 함께 되돌립니다.

$ git reset 커밋 해시 

작업 디렉토리, 인덱스를 유지하며 되돌리기 (--soft)

현재의 인덱스 상태와 작업 디렉토리 내용을 그대로 보존한채 커밋만 취소힙니다.

$ git reset --soft 커밋 해시 

작업 디렉토리, 인덱스를 유지하지 않고 되돌리기 (--hard)

작업했던 내용을 의도적으로 모두 버릴 때 사용합니다. 실수를 해서 특정 커밋으로 되돌리고 싶을 경우에 사용할 것 같습니다.

revert

reset에서 남지 않는 취소 이력을 만들면서 취소하는 경우에 사용합니다.

$ git revert <커밋 id>
$ git revert <커밋 id> ^..<커밋 id>
$ git revert –m 1 <merge 커밋 id>

10.2 cherry-pick

  • 브랜치를 통째로 merge하지 않고, 특정 커밋을 반영하고 싶을 때 사용합니다.
  • merge방식과 마찬가지로 conflict 해줘야합니다.
  • merge방식은 해당 브랜치의 이력이 통째로 다 넘어가는 것 입니다.
  • cherry-pick 은 원하는 커밋만 pick해서 내 브랜치에 반영하고 싶은 경우에 사용합니다.

cherry-pick 실습

image{width:”400”}

// master branch 
master1
master2
master3
// develop branch
dev1
dev2
dev3

master브랜치에서 develop브랜치의 특정 커밋 내용만 cherry-pick을 해보겠습니다.

현재 HEADmaster3커밋에서 dev2커밋만 반영해보도록 하겠습니다.

$ git cherry-pick 90cd9f7

image

위 화면처럼 master3커밋에 dev2커밋 내용을 합치려고 하니 충돌이 발생했습니다. 현재 git의 상태를 확인해보겠습니다.

 ✘ macpro@macproui-MacBookPro  ~/Desktop/git-study   master ●✚  gst   
현재 브랜치 master
현재 90cd9f7 커밋을 뽑아 내고 있습니다.
  (충돌을 바로잡고 "git cherry-pick --continue"를 실행하십시오)
  (뽑기 작업을 취소하려면 "git cherry-pick --abort"를 사용하십시오)

병합하지 않은 경로:
  (해결했다고 표시하려면 "git add <파일>..."을 사용하십시오)
        양쪽에서 수정:  new_file

커밋할 변경 사항을 추가하지 않았습니다 ("git add" 및/또는 "git commit -a"를
사용하십시오)

충돌이 발생했을 경우 아래의 두 선택지가 있습니다.

# 충돌 해결 후 cherry-pick 완료하기
git cherry-pick --continue
# cherry-pick 취소하고 충돌 전 상태로 돌아가기 
git cherry-pick --abort

충돌을 잘 해결 했다면 --continue 옵션을 통해 넘어갈 것이고, 충돌 해결하는 과정에서 문제가 생겼거나 잘못된 cherry-pick을 했다면 --abort옵션으로 되돌릴 수 있습니다. 이처럼 취소하거나 넘어가는 선택은 이후에 정리할 rebase에서도 등장합니다.

저는 충돌을 해결하고 --continue를 해보도록 하겠습니다.

# 충돌 해결 후 
$ git add {충돌 파일}
$ git cherry-pick --continue

커밋 히스토리를 확인해보겠습니다.

* ffba3c7 (HEAD -> master) cherry-pick으로 master에  dev2반영
* 2fc8e72 master3
* a478e9c master2
| * fb5680e (develop) dev3
| * 90cd9f7 dev2
| * 0e58cfc dev1
|/  
* 502ff54 master1
* cd81d07 add new_file
* 1ba010f remove rm_test
* dba90fd add rm_test

잘 적용된 것을 확인할 수 있습니다.

cherry-pick을 적용한 커밋도 되돌리고 싶다면 reset명령어를 이용해서 되돌릴 수 있습니다.

git reset --hard 2fc8e72
* 2fc8e72 (HEAD -> master) master3
* a478e9c master2
| * fb5680e (develop) dev3
| * 90cd9f7 dev2
| * 0e58cfc dev1
|/  
* 502ff54 master1
* cd81d07 add new_file
* 1ba010f remove rm_test
* dba90fd add rm_test
* 3eb3830 1

10.3 rebase

git에서 브랜치를 합치는 2가지 방법 중에 하나인 rebase입니다. 단어 그대로 re + base “베이스를 바꾸다” 입니다. rebase를 사용하는 방법, 왜 사용하는지, 단점에 대해서 얘기해보겠습니다.

이해를 돕기 위해서 mergerebase를 비교하며 설명하도록 하겠습니다.

Merge

image

위와 같이 master branch와 3개의 commit 이 있는 상황에서

git checkout -b dev 

위 명령어를 통해서 새로운 dev branch를 만들고 dev branchcheckout을 하고 커밋을 2번 하게 되면 그래프는 아래처럼 됩니다.

image

위 상황에서 dev branchmaster branch로 병합하는 명령어를 실행해봅니다.

git checkout master
git merge dev

image

commit2를 가르키고 있던 master branch가 merge를 통해서 fast-forward되어서 dev와 같은 branch를 가리킵니다.

이렇게 보면 merge만 사용해도 이상적인 commit graph를 나타낸다고 생각할 수 도 있습니다. 하지만 제가 branch를 만들어서 작업을 하는 도중 팀원이 커밋을 진행했을 경우 아래와 같은 그래프를 나타납니다.

image

위 상황에서 mastercheckout을 해서 merge를 해주게 되면 그래프가 복잡해지는 상황이 나옵니다.

git checkout master
git merge dev

image

이렇게 하게되면 fast-forward가 아닌 3-way merge를 하며 merge commit을 남기게 됩니다.

Rebase

그렇다면 rebase를 사용하면 어떻게 되는지 확인해보겠습니다.

image

위에서 얘기했듯이 rebase란 base를 다시 지정하다라는 의미입니다. 현재 dev branch의 root는 commit2로서 master가 가르키고 있는 commit보다 뒤에 위치합니다.

여기서 아래와 같은 명령어를 사용해봅니다.

git checkout dev
git rebase master

# 위와 동일
git rebase master dev

위 명령어는 dev branch의 base를 master로 사용하겠다는 의미입니다.

image

그러면 위와 같은 형태의 그래프가 나타나게 됩니다.

이렇게 되면 master브랜치에서 dev브랜치를 병합하면 fast-forward으로 깔끔하게 병합이 됩니다.

’ 그래서 rebase를 왜 써야하는건데? ‘ 라고 생각이 들 수도 있습니다. 이유를 생각해보면 Git은 버전 관리 시스템이고, 버전을 보기 쉽게 관리하는 것이 좋은 것은 당연한 것 같습니다. 이렇게 적고보니 무조건 rebase로 브랜치 병합을 해야하는 것 처럼 보이지만 rebase를 조심해야하는 상황이 있습니다.

로컬에서만 사용하기

rebase를 통해 원격 저장소에 올라간 커밋을 변경하게 될 경우 다른 팀원 모두가 힘들어질 수 있습니다.

rebase를 하게 되면 커밋을 수정하기 때문에 rebase전의 커밋에서 수정하던 팀원은 원격저장소에 올리기 위해 다시merge를 해야하고 그렇게 올린 커밋을 제가 다시 pull하게 될경우 엉킬 수 있습니다. 이런 문제점을 막기 위해 로컬에서 rebase로 정리를 한 후에 원격저장소에 올리는 것이 맞습니다.

merge , rebase에 대한 생각

둘 중에서 뭐가 좋다라고 말할수는 없지만 많은 브랜치를 통해 개발하는 특성상 프로젝트의 규모가 커질수록 merge방식을 사용한다면 버전 관리가 어려워질 것 같습니다. rebase를 이해하고 사용한다면 가시적인 측면에서 더 좋은 히스토리를 남길 수 있을 것 같습니다.

10.4 squash

간단히 여러 개의 커밋을 하나의 커밋으로 합칠 수 있는 기능입니다. 위에서 정리한 rebase 처럼 커밋 히스토리를 더욱 깔끔하게 만들 수 있습니다.

dev3커밋부터 dev1커밋까지 합치기

* 2fc8e72 (master) master3
* a478e9c master2
| * fb5680e (HEAD -> develop) dev3
| * 90cd9f7 dev2
| * 0e58cfc dev1
|/  
* 502ff54 master1
* cd81d07 add new_file
* 1ba010f remove rm_test
* dba90fd add rm_test
* 3eb3830 1
git checkout develop
git rebase -i HEAD~3 
pick 0e58cfc dev1
pick 90cd9f7 dev2
pick fb5680e dev3
# 아래처럼 수정
pick 0e58cfc dev1
s 90cd9f7 dev2
s fb5680e dev3

충돌이 발생하지 않았기 때문에 커밋 메세지를 수정하는 곳으로 넘어왔습니다.

# 커밋 3개가 섞인 결과입니다.
# 1번째 커밋 메시지입니다:

dev1

# 커밋 메시지 #2번입니다:

dev2

# 커밋 메시지 #3번입니다:

dev3

# 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
"~/Desktop/git-study/.git/COMMIT_EDITMSG" 28L, 730C
* 3a89a53 (HEAD -> develop) dev1
| * 2fc8e72 (master) master3
| * a478e9c master2
|/  
* 502ff54 master1
* cd81d07 add new_file
* 1ba010f remove rm_test
* dba90fd add rm_test
* 3eb3830 1

위 그래프를 보시면 dev2,dev3은 없어진 것을 볼 수 있습니다. 하지만 코드는 dev2,dev3내용이 적용되어있습니다. squash를 통해 불필요한 커밋을 합쳐서 좀 더 보기 좋은 커밋 히스토리를 만들 수 있을 것 같습니다.

s

11. 원격(remote) 저장소

  • 원격 저장소 조회

    $ git remote -v
    
  • 원격 저장소 목록 조회

    $ git remote show origin 
    
  • 원격 저장소에 push

    $ git push origin <branch-name>
    
  • 원격 저장소에 강제로 push

    $ git push origin <branch-name> -f
    
  • 원격 저장소에 브랜치 삭제

    $ git push origin :<branch-name>
    

11.1 fetch

  • 로컬 저장소에는 없지만, 원격 저장소에는 있는 데이터를 모두 가져옵니다.
  • 이 때 로컬의 파일 내용은 변경되지 않고 그대로 남습니다. 원격 저장소의 데이터를 가져와서 저장해두고 사용자가 merge 하도록 준비만 해둡니다.
  • 원격 저장소의 모든 브랜치를 로컬에서 접근할 수 있어서 언제든지 merge를 하거나 내용을 살펴볼 수 있습니다.

11.2 pull

원격 저장소 브랜치에서 fetch 뿐만 아니라 merge까지 수행합니다.

  • $ git push  을 사용할 때 다른 팀원이 해당 브랜치에 커밋 후 푸쉬를 해서 내가 작업하던 커밋이 뒤쳐져 있을 때 아래와 같은 에러가 발생합니다.

    ! [rejected]        <branch-name> -> <branch-name> (non-fast-forward)
    error: failed to push some refs to '<repository-url>.git'
    hint: Updates were rejected because the tip of your current branch is behind
    hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
    hint: before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    이 때 pull명령어를 사용하라는 힌트를 줍니다. pull을 사용하면 merge커밋이 생성되기 때문에 이와 같은 상황에서는 아래 명령어를 통해 원격 저장소에 있는 최신 커밋을 pull한 후에 그 커밋으로 rebase를 해줌으로써 불필요한 커밋 생성을 줄일 수 있습니다.

    $ git pull --rebase 
    
  • pull

    $ git pull origin foo 
    $ git fetch origin foo; git merge origin foo