🌍 English ∙ Español ∙ Русский ∙ 繁體中文 ∙ 简体中文 ∙ 한국어 ∙ Tiếng Việt ∙ Français ∙ 日本語
뭔가 잘못 됐을 때 뭘 해야할지에 대한 우주비행사를 위한 가이드 (여기선 깃을 쓰는 개발자를 위한)
Flight Rules 는 어떤 문제 X가 발생한 이유와 그 단계의 매뉴얼에서 어렵사리 얻은 지식이에요. 기본적으로 각 시나리오의 매우 자세하고 구체적인 운영 절차랍니다. [...]
NASA는 수성(Mercury) 시대 때 지상팀에서 처음으로 "lessons learned" 이란 것을 모았는데 수천개의 문제의 상황들, 부서진 해치 손잡이로 인한 엔진 고장부터 컴퓨터 문제 그리고 그 해답까지, 1960년대 초부터 우리의 실수들, 재앙들, 해결책 등이 목록화 돼있어요.
— Chris Hadfield, 인생을 위한 우주비행사의 가이드.
명확하게 하기 위해 이 문서의 모든 예제는 현재 브랜치를 표시하고 스테이지에 변경이 있는지를 나타내기 위해 커스텀 된 배시 프롬프트를 써요. 브랜치는 괄호 안에 있고, 브랜치 다음의 *는 스테이지의 변경된 것을 나타내요.
Table of Contents generated with DocToc
- 레파지토리
- 커밋 수정
- 스테이지
- 스테이지 전의 변경점
- 브랜치
- 모든 브랜치 리스트를 보고 싶어
- 커밋에서 브랜치 만들기
- 다른 브랜치에서 풀을 받아와버렸어
- 로컬의 커밋을 지워서 서버에 있는 내 브랜치와 맞추고 싶어
- 새 브랜치 대신에 마스터에 커밋을 해버렸어
- 다른 레퍼런스 같은 곳에서 모든 파일을 유지하고 싶어
- 한 브랜치에 다른 브랜치에 남겼어야 하는 커밋을 여러개 남겼어
- 업스트림에선 지워진 로컬 브랜치를 지우고 싶어
- 브랜치를 지워버렸어
- 브랜치를 지우고 싶어
- 여러개의 브랜치를 지우고 싶어
- 브랜치 이름을 바꾸고 싶어
- 다른 사람이 작업중인 리모트 브랜치로 체크아웃 하고 싶어
- 현재 로컬 브랜치로 새로운 리모트 브랜치를 만들고 싶어
- 리모트 브랜치를 로컬 브랜치를 위한 업스트림으로 설정하고 싶어
- HEAD를 기본 리모트 브랜치로 트래킹하도록 설정하고 싶어
- 다른 브랜치에 변경점을 잘못 남겼어
- 리베이스와 머지
- 스테이시
- 찾아보기
- 서브모듈
- 기타 항목들
- 파일 추적하기
- 설정
- 뭘 잘못했는지 모르겠어
- 다른 리소스
이미 존재하는 프로젝트 디렉토리를 깃 레파지토리로 최적화해 쓰려면:
(my-folder) $ git init
리모트 레파지토리를 클론하려면, 레파지토리 URL을 복사해와서 실행해요.
$ git clone [url]
폴더 이름이 리모트 레파지토리 이름과 같이 저장될 거에요.
복제할 리모트 서버의 연결을 확인하세요.(대부분 인터넷 연결을 확인하란 뜻이에요)
다른 레파지토리 이름으로 복제를 해오고 싶다면
$ git clone [url] name-of-new-folder
git commit -a
로 막 커밋을 남기고 내가 뭐라고 안에 적었더라? 한다고 하고. 최근의 커밋을 현재 HEAD에서 볼 수 있어요.
(main)$ git show
또는
$ git log -n1 -p
만약 특정 커밋의 파일을 보고 싶다면, 이렇게 할 수도 있어요. (commitID는 바로 당신이 관심있는 그 commit이에요)
$ git show <commitid>:filename
만약 메시지를 잘못 썼고 아직 푸시를 안했다면, 커밋 메시지 바꾸기를 따라해 볼 수 있어요.
$ git commit --amend --only
이 방법은 편집 가능한 기본 텍스트 에디터가 열릴텐데요, 다른 방법으론 한줄에 쓸 수도 있어요.
$ git commit --amend --only -m 'xxxxxxx'
만약 푸시를 이미 했다면, 커밋을 수정해서 강제 푸시를 할 수 있긴한대 별로 추천하진 않아요.
하나의 커밋이라면 이렇게 수정해요.
$ git commit --amend --no-edit --author "New Authorname <[email protected]>"
대안으로는 git config --global author.(name|email)
에서 설정을 다시 맞춘 다음
$ git commit --amend --reset-author --no-edit
만약 전체 이력 변경이 필요하다면, git filter-branch
의 설명 페이지를 봐요.
지난 커밋에서 파일 변경내역을 지우려면, 이렇게 해봐요:
$ git checkout HEAD^ myfile
$ git add myfile
$ git commit --amend --no-edit
그 파일이 새롭게 커밋으로 추가됐고 그 파일만 지우고 (git 에서만) 싶은 경우엔,
$ git rm --cached myfile
$ git commit --amend --no-edit
이 방법은 열린 패치가 있고 불필요한 파일을 커밋 했거나 리모트에 강제 푸시로 패치를 업데이트 해야할때 특히 유용해요. --no-edit
옵션은 기존 커밋 메세지를 유지하는데 사용돼요.
푸시된 커밋을 지우고 싶다면 이걸 따라하면 되는데, 이력을 돌이킬 수 없게 되고 레파지토리에서 이미 풀을 받아간 다른 사람의 이력도 엉망이 되요. 간단히 말하자면, 잘 모르겠으면 절대 하지마요.
$ git reset HEAD^ --hard
$ git push --force-with-lease [remote] [branch]
아직 푸시 안했으면, 리셋으로 마지막 커밋 전 상태로 돌아가요. (변경점은 스테이지에 두고서)
(my-branch)$ git reset --soft HEAD^
이 방법은 푸시를 안 했을 때만 동작해요. 푸시를 했으면, 안전한 방법은 git revert SHAofBadCommit
한가지 밖이에요.
이 방법은 모든 지난 커밋 변경점으로 되돌아간 새 커밋을 만들 거에요. 또는, 만약 푸시한 브랜치가 리베이스에 안전하다면 (만약 다른 사람이 풀 받지 않는다면), git push --force-with-lease
명령어를 쓸수 있어요.
더 알고 싶다면, 이 섹션을 참고해주세요.
이전과 동일한 경고에요. 가능한 이 방법은 쓰지 마세요.
$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
$ git push --force-with-lease [remote] [branch]
아니면 대화형 리베이스를 쓰고 지우고 싶은 커밋 라인을 지워도 돼요.
To https://github.com/yourusername/repo.git
! [rejected] mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
알아두세요, 리베이스(아래를 보세요)나 어멘드는 기존 커밋을 새걸로 바꿔요, 그래서 이미 먼저 수정된 커밋이 푸시됐다면 강제 푸시를 해야 해요. 이 방법을 쓸땐 조심하세요; 항상 작업되는 브랜치가 맞나 확인해요!
(my-branch)$ git push origin mybranch --force-with-lease
일반적으로 강제 푸시를 쓰지 마세요.
새 커밋을 만들어서 푸시하는게 수정된 커밋을 강제로 푸시하는 것보다 훨씬 나아요. 그런 수정된 커밋은 그 브랜치나 다른 자식 브랜치를 쓰는 다른 개발자의 소스 이력과 충돌의 원인이 될거에요.
--force-with-lease
는 여전히 실패할텐데, 누군가가 같은 브랜치를 쓴다면 변경점을 덮어쓰는 푸시를 할 수도 있어요.
절대로 아무도 같은 브랜치를 안 쓰거나, 절대로 브랜치에 업데이트를 해야할때 --force
(-f
) 옵션을 쓸 수 있지만 일반적으론 피하는게 좋아요.
만약 하드 리셋을 했다고 해도 커밋을 돌릴 순 있어요. 깃은 며칠간은 로그를 가지고 있거든요.
알아두기 : 이건 커밋을 남겼거나 스테이시같이 백업을 했을 때만 유효해요. git reset --hard
은 커밋되지 않은 수정사항을 다 지울 거에요, 그러니 조심해서 써야해요. (안전한 방법으론 git reset --keep
이 있어요)
(main)$ git reflog
지난 커밋과 리셋을 위한 커밋을 볼 수 있을 거에요. 돌아가고 싶은 커밋의 SHA 코드를 골라서, 리셋을 해요:
(main)$ git reset --hard SHA1234
계속 할 수 있을거에요.
만약 실수로 머지할 준비가 안된 피쳐 브랜치를 메인 브랜치에 머지했어도 되돌릴 순 있어요. 하지만 문제는 있어요: 머지 커밋은 한개 이상의 부모(보통은 두 개)를 가지게 돼요.
사용하려면
(feature-branch)$ git revert -m 1 <commit>
여기서 -m 1 옵션은 부모 번호 1(머지가 만들어진 브랜치)을 되돌릴 상위 항목으로 선택하라고 해요.
알아두기 : 부모 번호는 커밋 식별자가 아니고, 오히려 머지된 커밋이 Merge: 8e2ce2d 86ac2e7
이라는 라인을 가지고 있어요.
부모 번호는 이 라인에서 원하는 부모의 1 기반 인덱스이고, 첫번째 식별자는 1, 다음은 2 이렇게 이어져요.
(my-branch*)$ git commit --amend
보통은 부분적으로 파일을 스테이지하려면 이렇게 해요:
$ git add --patch filename.x
-p
는 축약된 옵션이에요. 이 방식은 대화형 모드를 열텐데요. s
옵션을 쓰면 커밋을 나눌 수 있어요. 하지만 새 파일이라면 그런 옵션이 없을거에요. 새 파일을 추가하려면:
$ git add -N filename.x
그 다음 임의적으로 라인들을 골라 추가해주려면 e
옵션이 필요할거에요. git diff --cached
나 git diff --staged
는 로컬에 저장된 부분과 스테이지에 있는 라인들을 비교해서 보여줄 거에요.
git add
는 전체 파일들을 커밋에 추가해요. git add -p
는 대화형으로 추가하고픈 변경점들을 고를 수 있어요.
이건 좀 꼼수인데요, 스테이지 전인 파일들을 스테이시해서 빼두고선 리셋 할 수 있을거에요. 그 다음 스테이시를 다시 불러와 추가를 해요.
$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A
$ git checkout -b my-branch
$ git stash
$ git checkout my-branch
$ git stash pop
만약 모든 스테이징 됐거나 안 된 변경점을 버리고 싶다면 이렇게 해요:
(my-branch)$ git reset --hard
# or
(main)$ git checkout -f
이 방법은 git add
로 스테이징된 모든 파일이 빠지게 돼요.
$ git reset
이 방법은 커밋되지 않은 모든 로컬 변경점이 되돌려 져요. (레파지토리 최상단 루트에서 실행해야 할거에요)
$ git checkout .
또 커밋되지 않은 변경점들 중 몇가지 파일이나 디렉토리만 되돌릴 수 있어요.
$ git checkout [some_dir|file.txt]
거기에 또 다른 되돌리는 방법으로 (타이핑 칠게 많지만 어떤 하위 디렉토리에서도 돼요):
$ git reset --hard HEAD
이 방법은 모든 트래킹되지 않은 파일들을 지워요, 그래서 깃에서 트래킹되는 파일들만 남아요:
$ git clean -fd
-x
옵 또한 무시된 파일들을 다 지워요.
작업중인 영역에서 전체가 아닌 특정 부분을 지우고 싶을때 원치않는 변경점을 확인하고, 변경점을 잘 보관하세요.
$ git checkout -p
# 날리고 싶은 사항에 y를 적으세요
또다른 전략은 stash
을 같이 쓰는거에요. 챙겨야 하는 변경점을 스테이시 하고, 작업 중인 영역을 리셋하고, 다시 올바른 변경점으로 재적용해요.
$ git stash -p
# 저장하고 싶은 사항들을 다 선택하세요
$ git reset --hard
$ git stash pop
대안으로, 원치않는 변경점을 스테이시해서 그걸 날리는 방법도 있어요.
$ git stash -p
# 저장하고 싶지 앟은 사항들을 다 선택하세요
$ git stash drop
작업 영역에서 특정 파일을 지우고 싶을 때.
$ git checkout myFile
대안으로, 작업영역 내 여러 파일들을 지우고 싶을때 모두 나열해서 적어요.
$ git checkout myFirstFile mySecondFile
모든 스테이징 안된 커밋 전인 변경점을 지우고 싶을 때
$ git checkout .
트래킹 안된 파일들 다 지우고 싶을 땐
$ git clean -f
로컬 브랜치 다 보기
$ git branch
리모트 브랜치 다 보기
$ git branch -r
로컬과 리모트 브랜치 모두 보기
$ git branch -a
$ git checkout -b <branch> <SHA1_OF_COMMIT>
이건 잘못된 풀을 받기전 HEAD가 어딜 가르키고 있었는지 볼 수 있는 git reflog
를 써볼 수 있는 기회에요.
(main)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here
간단히 원하는 커밋으로 브랜치를 되돌릴 수 있어요:
$ git reset --hard c5bc55a
끝!
서버에 변경점을 푸시 안했는지부터 확인해요.
git status
가 오리진보다 몇개의 커밋들이 앞서 있는지 보여줄거에요:
(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
# (use "git push" to publish your local commits)
#
오리진(리모트과 같은 상태의)로 맞추는 리셋을 하는 방법 중 하나는:
(my-branch)$ git reset --hard origin/my-branch
마스터에 있으면서 새 브랜치를 만들어요:
(main)$ git branch my-branch
마스터 브랜치를 기존 커밋으로 리셋해요:
(main)$ git reset --hard HEAD^
HEAD^
는 HEAD^1
의 축약인데요. HEAD^
의 첫번째 부모를 의미하고, 비슷한 HEAD^2
는 두번째 부모를 의미해요. (머지는 두 부모를 가질 수 있죠)
알아두세요 HEAD^2
는 HEAD~2
과 같은게 아니에요. (더 자세한 정보는 이 링크를 참고해요 )
대안으로, HEAD^
를 쓰고 싶지 않다면, 마스터 브랜치로 옮길 커밋 해시를 알아둬요 (git log
가 트릭을 부릴 거에요) 그리고 그 해쉬로 리셋을 해요. git push
가 리모트랑 변경점이 똑같은걸 확인해줄거에요.
예를 들자면, 그 마스터의 커밋의 해쉬가 a13b85e
라면:
(main)$ git reset --hard a13b85e
HEAD is now at a13b85e
새 브랜치로 체크아웃 해서 계속 작업을 해요:
(main)$ git checkout my-branch
수백번의 변경점을 가진 스파이크(아래 알아두기 참고) 작업을 한다고 가정해보죠. 모든 건 동작하고 있고,그 작업을 저장해두기 위해 다른 브랜치로 커밋을 해요:
(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."
그 커밋을 브랜치(아마 feature일수도 있고, develop
일수도 있겠죠)에 넣고 싶을 때, 모든 파일을 지키는데 관심이 있을거에요. 큰 커밋을 작게 나누고 싶을거에요.
현재 가지고 있는건:
- 스파이크를 위한 솔루션과 함께인
solution
브랜치.develop
브랜치의 1단계 앞선 상태. - 변경점을 추가하고 싶은
develop
브랜치
브랜치로 내용들을 불러오는 것으로 해결할 수 있어요:
(develop)$ git checkout solution -- file1.txt
develop
브랜치에서 solution
브랜치의 저 파일의 내용들을 얻을 수 있어요.
# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: file1.txt
그 다음, 평소처럼 커밋해요.
알아두기 : 스파이크 솔루션은 문제를 분석하거나 풀기위해 만들어졌어요. 이 솔루션들은 모두가 문제의 확실한 시각화를 얻고선 평가되고 제거돼요.~ 위키피디아.
마스터 브랜치에 있다고 가정하고 git log
해보면 커밋 두개 볼 수 있을거에요:
(main)$ git log
commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <[email protected]>
Date: Tue Jul 22 15:39:27 2014 -0400
Bug #21 - Added CSRF protection
commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <[email protected]>
Date: Tue Jul 22 15:39:12 2014 -0400
Bug #14 - Fixed spacing on title
commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <[email protected]>
Date: Tue Jul 21 01:12:48 2014 -0400
First commit
각 버그커밋의 해쉬를 가져와요. (21번은 e3851e8
, 14번은 5ea5173
)
우선, 마스터 브랜치의 정확한 커밋 (a13b85e
)으로 리셋해요:
(main)$ git reset --hard a13b85e
HEAD is now at a13b85e
그리고, 21번 버그 작업을 위한 새로운 브랜치를 만들수 있어요:
(main)$ git checkout -b 21
(21)$
그리고 21번 버그 커밋을 체리픽 해서 브랜치 최상단에 올려요. 그 커밋을 적용할건데, 오직 그 커밋만을 헤드에 뭐가 있든 최상단으로 적용할거란 의미에요.
(21)$ git cherry-pick e3851e8
이 지점에서 충돌이 있을 수도 있어요. 어떻게 충돌을 해결할지 대화형 리베이스 섹션 안에 있는 충돌이 있어 부분을 참고하세요.
자 이제 14번 버그 작업을 위해 마스터로 가서 새 브랜치를 만들어요.
(21)$ git checkout main
(main)$ git checkout -b 14
(14)$
그리고 마지막으로, 14번 버그작업을 위한 커밋을 체리픽해요.
(14)$ git cherry-pick 5ea5173
깃헙에 풀리퀘스트로 머지를 하면, 포크 뜬 머지 브랜치를 지울껀지 선택할 수 있는 옵션을 줘요. 해당 브랜치에 계속 작업할 예정이 없다면, 다량의 오래된 브랜치들로 뒤덮이지 않게 로컬 작업을 지워주는게 더 깔끔해요.
$ git fetch -p upstream
여기서, upstream
은 패치로 가져오려는 리모트 레파지토리에요.
주기적으로 리모트으로 푸시한다면, 대부분은 안전해야 해요. 그치만 가끔은 브랜치를 지울 수 있어요. 새 브랜치를 만들고 파일을 하나 만들었다고 해보죠:
(main)$ git checkout -b my-branch
(my-branch)$ git branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt
추가하고 커밋해요.
(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ foo.txt added
1 files changed, 1 insertions(+)
create mode 100644 foo.txt
(my-branch)$ git log
commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012
Author: siemiatj <[email protected]>
Date: Wed Jul 30 00:34:10 2014 +0200
foo.txt added
commit 69204cdf0acbab201619d95ad8295928e7f411d5
Author: Kate Hudson <[email protected]>
Date: Tue Jul 29 13:14:46 2014 -0400
Fixes #6: Force pushing after amending commits
이제 다시 마스터로 돌아가 '실수로' 브랜치를 지워보죠.
(my-branch)$ git checkout main
Switched to branch 'main'
Your branch is up-to-date with 'origin/main'.
(main)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(main)$ echo oh noes, deleted my branch!
oh noes, deleted my branch!
여기에서 업그레이드된 로그 도구인 '리플로그'에 익숙해져야 해요. 리플로그는 레파지토리의 모든 행동의 이력을 다 보관해요.
(main)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to main
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from main to my-branch
보시다시피 지워진 브랜치의 커밋 해쉬도 볼 수 있어요. 지웠던 브랜치를 살릴 수 있는 지 한번 해보죠.
(main)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt
짜잔! 지워진 파일들을 되돌려 놨어요. git reflog
는 리베이스가 끔찍하게 잘못 됐을때 아주 유용해요.
리모트 브랜치를 삭제하려면:
(main)$ git push origin --delete my-branch
이렇게도:
(main)$ git push origin :my-branch
로컬 브랜치를 삭제하려면:
(main)$ git branch -d my-branch
현재 브랜치나 업스트림에 머지되지 않은 로컬 브랜치를 지우려면:
(main)$ git branch -D my-branch
fix/
로 시작하는 모든 브랜치들을 지우고 싶다면:
(main)$ git branch | grep 'fix/' | xargs git branch -d
현재 (로컬) 브랜치 이름을 바꾸려면:
(main)$ git branch -m new-name
다른 (로컬) 브랜치 이름을 바꾸려면
(main)$ git branch -m old-name new-name
우선, 리모트 레파지토리에서 모든 브랜치를 패치 받아요:
(main)$ git fetch --all
리모트의 daves
로 체크아웃 하고 싶다고 하면.
(main)$ git checkout --track origin/daves
Branch daves set up to track remote branch daves from origin.
Switched to a new branch 'daves'
(--track
은 git checkout -b [branch] [remotename]/[branch]
의 축약이에요)
daves
브랜치의 로컬 카피를 줄거에요. 그리고 푸시된 업데이트들도 리모트로 표시돼요.
$ git push <remote> HEAD
또한 리모트 브랜치를 현재 브랜치를 위한 업스트림으로 설정하고 싶다면, 대신 아래 방법을 써봐요:
$ git push -u <remote> HEAD
push.default
설정의 upstream
모드와 simple
모드 (2.0 버전의 깃의 기본)와 함께,
아래 커맨드는 이전에 -u
옵션으로 등록된 리모트 브랜치와 관련된 현재 브랜치를 푸시할거에요:
$ git push
git push
의 다른 모드의 동작은 push.default
문서에 설명돼 있어요.
리모트 브랜치를 현재 쓰고 있는 로컬 브랜치를 위한 업스트림으로 설정할 수 있어요:
$ git branch --set-upstream-to [remotename]/[branch]
# 아니면 짧게:
$ git branch -u [remotename]/[branch]
다른 로컬 브랜치를 위한 업스트림 리모트 브랜치를 설정하려면:
$ git branch -u [remotename]/[branch] [local-branch]
리모트 브랜치를 확인해보는 것으로, HEAD가 트래킹 중인 리모트 브랜치를 볼 수 있어요. 몇몇 경우에는, 원하던 브랜치가 아닐거에요.
$ git branch -r
origin/HEAD -> origin/gh-pages
origin/main
origin/HEAD
를 origin/main
를 트래킹하는 것으로 변경하려면, 이 커맨드로 실행할 수 있어요:
$ git remote set-head origin --auto
origin/HEAD set to main
커밋 되지 않은 변경점, 거기다 잘못된 브랜치에 하고 있었다면 변경점을 스테이시 하고 원하는 브랜치로 가 스테이시 어플라이 해요:
(wrong_branch)$ git stash
(wrong_branch)$ git checkout <correct_branch>
(correct_branch)$ git stash apply
현재 브랜치를 의도하지 않던 브랜치로 머지 또는 리베이스 했거나, 리베이스/머지 도중에 완료하거나 끝내지 못했을거에요. 깃은 위험한 과정 전에 원래의 HEAD 포인트를 ORIG_HEAD라 불리는 변수에 보관해요, 그러니 리베이스/머지 전 상태로 브랜치를 복구하기 간단해요.
(my-branch)$ git reset --hard ORIG_HEAD
아쉽게도 그런 변경점을 리모트 브랜치에 반영하려면 강제 푸시밖에 방법이 없어요. 이력을 변경해왔기 때문이죠. 리모트 브랜치는 강제 푸시 외엔 적용 해주지 않을거에요. 많은 분들이 머지 워크플로우를 리베이스 워크플로우보다 선호하는 많이 이유 중 하나죠 - 큰 팀에선 개발자의 강제 푸시로 곤란해질 수 있어요. 주의해서 쓰세요. 리베이스를 그나마 안전하게 쓰는 방법은 리모트 브랜치의 모든 변경점과 똑같이 반영하는게 아니라 대신에 이렇게 해봐요:
(main)$ git checkout my-branch
(my-branch)$ git rebase -i main
(my-branch)$ git checkout main
(main)$ git merge --ff-only my-branch
더 확인이 필요하다면, 이 스택오버플로우의 쓰레드를 참고해요.
main
에 풀 리퀘스트가 될 브랜치에서 작업하고 있다고 가정해봐요.
가장 간단한 경우는 원하는게 모든 커밋을 하나의 커밋으로 합치고 변경점의 시간을 신경쓰지 않아도 되는 것일 때, 리셋하고 커밋 다시하면 돼요.
마스터 브랜치가 최신이고 모든 변경점이 커밋된 것만 확인한 다음:
(my-branch)$ git reset --soft main
(my-branch)$ git commit -am "New awesome feature"
좀더 조정하고, 시간기록까지 보관하고 싶다면, 대화형 리베이스가 필요할거에요.
(my-branch)$ git rebase -i main
만약 다른 브랜치로 붙는 작업을 하는게 아니라면, HEAD
을 기준으로 리베이스 해야해요.
예로 마지막 2개의 커밋을 스쿼시(기존 커밋에 반영해넣는)하고 싶다면 HEAD~2
로 리베이스 해요. 3개라면 HEAD~3
으로 하구요.
(main)$ git rebase -i HEAD~2
대화형 리베이스를 실행하면 텍스트 에디터로 이런 것들을 볼 수 있을거에요.
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
pick b729ad5 fixup
pick e3851e8 another fix
# Rebase 8074d12..b729ad5 onto 8074d12
#
# 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
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
모든 #
으로 시작하는 주석줄은 리베이스에 영향을 주진 않아요.
다음으로 pick
부분을 다른 명령어로 바꾸거나, 해당하는 라인을 지워서 커밋을 지울 수도 있어요.
예를 들자면 오래된 (첫번째) 커밋만 두고 두번째 오래된 커밋과 나머지를 다 합치고 싶을때, 첫번째와 두번째 커밋 제외한 나머지 커맨드들을 f
로 바꿔야 할거에요:
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
f b729ad5 fixup
f e3851e8 another fix
이 커밋들을 합치고 커밋 이름을 바꾸고 싶다면, 추가로 적어줘야 해요 두번째 커밋 다음에 r
를 추가하거나 간단히 f
대신 s
를 추가해주면 될거에요:
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
s b729ad5 fixup
s e3851e8 another fix
그런 다음에 한번 더 뜨는 텍스트 에디터로 커밋 이름을 바꿀 수 있어요.
Newer, awesomer features
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 8074d12
# You are currently editing a commit while rebasing branch 'main' on '8074d12'.
#
# Changes to be committed:
# modified: README.md
#
전부 다 성공하면, 이런 메세지를 볼거에요:
(main)$ Successfully rebased and updated refs/heads/main.
--no-commit
는 머지는 하지만 실패하고 자동 커밋이 안된것처럼 보이는데, 커밋하기전에 머지 결과를 보고 추가로 조정할 수 있게 해줘요.
no-ff
는 피쳐 브랜치가 있었다는 증거를 남기고, 이력을 일관되게 가지게 해요.
(main)$ git merge --no-ff --no-commit my-branch
(main)$ git merge --squash my-branch
가끔 여러가지 작업 도중인 커밋을 푸시하기 전에 합치고 싶을거에요. 다른 누군가가 벌써 참고해서 커밋을 만들고 있을테니 이미 푸시된 커밋을 잘못 합치길 바라진 않을거에요.
(main)$ git rebase -i @{u}
이 명령은 아직 푸시하지 않은 커밋만으로 대화형 리베이스를 실행해요. 그러니 목록 내에 있는 어떤 커밋이든 재정렬/수정/합치기 안전해요.
때때로 머지는 어떤 파일에 문제를 일으킬 수도 있어요, 이 경우 옵션 abort
으로 현재 충돌 해결 프로세스를 중단하고 병합하기 전 상태로 다시 구성할 수 있어요.
(my-branch)$ git merge --abort
이 명령은 1.7.4 버전부터 쓸 수 있어요.
브랜치 내 모든 커밋이 다른 브랜치로 머지됐는지 확인하려면, 그 브랜치들 HEAD (또는 특정 커밋)를 비교해야해요:
(main)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll
이 명령은 어디에는 있고 다른덴 없는 커밋이 있나를 알려줄거에요 그리고 브랜치들 사이에 공유되지 않은게 목록을 보여줄 거구요. 다른 옵션은 이렇게:
(main)$ git log main ^feature/120-on-scroll --no-merges
이런 걸 본다면:
noop
동일한 커밋에 있거나 현재 브랜치보다 앞서 있는 브랜치에 대해 리베이스를 시도한다는 의미에요. 이렇게 해볼 수 있어요:
- 마스터 브랜치가 있어야 할 곳에 있나 확인
- 대신해서
HEAD~2
또는 더 기존 항목을 리베이스
리베이스를 똑바로 끝내지 못했다면, 충돌을 해결해야 할거에요.
어떤 파일이 충돌났는지 git status
를 먼저 실행해봐요:
(my-branch)$ git status
On branch my-branch
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
both modified: README.md
이 예시에선, README.md
가 충돌났네요. 파일을 열어서 아래와 같은 부분을 찾아봐요:
<<<<<<< HEAD
some code
=========
some code
>>>>>>> new-commit
새로운 커밋으로 추가된 코드(예시에선, 중간 선부터 new-commit
까지의)와 HEAD
사이에서 차이점을 해결해야 할거에요.
어느 한쪽 브랜치의 코드를 남기고 싶다면, --ours
또는 --theirs
를 쓰면 돼요:
(main*)$ git checkout --ours README.md
- 머지할때,
--ours
를 쓰면 로컬 브랜치의 변경점 유지하고,--theirs
는 다른 브랜치의 변경점를 유지해요. - 리베이스할 땐,
--theirs
가 로컬 브랜치의 변경점을 유지하고--ours
는 다른 브랜치의 변경점을 유지해요. 이런 차이에 관한 설명은 Git 정식 문서 중 이 문서를 보세요.
만약 머지가 더 복잡하면, 비주얼 디프 에디터를 쓸 수도 있어요:
(main*)$ git mergetool -t opendiff
코드의 충돌을 해결하고 테스트가 해결되고 나면, 바뀐 파일 내용을 git add
해주고, git rebase --continue
로 리베이스를 이어서 해요.
(my-branch)$ git add README.md
(my-branch)$ git rebase --continue
만약 모든 충돌을 개선한 내용이 커밋 전과 동일한 트리 구조를 가진다면, 대신에 git rebase --skip
를 해야 해요.
리베이스 중 멈추고 싶은 어떤 시점이거나 원래 상태의 브랜치로 돌아가고 싶다면, 이렇게 할 수 있어요:
(my-branch)$ git rebase --abort
작업중인 디렉토리에서의 변경한 내용 전부를 스테이시 하려면
$ git stash
트래킹 되지 않은 파일까지도 포함하려면, -u
옵션을 써요.
$ git stash -u
작업중인 디렉토리에서 한 파일만 스테이시 하기
$ git stash push working-directory-path/filename.ext
작업중인 디렉토리에서 여러 파일 스테이시 하기
$ git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext
$ git stash save <message>
메세지 작성된 스테이시 리스트 먼저 확인하세요
$ git stash list
그런 다음 리스트 내 특정 스테이시를 적용해요
$ git stash apply "stash@{n}"
여기에서, 'n' 은 스택 안에서 스테이시의 위치를 나타내요. 젤 위에 있는 스테이시가 0 일거에요.
특정한 문자열이 포함된 어떤 커밋을 찾으려면, 이런 구조로 쓸 수 있어요:
$ git log -S "string to find"
일반적인 파라미터들은:
-
--source
각 커밋에 도달한 명령어에 지정된 참조 이름을 보여주는걸 의미해요. -
--all
는 모든 브랜치에서 시작하는걸 의미해요. -
--reverse
반대의 순서로 출력해요, 변경점의 첫번째 커밋이 보일꺼란 거죠.
작성자나 커미터의 모든 커밋을 찾으려면 이렇게 쓸 수 있어요:
$ git log --author=<name or email>
$ git log --committer=<name or email>
작성자와 커미터가 같지 않다는 것만 염두해두세요.
--author
는 코드를 실제로 코드를 작성한 사람이고
반면에 --committer
는 실제 작성자를 대신해 커밋을 한 사람이에요.
특정 파일이 든 모든 커밋을 찾으려면 이렇게 해요:
$ git log -- <path to file>
보통은 정확한 경로를 쓸테지만 와일드 카드로 경로나 파일명을 쓸수도 있어요:
$ git log -- **/*.js
와일드 카드를 쓸 때, 커밋된 파일의 목록을 볼 수 있는 --name-status
로 확인하는게 유용할거에요:
$ git log --name-status -- **/*.js
특정 커밋이 포함된 모든 태그를 찾으려면:
$ git tag --contains <commitid>
$ git clone --recursive git://github.com/foo/bar.git
벌써 클론했다면:
$ git submodule update --init --recursive
서브모듈을 만드는건 아주 간단하지만 지우는건 그렇진 않아요. 필요한 명령어는:
$ git submodule deinit submodulename
$ git rm submodulename
$ git rm --cached submodulename
$ rm -rf .git/modules/submodulename
우선 그 파일이 마지막으로 있었던 커밋을 찾고:
$ git rev-list -n 1 HEAD -- filename
그런 다음 그 파일을 체크아웃해요
git checkout deletingcommitid^ -- filename
$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>
이미 지운 태그를 복구하고 싶다면, 이런 단계를 따라해 볼 수 있어요: 우선, 연결할 수 없는 태그를 찾고:
$ git fsck --unreachable | grep tag
태그의 해쉬를 메모해두세요.
그런 다음 git update-ref
을 써서 지워진 태그를 복구해요:
$ git update-ref refs/tags/<tag_name> <hash>
이제 태그가 복구돼있을거에요.
만약 깃헙에서 누군가가 풀리퀘스트를 보냈는데 이미 원래의 포크가 지워졌다면,
url을 쓸 수 없게 돼 레파지토리를 클론할 수 없거나 .diff, .patch와 같은 git am
를 쓸 수 없을 거에요.
하지만 깃헙의 특별한 참조을 이용해서 풀 리퀘스트 자체를 확인할 수 있어요.
PR#1의 내용을 pr_1이란 새 브랜치로 패치 받으려면:
$ git fetch origin refs/pull/1/head:pr_1
From github.com:foo/bar
* [new ref] refs/pull/1/head -> pr_1
$ git archive --format zip --output /full/path/to/zipfile.zip main
(main)$ git mv --force myfile MyFile
(main)$ git fetch --all
(main)$ git reset --hard origin/main
(main)$ git rm --cached log.txt
복구하고 싶은 해시가 c5f567 이라고 가정하면:
(main)$ git checkout c5f567 -- file1/to/restore file2/to/restore
c5f567 한 단계전으로 복구하고 싶다면, c5f567~1로 적어줘요:
(main)$ git checkout c5f567~1 -- file1/to/restore file2/to/restore
마지막 커밋과 c5f567으로 부터의 차이를 비교하고 싶다고 가정하면:
$ git diff HEAD:path_to_file/file c5f567:path_to_file/file
# 아니면 짧게:
$ git diff HEAD c5f567 -- path_to_file/file
브랜치도 같은 방법으로:
$ git diff main:path_to_file/file staging:path_to_file/file
# 아니면 짧게:
$ git diff main staging -- path_to_file/file
맥OS나 리눅스에는, 깃 설정 파일이 ~/.gitconfig
에 있어요. 단축용으로 (몇개는 평소 쓰는 용도로) 앨리어스 몇개를 아래와 같이 계속 추가해오고 있어요.
[alias]
a = add
amend = commit --amend
c = commit
ca = commit --amend
ci = commit -a
co = checkout
d = diff
dc = diff --changed
ds = diff --staged
f = fetch
loll = log --graph --decorate --pretty=oneline --abbrev-commit
m = merge
one = log --pretty=oneline
outstanding = rebase -i @{u}
s = status
unpushed = log @{u}
wc = whatchanged
wip = rebase -i @{u}
zap = fetch -p
못해요! 깃은 지원하지 않거든요, 근데 꼼수가 있어요. 디렉토리에에 .gitignore 파일을 아래 내용으로 만들어요:
# Ignore everything in this directory
*
# Except this file
!.gitignore
다른 일반적인 컨벤션은 그 폴더 안에 .gitkeep이라는 이름의 빈 파일을 만드는 거에요.
$ mkdir mydir
$ touch mydir/.gitkeep
.keep이란 이름으로도 쓸 수 있는데요, 두번째 라인이 touch mydir/.keep
가 되어야겠죠.
인증이 필요한 레파지토리를 가지고 있을 텐데요. 이런 경우 유저명과 비밀번호를 캐시할 수 있을테니 매번 푸시/풀 할 때마다 입력할 필욘 없어요. 크리덴셜 헬퍼가 해줄거에요.
$ git config --global credential.helper cache
# Set git to use the credential memory cache
$ git config --global credential.helper 'cache --timeout=3600'
# Set the cache to timeout after 1 hour (setting is in seconds)
$ git config core.fileMode false
이 것을 로그인된 유저의 기본 행위로 설정으로 해두려면, 이렇게 써요:
$ git config --global core.fileMode false
모든 로컬 레파지토리에 사용되는 유저 정보를 설정하려면, 그리고 버전 이력을 리뷰할때 알아보기 쉬운 이름으로 설정하려면:
$ git config --global user.name “[firstname lastname]”
각 이력 생산자에게 연관해서 이메일 설정을 해주려면:
git config --global user.email “[valid-email]”
음, 망했군요. 뭔가를 reset
했거나, 다른 브랜치로 머지했거나, 지금은 찾질 못하는 커밋으로 강제 푸시를 해버렸군요.
알다시피, 어떤 시점에선, 잘 하고 있었고 거기로 돌아가고 싶겠죠.
이게 바로 git reflog
의 존재이유에요. reflog
는 브랜치 끝의 어떤 변경점이든 브랜치나 태그에 의해 참조되지 않더라도 다 보관해요.
기본적으로, HEAD가 변경되는 모든 경우, 리플로그에 새로운 입력이 추가돼요. 아쉽게도 이 기능은 로컬 레파지토리에서만 동작해고, 오직 움직임만을 트래킹해요 (예를 들자면 어디에도 기록되지 않았던 파일의 변경은 아니에요)
(main)$ git reflog
0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to main
c10f740 HEAD@{2}: checkout: moving from main to 2.2
이 리플로그는 마스터에서 2.2 브랜치로 체크아웃하고 되돌린 것을 보여주네요.
저기에선, 오래된 커밋으로 리셋하기 어려워요. 최신 활동이 HEAD@{0}
상단 라벨로 보여지네요.
만약 실수로 뒤로 이동했다면, 리플로그는 실수로 지워진 2개의 커밋 전 상태인 (0254ea7)를 가리키는 커밋 마스터를 포함할거에요.
$ git reset --hard 0254ea7
git reset
을 쓰는 것으로 마스터를 이전 커밋으로 되돌릴 수 있어요.
이력이 실수로 변경됐을 때의 안정망을 제공할거에요.
(여기에서 복제해와 수정했어요).
- Pro Git - Scott Chacon 과 Ben Straub의 훌륭한 깃 책
- Git Internals - Scott Chacon의 또다른 훌륭한 깃 책
- Atlassian's Git tutorial 기초부터 고급까지 튜토리얼과 함께 하는 깃 제대로 얻기
- Learn Git branching 대화형 웹 기반의 브랜치/머지/리베이스 튜토리얼
- Getting solid at Git rebase vs. merge
- git-workflow - Aaron Meurer의 어떻게 깃을 통해 오픈소스 레파지토리에 어떻게 기여할까
- GitHub as a workflow - 깃헙을 워크플로우로 쓰는 흥미로운 작업, 특히 빈 풀 리퀘스트를 활용하는
- Githug - 더 일반적인 깃 워크플로우를 배우는 게임
- firstaidgit.io 가장 많이 묻는 깃 질문의, 검색가능한 모음
- git-extra-commands - 유용한 기타 깃 스크립트 모음
- git-extras - 깃 유틸리티 -- 레파지토리 요약, repl, 변경이력 밀집도, 작성자 커밋 비율 등
- git-fire - git-fire는 모든 현재 파일을 추가,커밋,새 브랜치로 푸시(머지를 예방하기 위한) 등 비상사태를 도와주는 플러그인
- git-tips - 자그마한 깃 팁들
- git-town - 포괄적이고, 높은 수준의 깃 워크플로우 지원! http://www.git-town.com
- GitKraken - 완전 고급의 깃 클라이언트 Windows, Mac & Linux
- git-cola - 또 다른 깃 클라이언트 Windows, OS X
- GitUp - 아주 독단적으로 방식으로 깃의 복잡함을 다루는 새로운 GUI
- gitx-dev - 또다른 그래픽적인 깃 클라이언트 OS X
- Sourcetree - 아름답고 무료인 깃 GUI 안에서 단순함과 강력함이 만났어 Windows and Mac
- Tower - 그래픽 Git 클라이언트 OS X (유료)
- tig - 깃을 위한 터민러 텍스트 모드 인터페이스
- Magit - Emacs 패키지를 위해 구현된 깃 인터페이스
- GitExtensions - 쉘 확장, 비주얼 스투디오 2010-2015 플러그인 그리고 독자적인 깃 레파지토리 도구
- Fork - 빠르고 친숙한 깃 클라이언트 Mac (베타)
- gmaster - 3-way 머지, 리팩터 분석기, 시멘틱 diff와 머지 기능의 윈도 전용 깃 클라이언트 (베타)
- gitk - 간단한 레파지토리 상태를 볼 수 있는 리눅스 깃 클라이언트