Git

GIT이란

  • 분산 버전 관리 시스템

기본 저장 방식

  • 커밋을 하거나 프로젝트의 상태를 저장 할 때마다 시간순으로 스냅샷의 형태로 저장한다
    • (모든 파일에 대해 추적하여 파일의 변화가 있을경우 해당 변화된 파일만을 새로이 저장한다, 파일이 변하지 않았을경우 해당 파일의 링크만 가지고 있다.)

GIT의 무결성

  • 데이터를 저장하기 전에 항상 SHA-1을 이용하여 체크섬을 만들어 관리 한다. 실제로 파일을 이름으로 저장하지 않고 해시 값으로 저장한다.

Git의 동작

  • Working Directory Staging area .git directory (repository)
    1. Working Directory에서 파일을 수정 한다.
    2. Staging Area에 파일을 Stage 해서 커밋할 스냅샷을 만든다.
    3. Staging Area에 있는 파일들을 커밋하여 .git Directory에 영구적인 스냅샷을 저장한다.

File의 상태

  • Working Directory
    • Untracked
      • 워킹 디렉토리에 있는 파일 중 스냅샷에도 Stageing Area에도 포함되지 않는 파일 이다. 처음 저장소를 init 하면 모든 파일이 Untracked이다.
      • clone 하게 되면 모든 파일은 Tracked 이면서 Unmodified이다. 파일을 checkout하고 나서 아무것도 수정하지 않았기 때문이다.
    • Tracked 이미 스냅샷에 포함 되어 있는 파일
      • Unmodified
        • commit후 파일을 수정하지 않았을 때의 상태
      • Modified
        • commit후 파일을 한번이라도 수정 한 상태
      • Staged
        • Modified인 경우 add를 한 상태
  • Life Cycle untracked->add file->staged->commit->unmodified->edit the file->modified->add file->staged->commit

우분투 GIT 설치

sudo apt-get purge runit
sudo apt-get purge git-all
sudo apt-get purge git
sudo apt-get autoremove
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git


GIT의 기초

Git 소스 받아 오기

git clone git://git.kernel.org/pub/scm/git/git.git


Git 계정 설정 하기

git config --global user.name "John Doe"
git config --global user.email johndoe@example.com


GIt 편집기 바꾸기

git config --global core.editor vim


Git 설정 확인

git config --list


Git 저장소 만들기

  • 기존 프로젝트를 저장소로 만드는 방법
    해당 디렉토리에서 `git init`
    
  • 기존 저장소를 clone하기
    git clone https://github.com/libgit2/libgit2 mylibgit
    
  • Git은 다양한 프로토콜을 지원 한다 git://, user@server:path/to/repo.git처럼 SSH프로토콜도 사용 가능 하다

**파일 만들기 **

echo 'test' > or >> test.md
touch test.md
vim test.md


파일 상태 확인 하기

git status
git status -s 짧게 확인 하기


파일 추적 하기

git add <file>
git add -A 한꺼번에 모두


파일 무시 하기

.gitignore 파일에 무시할 파일의 패턴을 적는다.

# 확장자가 .a인 파일 무시
*.a

# 윗 라인에서 확장자가 .a인 파일은 무시하게 했지만 lib.a는 무시하지 않음
!lib.a

# 현재 디렉토리에 있는 TODO파일은 무시하고 subdir/TODO처럼 하위디렉토리에 있는 파일은 무시하지 않음
/TODO

# build/ 디렉토리에 있는 모든 파일은 무시
build/

# doc/notes.txt 파일은 무시하고 doc/server/arch.txt 파일은 무시하지 않음
doc/*.txt

# doc 디렉토리 아래의 모든 .pdf 파일을 무시
doc/**/*.pdf


staged 와 unstaged의 상태의 변경 내용 보기

  • git diff 을 실행하면 수정 했지만 아직 staged 상태가 아닌 파일을 비교해 볼 수 있다. 이 명령은 워킹 디렉토리에 있는 것과 Staging Area에 있는 것을 비교한다. 그래서 수정하고 아직 Stage 하지 않은 것을 보여준다.

  • 만약 커밋하려고 Staging Area에 넣은 파일의 변경 부분을 보고 싶으면 git diff --staged 옵션을 사용한다. 이 명령은 저장소에 커밋한 것과 Staging Area에 있는 것을 비교한다.

  • Staged 상태인 파일은 git diff --cached 옵션으로 확인한다. --staged--cached 는 같은 옵션이다.

변경 사항 commit 하기

git commit 만 하게 되면 메세지를 적을 수 있는 vim창이 뜬다.
git commit -m 'commit message'
git commit -am 'Omit staging and commit message'


파일 삭제 하기

  • Git에서 파일을 제거하려면 git rm 명령으로 Tracked 상태의 파일을 삭제한 후에(정확하게는 Staging Area에서 삭제하는 것) 커밋해야 한다. 이 명령은 워킹 디렉토리에 있는 파일도 삭제하기 때문에 실제로 파일도 지워진다.

  • Git 명령어로 제거 하지 않고 단순히 파일만 삭제 했을 경우 해당 파일은 Unstaged가 된다
  • git rm 으로 삭제 하면 파일은 Staged가 되어 commit 하게 되면 해당 파일은 더이상 추적 하지 않는다.

  • git rm --cached <file> 은 Staging Area에서만 삭제 하고 워킹 디렉토리의 파일 지우지 않게 할수 있다

파일 이름 변경 하기

it mv file_from file_to을 하면 Git이 알아서 이름이 바뀐 사실을 알고 있고 stageing되어 있다.

커밋 히스토리 조회 하기

git log
git log -p -2 각 커밋의 diff결과를 최근 2개 만 보여 줌
git log --oneline --all --graph 모든 커밋 한줄로 그래프 표현
git log --stat 통계 조회
git log --pretty=oneline|short|full|fuller 커밋 정보를 가감해서 보여줌
git log --pretty=format:"%h - %an, %ar : %s" 결과를 custom으로 파싱 할때 씀 


git log –pretty=format에 쓸 몇가지 유용한 옵션
옵션 설명 옵션 설명 옵션 설명
%H 커밋 해시 %h 짧은 길이 커밋 해시 %T 트리 해시
%t 짧은 길이 트리 해시 %P 부모 해시 %p 짧은 길이 부모 해시
%an 저자 이름 %ae 저자 메일 %ad 저자 시각 (형식은 –date= 옵션 참고)
%ar 저자 상대적 시각 %cn 커미터 이름 %ce 커미터 메일
%cd 커미터 시각 %cr 커미터 상대적 시각 %s 요약
 git log --pretty=format:"%h %s" --graph


git log 주요 옵션
옵션 설명
-p 각 커밋에 적용된 패치를 보여준다.
–stat 각 커밋에서 수정된 파일의 통계정보를 보여준다.
–shortstat –stat 명령의 결과 중에서 수정한 파일, 추가된 라인, 삭제된 라인만 보여준다.
–name-only 커밋 정보중에서 수정된 파일의 목록만 보여준다.
–name-status 수정된 파일의 목록을 보여줄 뿐만 아니라 파일을 추가한 것인지, 수정한 것인지, 삭제한 것인지도 보여준다.
–abbrev-commit 40자 짜리 SHA-1 체크섬을 전부 보여주는 것이 아니라 처음 몇 자만 보여준다.
–relative-date 정확한 시간을 보여주는 것이 아니라 ‘`2 weeks ago’’처럼 상대적인 형식으로 보여준다.
–graph 브랜치와 머지 히스토리 정보까지 아스키 그래프로 보여준다.
–pretty 지정한 형식으로 보여준다. 이 옵션에는 oneline, short, full, fuller, format이 있다. format은 원하는 형식으로 출력하고자 할 때 사용한다.


조회 제한 조건

지난 2주 동안 만들어진 커밋들만 조회하는 명령은 아래와 같다.

git log --since=2.weeks

진짜 유용한 옵션으로 -S가 있는데 이 옵션은 코드에서 추가되거나 제거된 내용 중에 특정 텍스트가 포함되어 있는지를 검색한다. 예를 들어 어떤 함수가 추가되거나 제거된 커밋만을 찾아보려면 아래와 같은 명령을 사용한다.

git log --Sfunction_name

마지막으로 파일 경로로 검색하는 옵션이 있는데 이것도 정말 유용하다. 디렉토리나 파일 이름을 사용하여 그 파일이 변경된 log의 결과를 검색할 수 있다. 이 옵션은 –`와 함께 경로 이름을 사용하는데 명령어 끝 부분에 쓴다

git log -- path1 path2


git log 조회 범위를 제한하는 옵션
옵션 설명
-(n) 최근 n 개의 커밋만 조회한다.
–since, –after 명시한 날짜 이후의 커밋만 검색한다.
–until, –before 명시한 날짜 이전의 커밋만 조회한다.
–author 입력한 저자의 커밋만 보여준다.
–committer 입력한 커미터의 커밋만 보여준다.
–grep 커밋 메시지 안의 텍스트를 검색한다.
-S 커밋 변경(추가/삭제) 내용 안의 텍스트를 검색한다.


이제 살펴볼 예제는 Merge 커밋을 제외한 순수한 커밋을 확인해보는 명령이다 Junio Hamano가 2008년 10월에 Git 소스코드 저장소에서 테스트 파일을 수정한 커밋들이다.

git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \ --before="2008-11-01" --no-merges -- t/


되돌리기

한 번 되돌리면 복구할 수 없기에 주의해야 한다. Git을 사용하면 우리가 저지른 실수는 대부분 복구할 수 있지만 되돌린 것은 복구할 수 없다.

  • 마지막 커밋을 취소 하고 staging area에 있는 것을 같이 다시 커밋한다.
    git commit --amend
    


  • 여러개의 file이 staging area에 같이 있을 경우 각각 따로 커밋 하고 싶을 때 하나의 file을 staging area에서 꺼내고 싶을때 file은 unstaged 상태가 된다.
    git reset HEAD <file>
    


  • modified 파일 되돌리기
    git checkout -- <file>
    

    git checkout – [file] 명령은 꽤 위험한 명령이라는 것을 알아야 한다. 원래 파일로 덮어썼기 때문에 수정한내용은 전부 사라진다. 수정한 내용이 진짜 마음에 들지 않을 때만 사용하자.

리모트 저장소

리모트 저장소 확인하기

git remote
git remote -v

git clone 하게되면 origin 이라는 리모트 저장소가 자동으로 등록 된다.

리모트 저장소 추가하기

git remote add <remote-name> <url>


리모트 저장소를 Pull 하거나 Fetch 하기

리모트 저장소에서 데이터를 가져 오기 fetch는 자동으로 merge를 하지 않는다

git fetch <remote-name>

pull은 자동으로 merge를 한다.

git pull

기본 저장소인 origin에 master 브랜치를 push한다!

git push origin master

리모트 저장소 살펴 보기

git remote show [remote-name]

리모트 저장소 이름을 바꾸거나 삭제하기

rename

git remote rename origin blog

remove

git remote remove blog
git remote rm blog

태그

태그 조회하기 다른 VCS처럼 Git도 태그를 지원한다. 사람들은 보통 릴리즈할 때 사용한다(v1.0, 등등). 이번에는 태그를 조회하고 생성하는 법과 태그의 종류를 설명한다.

git tag
git tag -l "v1.8.5*"
git show v1.4 태그정보와 커밋정보를 동시에 볼수 있다.

태그 붙이기 Git의 태그는 Lightweight 태그와 Annotated 태그로 두 종류가 있다.

  • Annotated 태그 Annotated 태그는 Git 데이터베이스에 태그를 만든 사람의 이름, 이메일과 태그를 만든 날짜, 그리고 태그 메시지도 저장한다. GPG(GNU Privacy Guard)로 서명할 수도 있다.
git tag -a v1.4 -m "my version 1.4"
git tag
v1.4
  • Lightweight 태그

나중에 태그 하기 특정 커밋에 태그하기 위해서 명령의 끝에 커밋 체크섬을 명시한다

git tag -a v1.2 9fceb02

태그 공유 하기 git push 명령은 자동으로 태그를 전송 하지 않는다. 명시적으로 태그를 push해야 한다.

git push origin v1.5

태그를 checkout하기 태그는 브랜치와는 달리 가리키는 커밋을 바꿀 수 없는 이름이기 때문에 Checkout 해서 사용할 수 없다. 태그가 가리키는 특정 커밋 기반의 브랜치를 만들어 작업하려면 아래와 같이 새로 브랜치를 생성한다.

git checkout -b version2 v2.0.0

브랜치

새 브랜치 생성하기

지금 작업 하고 있던 마지막 커밋을 가리킨다.

git branch testing


HEAD 포인터를 이용하여 지금 작업하고 있는 브랜치를 가리킨다. 기본적으로 Master 브랜치를 가리킨다.

브랜치 이동하기

HEAD는 testing 을 가리킨다.

git checkout testing

브랜치와 Merge의 기초

브랜치를 만들고 바로 iss53브랜치로 checkout 하기

git checkout -b iss53

브랜치를 이동 하려면 워키 디렉토리를 정리 하는것이 좋다 작업하던것들을 모두 커밋 mater 브랜치에 hotfix 브랜치를 합쳐 보자

git checkout master
git merge hotfix

더이상 필요 없는 hotfix 브랜치를 삭제 한다

git branch -d hotfix

merge의 기초

fast-forward는 현재 브랜치가 가리키는 커밋이 merge 할 브랜치의 조상이므로 빨리감기로 최신 커밋으로 이동 하는것. 하지만 fast-forward가 아닐경우 각 브랜치가 가리키는 커밋 두개와 공통조상 하나를 아요하여 3way-merge를 한다. 단순히 최신 커밋으로 이동이 아니라 결과를 별도의 커밋으로 만들어 해당 브랜치가 그 커밋을 가리키게 만든다. 그래서 부모가 여러개 이고 Merge 커밋이라 부른다.

충돌의 기초

머지가 실패할 경우도 있는데, Merge하는 두 브랜치에서 같은 파일을 수정하는 경우 conflict가 발생 한다. Git은 자동으로 커밋하지 못하여 새 커밋이 생기지 않는다. git status를 통하여 변경사항을 확인하고 개발자가 직접 merge(직접수정) 하여야 한다.

<<<<<<< HEAD:index.html '(master 브랜치의 변경사항)'
<div id="footer">contact : email.support@github.com</div>
======= '(iss53 브랜치의 변경사항)'
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

이후 git add로 다시 저장 한다. 다른 Merge도구를 이용할 수도 있다.

git mergetool

이후 git commit으로 merge commit을 만들면 된다.

브랜치 관리

git branch
git branch -v
git branch --merged
git branch --no-merged

==브랜치워크플로==

배포했거나 배포 할 코드만 master 브랜치에 Merge하여 안정 버전의 코드만 master브랜치에 둔다. 개발을 진행하고 안정화 하는 브랜치는 develop 이나 next라는 브랜치를 만들고 테스트를 거쳐 안정 된 상태가 되면 이 브랜치를 master에 Merge시킨다. 각 브랜치를 하나의 실험실로 생각 하라

토픽 브랜치

앞서 본 hotfix나 iss53 브랜치가 토픽 브랜치에 해당 된다. iss91 이라는 브랜치를 만들어 이슈를 수정하던 도중 다른 방법으로 해당 이슈를 수정하고 싶을때 iss91-2라는 브랜치를 또 만들어 둘중에 마음에 드는 것으로 develop브랜치에 merge하면 된다.

리모트 브랜치

서버 저장소에 있는 브랜치 리모트 브랜치의 이름은 (remote)/(branch)로 되어 있다. originmaster 브랜치를 보고 싶다면 origin/master로 브랜치를 확인하면 된다.

git ls-remote [remote]
git remote show [remote]

리모트 서버로부터 저장소를 동기화 하려면 git fetch origin 명령을 사용한다. 새로운 정보가 있으면 내려받고 받은 데이터를 로컬 저장소에 업데이트 하고 나서 origin/master 포인터의 위치를 최신 커밋으로 이동 시킨다.

로컬 브랜치를 서버로 전송 하려면 명시적으로 브랜치를 push해야 정보가 전송 된다.

git push <remote> <branch>

fetch 명령으로 리모트 트래킹 브랜치를 내려 받는 다고 해서 로컬 저장소에 수정할 수 있는 브랜치가 새로 생기는 것은 아니다. 그저 수정 못하는 origin/remotebranch가 생기는 것이다. 새로 받은 브랜치를 Merge 하려면 git merge origin/remotebranch를 사용한다. 만약 Merge 하지 않고 리모트 트래킹 브랜치에서 시작하는 새 브랜치를 만들려면

git checkout -b serverfix origin/serverfix

그러면 origin/serverfix에서 시작하고 수정 할 수 있는 serverfix 브랜치가 만들어 진다.

브랜치 추적

리모트 트래킹 브랜치를 로컬 브랜치로 checkout 하면 자동으로 트래킹 브랜치가 만들어 진다.(트래킹 하는 대상 브랜치를 UpsteamBranch 라고 부른다 서버로 부터 저장소를 clone하면 자동으로 master 브랜치를 origin/master브랜치의 트래킹 브랜치로 만든다. 트래킹 브랜치를 직접 만들 수 이는데 리모트를 origin 이 아닌 다른 리모트로 할 수도 있고, 브랜치도 master가 아닌 다른 브랜치로 추적하게 할 수 있다. git checkout -b [branch][remotename]/[branch] 명령으로 간단히 트래킹 브랜치를 만들 수 있따. --track 옵션을 사용하여 로컬 브랜치 이름을 자동으로 생성할 수 있다.

git checkout --track origin/serverfix
git checkout -b sf origin/serverfix
git branch -u origin/serverfix

추적 브랜치가 현재 어떻게 설정 되어 있는지 확인

git branch -vv

결과가 모두 마지막으로 서버에서 데이터를 가져온(fetch)시점으로 계산함. 최신 데이터로 추적상황을 알아 보려면

git fetch --all; git branch -vv

pull 하기

git fetch 명령을 싱행하면 서버에는 존재 하지만, 로컬에는 아직 없는 데이터를 받아와서 저장함. 이때 워킹 디렉토리의 파일은 변경되지 않고 그대로 남음.서버로부터 데이터를 가져와 사용자가 Merge하도록 준비만 해줌 git pull 명령은 git fetch 명령을 실행하고 나서 자동으로 git merge명령을 수행하는 것 뿐 일반적으로 fetchmerge명령을 명시적으로 사용하는 것이 pull 명령으로 한번에 두 작업을 하는 것보다 낫다.

리모트 브랜치 삭제

git push origin -delete serverfix

Git 브랜치 -Rebase하기

Git에서 한 브랜치에서 다른 브랜치로 합치는 방법으로는 두 가지가 있음.

  • merge - 3way-사용하여 두 브랜치의 마지막 커밋 두개와 공통의 부모를 통해 merge함. merge commit이 생성됨. 프로젝트의 히스토리를 보고 싶을 경우 사용
  • rebase- 한 브랜치에서 변경된 사항은 Patch로 만들고 다른 브랜치에 적용 시키는 방법. 공통 부모 커밋으로 이동하여 checkout한 브랜치까지 diff를 차례로 만들어 어딘가에 저장해놓고 rebase할 브랜치가 합칠(master) 브랜치가 가리키는 커밋을 가리키게 하고 저장해놓은 diff를 차례대로 적용 한다.rebase는 보통 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을때 사용
    git checkout experiment
    git rebase master
    git checkout master
    git merge experiment
    

***Rebase 활용``` master 브랜치 에서 갈라져 나온 server 브랜치가 있고 server 브랜치에서 갈라져 나온 client 브랜치가 있다고 하자 이때 테스트가 덜 된 server 브랜치는 그대로 두고 client 브랜치만 master브랜치로 합치려는 상황을 하려고 한다.이떄 --onto 옵션을 사용하여 명령을 실행한다.

git rebase --onto master server client

이 명령은 master 브랜치부터 server 브랜치와 client브랜치의 공통 조상까지의 커밋을 client브랜치에서 없애고 싶을때 사용함 client 브랜치에서만 변경된 Patch를 만들어 master 브랜치에서 client 브랜치를 기반으로 새로 만들어 적용 함. 그리고 나서

git checkout master
git merge clinet

를 하게 되면 fast-forward 가 동작 함 그리고 server 브랜치의 작업이 다 끝나면 git rebase [basebranch] [topicbranch] 라는 명령으로 checkout하지 않고 바로 server 브랜치를 master 브랜치로 rebase 할 수 있다. 이 명령은 토픽(server) 브랜치를 checkout하고 베이스(master)브랜치에 rebase 한다.

git rebase master server

server 브랜치의 수정사항을 master 브랜치에 적용함. 그 결과는 master 브랜치에 server 브랜치의 수정사항을 적용 한다. 그리고 나서 master 브랜치를 Fast-forward 시킨다.

git checkout master
git merge server

모든 것이 master 브랜치에 통합 됐기 때문에 더 필요 하지 않으면 clientserver 브랜치는 삭제 해도 된다.

Rebase의 위험성

이미 공개 저장소에 Push한 커밋을 Rebase 하지 마라. rebase는 기존의 커밋을 그대로 사용하는게 아니라 내용은 같지만 다른 커밋을 새로 만든다. 그렇기 때문에 동료와의 협업에 있어서 서로 다른 커밋 상태를 가지게되므로 동료가 push 했을때 다시 merge를 하고 그 것을 내가 pull 하게되면 코드는 엉망이 되어 버린다.

rebase 한것을 다시 rebase하기

어떤 팀원이 강제로 내가 한일을 덮어 썼을 경우 내가 했던일이 무엇이고 덮어쓴 내용이 무엇인지 알아야 한다. 커밋 체크섬외에도 Git은 커밋에 Patch할 내용으로 체크섬을 한번 더 구한다. 이값을 patch-id라고 한다. 덮어쓴 커밋을 받아서 그 커밋을 기준으로 rebase 할 때 Git은 원래 누가 작성한 코드인지 잘 찾아 낸다. 한 팀원이 다른 팀원이 의존하는 커밋을 없애고 rebase한 커밋을 다시 push 한 상황에서 merge 하는 대신 git rebase teamone/master 명령을 통해 아래와 같은 작업을 한다.

  • 현재 브랜치에만 포함된 커밋을 찾음.
  • merge 커밋을 가려냄.
  • 이중 덮어쓰지 않은 커밋들만 골라냄.
  • 남은 커밋ㅇ르만 다시 teamone/master를 바탕으로 커밋을 쌓는다 제대로 정리된 강제로 덮어쓴 브랜치에 rebase하기 같은 결과를 얻을 수 있다.


Rebase vs. Merge Merge가 뭔지, Rebase가 뭔지 여러 예제를 통해 간단히 살펴보았다. 지금쯤 이런 의문이 들 거로 생각한다. 둘 중 무엇을 쓰는 게 좋지? 이 질문에 대한 답을 찾기 전에 히스토리의 의미에 대해서 잠깐 다시 생각해보자.

히스토리를 보는 관점 중에 하나는 작업한 내용의 기록으로 보는 것이 있다. 작업 내용을 기록한 문서이고, 각 기록은 각각 의미를 가지며, 변경할 수 없다. 이런 관점에서 커밋 히스토리를 변경한다는 것은 역사를 부정하는 꼴이 된다. 언제 무슨 일이 있었는지 기록에 대해 _거짓말_을 하게 되는 것이다. 이렇게 했을 때 지저분하게 수많은 Merge 커밋이 히스토리에 남게 되면 문제가 없을까? 역사는 후세를 위해 기록하고 보존해야 한다.

히스토리를 프로젝트가 어떻게 진행되었나에 대한 이야기로도 볼 수 있다. 소프트웨어를 주의 깊게 편집하는 방법에 메뉴얼이나 세세한 작업내용을 초벌부터 공개하고 싶지 않을 수 있다. 나중에 다른 사람에게 들려주기 좋도록 Rebase 나 filter-branch 같은 도구로 프로젝트의 진행 이야기를 다듬으면 좋다.

Merge 나 Rebase 중 무엇이 나으냐는 질문은 다시 생각해봐도 답이 그리 간단치 않다. Git은 매우 강력한 도구고 기능이 많아서 히스토리를 잘 쌓을 수 있지만, 모든 팀과 모든 이가 처한 상황은 모두 다르다. 예제를 통해 Merge 나 Rebase가 무엇이고 어떤 의미인지 배웠다. 이 둘을 어떻게 쓸지는 각자의 상황과 각자의 판단에 달렸다.

일반적인 해답을 굳이 드리자면 로컬 브랜치에서 작업할 때는 히스토리를 정리하기 위해서 Rebase 할 수도 있지만, 리모트 등 어딘가에 Push로 내보낸 커밋에 대해서는 절대 Rebase 하지 말아야 한다

Comments