본문 바로가기
A. Development/Free Topic

Git 사용방법

by IMCOMKING 2014. 5. 7.

# git을 공부하기에 좋은 최적의 입문 자료
https://git-scm.com/book/ko/v2/%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC%EB%9E%80%3F

# Git의 기본 매커니즘. 아래의 그림이 전체 아키텍처를 전부 설명해준다.

 

 

git은 svn과 달리 local repo와 remote repo가 구분되어 있어서 매우 편리한 분산 버전 관리 시스템이다.

# 일단 git commit으로 로컬에 저장해놓은 데이터는 완벽히 보존된다! 내가 겪은 버그는 ipython의 자동저장기능 때문에, git checkout으로 이동 중 과거에 켜둔 웹이 최신버전에 자동저장되면서 마치 데이터가 사라진것 처럼 보이는 현상이었음.

 

# git에서 HEAD의 의미: 내 현재 위치라고 생각하면 됨.

 

# Git 환경 설정하기

git config --global user.email "이메일주소"

git config --global user.name "이름"

git config --global color.ui true : git 출력을 컬러로하기

git config --global core.editor "vim" : git 에서 사용하는 기본 editor를 vim으로 설정하기

git config format.pretty oneline : log에서 확정본을 한줄로 짧게 표시 # 별로일 수도 있음. 그러면 full 로 되돌림

 

# Github의 계정을 public key를 이용, ssh 환경을 세팅해서, 로그인 과정을 생략하기

이렇게하면, putty로 git remote repo에 접속할 때, id 비번 입력없이 바로바로 가능하다.

https://developer.github.com/enterprise/2.17/v3/guides/managing-deploy-keys/#deploy-keys
https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key

 

1) ssh key 생성
ssh-keygen -t rsa -b 4096
(

-C "id@mail.com" 는 comment추가 옵션이다.)

 

(경로를 입력할 때, 생성될 키의 이름도 바꿀 수 있다.)

(Enter passphrase 입력은 그냥 enter치면 없이 넘어간다.)

 

2) ssh private key를 내 컴퓨터의 ssh-agent에 등록하기(최초 설정시에만 필요)

 

eval $(ssh-agent -s) 을 입력해서 agent가 실행 중인지 확인한다.
ssh-add ~/.ssh/id_rsa 

을 입력해서 ssh를 agent에 등록한다.

(ssh-add -l -E md5 을 입력해서 제대로 등록되었는지 확인가능)


3) ssh public key를 github에 등록하기

 

cat ~/.ssh/id_rsa.pub 으로 나온 결과를 통째로 복사한다음

github 사이트의 setting - SSH and GPG keys(혹은 deploy key)에 들어가서 복사한 key를 넣으면된다.(title은 적당히 해당 컴퓨터를 가리키는 말을 적으면 됨)
입력하고 나온 Fingerprint가 ssh-add -l -E md5 의 결과와 동일해야함


4) 접속하기
(최초 설정시에만 필요)
ssh -T git@github.com 을 입력하고 yes를 입력해서 바로 아래의 메시지가 출력되면 정상 접속된 것이다.(주의: git@~~.com 에서 git은 고정이다. 유저 id가 아님)

  1. Hi username! You've successfully authenticated, but GitHub does not provide shell access.

4-1) Permission denied (publickey) 에러
위의 메시지가 나오지 않고, 

Permission denied (publickey)에러가 뜰 경우 다음 링크를 확인.
https://help.github.com/enterprise/2.10/user/articles/error-permission-denied-publickey/

 

5) ID / Password 없이 git 사용하기
기존의 remote url이 https를 통하도록 지정이 되어 있으면, 무조건 항상 ID와 비번을 물어본다. 따라서 방금 우리가 만든 ssh를 이용하도록 remote url을 변경해줘야한다.

git remote set-url origin 
ssh://

git@github.com/PATH/TO/Repo.git

https://stackoverflow.com/questions/14762034/push-to-github-without-password-using-ssh-key/37075653
5-1) ssh-agent 자동 실행 --> bashrc에 등록하기

(최초 설정시에만 필요)

bash script에 다음을 추가하여, 자동으로 ssh_key를 이용하도록 수정한다.

eval $(ssh-agent -s)
ssh-add ~/.ssh/id_rsa
ssh -T git@github.com

그런데 위의 메시지를 봤는데도, 계속해서 username과 password를 물어본다면 다음을 실행해야한다.(ssh-agent가 실행이 안되어있는 경우임)



https://help.github.com/enterprise/2.10/user/articles/working-with-ssh-key-passphrases/ 

 

### 여러가지 에러 해결 ###

# ssh-add 를 하여도 passphrase를 계속 물어보면?
ssh-keygen -p -P "old_passphrase" -N "" -f ~/.ssh/id_

rsa
을해서 passphrase를 제거한다.

https://stackoverflow.com/questions/42631711/git-keeps-prompting-me-for-passphrase-for-my-ssh-key-ubuntu-vm
# remote: Invalid username or password. 에러 해결
git remove -v
git remote set-url origin ssh://git

@github.com/PATH/TO/Repo.git

https://stackoverflow.com/questions/29297154/github-invalid-username-or-password

Key is already in use

 

github에서는 하나의 key를 오직 하나의 repository에만 등록할 수 있다. 따라서 기존의 repo에서 해당 키를 삭제하거나, 아니면 새로운 키를 생성해서 사용해야 한다.

https://help.github.com/en/github/authenticating-to-github/error-key-already-in-use





# git credential 사용하기: 보안에 매우 취약한 방법으로, 

완전한 개인용 컴퓨터에서만 사용해야한다. 동작 원리는 한 번 로그인한 git의 비밀번호를 plain text로 저장하는 것이다. 이 점을 유의하고 사용해야한다.


git config --global credential.helper store


다시 해제하고 싶으면 아래를 입력한다.



git config --global --unset credential.helper
## git credential ID/비번 업데이트하기

git config --global --unset credential.helper
git config --global credential.helper store

이렇게 하면 reset되어서 다시 입력하면 된다.


## 주의사항 ##

위의 명령을 실행하면 ~/.git-credentials라는 파일이 생성되는데 이 파일에는 사용자의 ID와 Password가 플레인 텍스트 형태로 그냥 기록된다. 따라서 완전히 무방비로 보안이 노출되기 때문에 가능하면 이 기능은 사용해선 안된다. SSH public key를 이용하는것이 가장 좋은 해결책으로 보인다.




- Git stash: commit 하지 않은 현재 작업내용을 임시로 저장하고, 마지막 commit한 상태를 불러오기
git stash: 현재 modified 된 파일을 임시로 저장하고, 마지막 commit상태를 불러옴

git stash -umodified + untracked까지 백업

git stash pop: 임시로 저장해둔 변경내용을 불러옴.
          git stash pop == git stash apply && git stash drop.
git stash list : 임시 저장목록 가져오기
git stash apply [stash@{0}] : 임시 저장목록 불러와서 다시 적용하기
git stash drop [stash@{0}] : 저장해둔 내용 삭제
git stash show -p stash@{0} | git apply -R : 실수로 충돌 등이 일어난 경우, stash apply한 것을 되돌릴 수 있다.
https://www.git-tower.com/learn/git/faq/save-changes-with-git-stash
https://git-scm.com/book/ko/v1/Git-%EB%8F%84%EA%B5%AC-Stashing

 

- Git 의 핵심 명령어
git clone : 최초로 원격 서버에서 최신 버전의 git 서버의 파일들을 컴퓨터로 받아옴.

git clone git_url -b branch_name : 해당 git repo의 특정 branch를 가져온다.
ex) git clone https://github.com/imcomking/Practical_Deep_Learning_Tutorial.git
ex) git clone https://github.com/imcomking/Practical_Deep_Learning_Tutorial.git DirName

git pull : 서버에서 최신 버전의 workspace 가져오기 (workspace 안에서 실행. push안한 사항이 있을 경우 충돌이 발생함. fetch + merge 을 합친 명령어)
ex) git pull origin master : 해당 branch의 원격 서버에 저장된 최신 내용을 가져온다

git checkout . : local repo에서 작업하고, commit 하지 않은 file을 모두 마지막 pull 시점으로 되돌린다.
(정확히는 Modified file들의 변경사항만 되돌리고, Untracked file들은 가만히 놔둔다.)

git checkout HEAD [파일명] : 해당 파일의 변경사항을 HEAD에 있는 버전으로 되돌린다. 

git checkout branch이름 : 해당 branch로 옮겨간다. 이를 실행하기 위해서는 일단 현재 시점에서 modified파일과 untracked파일이 없어야만 가능하다.

git add . : 서버로 새로 추가한 모든 파일의 목록을 전송한다. (git commit 혹은 push 하기 전에 반드시 add 먼저해야함.) (git add -i 혹은 -p 를 하면 대화식으로 추가가 가능) (git add * or -A 랑 다르네 뭔가)

(git add -u : 기존에 tracking하던 파일들만 add하기. 즉 modified file들만 add)
(git add -i : 를 이용하면 편리하게 원하는 파일들만 골라서 add할 수 있다. 13 14 15 a 이런식으로)

git rm 파일명 : commit 된 파일이나 폴더를 삭제한다.(로컬에 있는 것까지 삭제해버리는듯.) reset만하면 staging만 안하는데, rm은 여기서 로컬 삭제까지 시켜버림.
git commit : editor가 켜지면서 commit message를 작성하게 된다.

git commit -m "메시지" : 현재 작업 상태의 스냅샷(백업)를 메시지와 함게 local repository에 저장
ex) git commit -m "something is changed" (workspace 안에서 실행. -a를 붙이면 add도 한번에 같이 처리 가능)

git push origin master : 서버로 commit 한 내용을 보냄 (git clone으로 workspace를 먼저 다운받은 뒤, 그 안에서 실행) ( -u는 최초의 branch를 push할 때 1회 해주어야 한다는 것 같음.)
git push가 하는 일은 local에 있는 branch를 remote에 있는 branch로 보내는 것이다.

 

 

# Git 을 local에서부터 시작해서 remote repo에 push 하기 : 주의 사항. remote repo에 파일이 0개인 상태여야만 깔끔하게 연결이 가능하다. (README, License, .gitignore 파일 등도 있으면 안됨.)
rm -rf .git : git init 취소하기. 즉 내가 작업하던 폴더의 기존 git 저장소 삭제하기(내 소스코드는 그대로지만, commit/branch 등의 내역들은 다 삭제된다.)
git init : 내가 작업하던 폴더를 git의 workspace로 만들기

git remote add origin 원격서버주소.git : git clone이 아닌, git init에서부터 시작 한 경우, 이 명령어를 통해 서버와 local repo를 연결해야한다. 당연히 이걸 하려면, 서버에서 먼저 repo를 생성한 다음 해야한다. ( 여기서 origin은 내가 add할 remote repo를 가리키는 별칭이다. 만약 여러개의 remote repo를 사용할 경우 origin2, origin3 등의 이름을 지어줄 수 있다.)

git add .
git commit -m "메시지"
git branch --set-upstream-to=origin/master : git pull만 입력했을 때 default로 가져올 remote를 설정한다. git push -u origin master로도 동일한 일을 할 수 있음.
git pull

git push origin master

https://stackoverflow.com/questions/5697750/what-exactly-does-the-u-do-git-push-u-origin-master-vs-git-push-origin-ma

 

# 작업 내용 추적 관리
https://stackoverflow.com/questions/936249/how-to-stop-tracking-and-ignore-changes-to-a-file-in-git

 
 
# Unstage 하기
- git reset -- <파일명> : 아직 commit은 안한 상태에서, add 한 파일 취소하기. 이 명령어는 git add와 완벽히 정반대의 행동을 한다. 즉 unadd라고 볼수 있고, 이번에 commit할 대상에서 제외하기 위함이다.
https://stackoverflow.com/questions/348170/how-to-undo-git-add-before-commit
http://stackoverflow.com/questions/1505948/how-do-i-remove-a-single-file-from-the-staging-area-of-git-but-not-remove-it-fro

- git rm --cached [path]
: 이미 commit한 특정 파일을 git repo의 index에서 제외시키기. 즉 이미 커밋된 특정 파일을 repo에서 unstaging(=untracked)한다. (git index에서만 빠져있으므로, 아직 내 local dir에는 남아있다.)
https://stackoverflow.com/questions/6919121/why-are-there-two-ways-to-unstage-a-file-in-git/6919749
 
- git rm --cached -r [dir path] : 디렉토리에 대해서는 -r을 붙인다. 
 
 
 
# 이미 tracked 중인 파일에 대해 변경사항 무시하기
- 단점 1 : assume-unchanged 혹은 skip-worktree로 지정한 파일에 변화가 생겼을 때, git status에는 전혀 보여지지 않지만, git checkout branch 을 실행시 다음과 같은 에러가 뜨면서 먹히질 않게된다. 
(error: Your local changes to the following files would be overwritten by checkout: Please, commit your changes or stash them before you can switch branches. Aborting)
그런데 이 문제는 설계적으로 의도된 것이라고 한다. 어쩔 수 없이 맞딱들일 수 밖에 없다. https://stackoverflow.com/questions/35690736/handling-changes-to-files-with-skip-worktree-from-another-branch
현실적인 방법은 alias를 등록해서 편하게 사용하는 것이다.
 
- 단점 2 : 이 옵션들은 철저히 local을 위한 것으로 push하여 다른 사용자에게 propagation할수는 없다. 
- Change Tracking 중지하기: 바뀌지 말아야할 파일
git update-index --assume-unchanged [path] : 내가 해당 파일을 local에서 수정한 모든 내용을 무시함. 즉 개발자가 앞으로 변경하지 않을 것이라고 생각되는 대규모의 sdk와 같은 파일에 대해 추적을 중단하여 git stat의 속도를 빠르게 하기 위함.
 
- 독립 작업 파일/폴더 설정하기: 편히 바꾸어야할 파일
git update-index --skip-worktree [path] : local 전용 개발을 위한 독립 파일/폴더를 설정함. 즉 개발자가 오직 local에서만 사용할 목적의 수정이 일어나는 경우 설정해줌. 
git update-index --no-skip-worktree [path] : undoing
 
 
 
 
# .gitignore: 계속해서 untracking할 파일을 지정한다.(이미 tracking중인 파일은 영향받지 않는다.)
위의 내용으로 .gitignore파일을 만들고 시작하면 좋음. github에서도 선택을 하면 기본 제공한다고하는듯
 
- commit 해둔 버전으로 돌아가기 : 이건 정말 흔하게 많이 쓰이는 기능이다.
git checkout HEAD~5 : 최신 버전에서 5번 뒤로 가기
git checkout 9d51a7 : 9d51a7이름의 commit으로 돌아가기
git checkout master : 다시 최신버전으로 돌아오기
(여기에 -f 옵션을 추가하면, 수정하던 것을 다 버리고 해당 버전으로 옮겨감)
http://opendive.blogspot.kr/2015/06/git.html

- 특정 파일만 commit 해둔 버전으로 돌아가기 :
git checkout <commit> 파일명
http://ohgyun.com/443
 
- Remote branch에 checkout하기
git fetch
git checkout <remote브랜치명>
 
- 과거에 삭제하고 commit한 file 되살리기
git rev-list -n 1 HEAD -- file_path :으로 해당 파일이 삭제된 commit 찾기
git checkout [삭제된 commit]^ -- file_path : [commit hash]^ 을 하면, 그 이전 commit이 선택된다.
 

- Auto-merge된 파일 되돌리기
git checkout HEAD~1 -- 해당파일

* 이때 --는 생략해도 되는데, 하면 더 좋은 이유는 똑같은 이름을 가진 branch와 파일이 서로 헷갈리는 것을 막기 위함이다. 즉 -- 를 붙이면 오직 파일중에서만 해당이름을 인식함.
https://stackoverflow.com/questions/41101998/git-checkout-vs-git-checkout

- 실수로 pull 한 작업 취소하기
git reset --hard

* 설명) 일단 pull은 아래와 동치이다.
git pull origin master = git fetch origin + git merge origin/master

이 작업 중에서 merge가 

항상 문제인데, 이 merge는 git reset --hard하면 취소됨. 즉 git fetch origin만 한 상태로 돌아가짐.

 

- 실수로 Commit 한 작업 취소하기

git reset --soft HEAD~1 : 

최근 HEAD에서 한단계로 돌아가, commit 한 명령을 취소한다.

git reset --mixed HEAD~1 : 

최근 HEAD에서 한단계로 돌아가, commit 한 명령과 add 명령을 취소한다.(코드의 변경사항은 modified인 상태로 남아 있다.)

git reset --hard HEAD~1 : 

최근 HEAD에서 한단계로 돌아가, commit 한 명령과 add 명령과 코드의 변경사항을 취소한다. (코드의 변경사항인 modified까지 날아가버리므로, 나의 작업 내용이 삭제되어버린다.)

https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Reset-%EB%AA%85%ED%99%95%ED%9E%88-%EC%95%8C%EA%B3%A0-%EA%B0%80%EA%B8%B0

 

- 실수로 Commit 한 작업에서 1개 파일만 취소하기
git reset --soft HEAD~1 : commit 한 내용을 취소한다.
git reset HEAD <파일이름> : add한 내용을 취소한다.
git commit -c ORIG_HEAD : message를 수정하면서 다시 원래대로 commit한다. 

https://stackoverflow.com/questions/12481639/remove-files-from-git-commit

 

- 이미 push 한 작업에 대한 처리인듯
git revert --hard commit이름 : 그동안의 커밋을 지우지는 않고, 예전으로 되돌아간다.
http://ecogeo.tistory.com/276

 

- Commit의 작업 내용 확인하기
git show [Commit]

git show [Commit] --name-status : 변경된 파일 목록만 확인
 
- 두 Commit 간의 차이점 비교하기

git diff HEAD : 현재의 작업으로 변경한(commit하지 않은)내용을 확인
git diff <commit> : HEAD와 commit과의 차이 비교
git diff HEAD~3 : HEAD~3번째와 HEAD와의 차이 비교
git diff <commit1> <commit2> : 두 commit간의 차이 비교 (이때 나중에 만든 commit이 뒤쪽에 와야 +, - 기호가 제대로 표기됨..)
--color-words : 이 옵션을 주면 간결하게 색으로 구분할 수 있음

git diff --name-status HEAD : 변경된 파일의 이름과 상태만 출력
git diff --staged : add는한 상태이나, 아직 commit되지 않은 변경내용을 확인

 

- local에 commit은 했는데 아직 remote에 push는 안한 내용 확인하기

 

git log origin/master..HEAD
git diff origin/master..HEAD

https://stackoverflow.com/questions/2016901/viewing-unpushed-git-commits

 

- Dropbox 때문에 git diff에서 이상한 warning: ~~conflicted copy~~이 발생할 때

.git/refs/heads 에 들어가서 conflict 난 파일을 삭제한다.

.git/refs/remotes/origin 에 들어가서 conflict 난 파일을 삭제한다.

 

https://stackoverflow.com/questions/28894135/removing-broken-names-in-git-dropbox-conflicted-copy

 

- git push만 입력했을 때의 기본 행동 설정하기

git config --global push.default simple

(현재의 branch만 push한다. simple 대신 matching을 할 경우 모든 이름이 똑같은 branch를 한 번에 전부 push한다.)

https://stackoverflow.com/questions/21839651/git-what-is-the-difference-between-push-default-matching-and-simple/21866819

 

 

 

# git rebase

git rebase는 과거의 했던 commit의 내용을 수정하고 싶을 때 사용하는 커맨드이다. 보통 git rebase -i HEAD~[N]의 형태로, 과거 N시점부터 현재까지의 commit들을 쭉 살펴보면서 어떻게 수정할지를 정한다.

git rebase -i HEAD~[N] 을 입력하면, editor가 나오는데 여기서 pick이라고 된 것은 해당 commit을 그대로 놔둔다는 의미이고, edit을 입력하면 해당 commit으로 checkout한 뒤, 내용을 수정하겠다는 의미이다. (이때 edit하고난 다음, git add 및 git commit --amend를 입력해서 커밋을 저장한다.) 그리고 reword는 commit message만 변경하겠다는 것이고, squash는 해당 commit을 이전 commit과 합쳐서 하나로 만들겠다는 것이다.

 

한 단계의 commit을 수정이 완료가 되면, git rebase --continue를 입력해서 다음 commit으로 넘어갈 수 있고 또는 git rebase --abort를 해서 rebase작업을 중단할 수도 있다.

 

* git show --name-status HEAD 을 입력하면 현재 수정중인 commit에서 어떠한 file들을 변경했는지 그 내역을 알 수 있다.

* git rebase를 하다보면, "The previous cherry-pick is now empty, possibly due to conflict resolution." 이런 메시지가 나올 수 있는데, 이는 내가 다른 commit과 내가 edit한 commit의 내용이 겹쳐서 merge가 되어버리면 아무런 파일에 변경 내용이 없는 commit이 발생 할 수 있다. 이런 경우 git commit --allow-empty 을 입력해서, 그냥 아무 내용 없는 빈 commit을 저장하고 git rebase --continue를 하면 된다.

* git rebase를 하다보면, 내가 다시 작성한 과거의 commit과 다른 그 이후의 commit에서 변경사항이 서로 충돌이 날 수 있다. 이런 경우 git status를 입력해보면 어떤 파일에서 수정이 일어났는지 알 수 있고, conflict 를 해결하고 git add / commit을 하면 된다.

https://backlog.com/git-tutorial/kr/stepup/stepup7_6.html

 

 

- 여러개의 commit을 1개의 commit으로 합치기

git rebase -i HEAD~[N]

합칠 커밋들의 맨 앞에 있는 pick을 지우고 squash 또는 s를 적고 editor를 저장한다. 

그런데 그리고나서 push를 하려고하면 remote origin에 있는 내용과 맞지 않아서 push할 수 없다. 이러한 경우 git push origin master -f 을 입력해서 강제로 remote의 내용을 덮어씌워야한다.

 

https://www.internalpointers.com/post/squash-commits-into-one-git

 

 

- 실수로 용량이 매우 큰 파일을 commit해버렸다가, 다시 되돌리는 방법.

가장 중요한 것은 앞으로의 commit에서 해당 파일을 삭제한다고 되는게 아니라, commit history에서 삭제해야한다는 점이다. 만약 해당 파일이 가장 최근의 commit에서 포함되었다면, 매우 간단히 아래의 두 커맨드로 해결 가능하다.

 

git rm --cached <filename>

git commit --amend -C HEAD

 

그러나 만약 과거의 commit에 포함이 되어버렸다면 git log와 git show <hash> --name-status 등의 명령어를 통해 어떤 commit에서 포함된지 알아낸 다음, git rebase -i <hash>를 하여 해당 commit에 가서 아래의 두 커맨드를 실행한다.

 

git rm --cached <filename>

git commit --amend -C HEAD

 

그다음 git rebase --continue를 하면 된다.

 

https://medium.com/analytics-vidhya/tutorial-removing-large-files-from-git-78dbf4cf83a

 

 

 

# Git branch

 

- Git 의 branch 다루기 : branch란 내가 원래 작업하던 파일을 안전하게 격리보존한 상태에서(master), 새로운 작업을 다른 저장소에서 해줄 수 있게 하는 기능이다. 마치 backup본 저장시켜 놓고 새로 작업하는 것과 같음. 말그대로 가지치기

git checkout -b 브랜치이름 : 새로운 branch를 생성
git branch -d 브랜치이름 : 해당 branch를 삭제
git branch -m Old이름 New이름 : branch 이름 변경

git branch : 현재 있는 모든 branch 확인
git push origin 브랜치이름 : 해당 branch를 remote 서버에 저장한다.

git checkout master : 현재 workspace를 master branch에 저장해둔 상태로 되돌린다.
git checkout 브랜치이름 : 현재 workspace를 해당 branch로 이동한다. ( master , devel , debug 등등 )
git checkout 브랜치이름 --파일이름 : 해당 파일 하나를 해당 branch의 최신 버전으로 되돌린다. (SVN의 revert와 완전히 동일. 즉, 변경사항을 전부 날리고 원래 commit했던 파일로 복구하기)

 

- remote branch에 force push하기

remote branch의 commit을 지워버리고, 현재 branch의 commit으로 덮어 씌워버릴 때, 강제로 push를 할 수 있다. 즉 원래는 pull을 한다음 push를 해야하는데, 이를 무시하고 그냥 push를 하는 것이다.

ex) remote의 commit을 간결하게 하려면, local branch에서 rebase를 한다음, 그것을 강제로 remote에 덮어씌워야한다. 그런데 이 경우 remote에서는 해당 commit들이 복구되지 않을 수 있으므로 주의해야한다.

 

git push -f origin remote_branch

 

- local branch를 이름이 다른 remote branch에 push하기(branch mapping)

아래와 같이 <local>:<remote>로 mapping을 알려주면서 push를 하면 된다.

git push origin local_branch:remote_branch

 

- 두 Branch 간의 차이점 비교하기
git diff branch1 branch2
git diff branch1:[파일경로] branch2:[파일경로]
https://stackoverflow.com/questions/4099742/how-to-compare-files-from-two-different-branches

 

- 두 Branch 합치기: 이게 branch를 다루는 데서 가장 어려운 부분일 듯.
git diff 브랜치1 브랜치2 : 두 branch사이의 차이점 비교

git rebase 목표브랜치이름 : 두개의 branch를 하나의 branch로 합치기 위해서 없애고, 현재의 branch에다가 목표브랜치의 commit을 가져와서 적용시킨다. (그다음 git checkout 를해서 목표브랜치로 이동한다음, 아래의 merge 명령어 실행)

git merge 가져올브랜치이름 --no-commit : 현재 branch에다가 해당 branch를 가져와서 합치되, commit은 하지 않고 staging만 한다.
    * git merge의 기본 동작은 다른 branch의 변경사항을 가져와서 auto-merge를 한 다음, conflict가 안나면 commit까지 해버리는 것이다.

 

- 다른 Branch에 있는 일부 변경사항(commit)을 가져오기
git cherry-pick <commit>
https://stackoverflow.com/questions/4315948/git-partial-merge-not-whole-branch

 

- 다른 Branch에 있는 새로운 파일을 복사해서 가져오기
git checkout 다른브랜치 파일이름
http://seorenn.blogspot.kr/2014/04/git.html


- 다른 Branch에 있는 파일 내용으로 덮어씌우기
git checkout -p 다른브랜치 파일이름
http://seorenn.blogspot.kr/2014/04/git.html

- 다른 commit에 있는 파일 내용을 가져오기
git checkout HEAD~3 -- 파일이름

 

- Branch 이름 변경하기: 이미 remote 에 branch를 push한 경우에 해당함. (push를 안한 경우 그냥 첫번 째 줄만 하면됨.)
git branch -m old_name new_name
git branch -a (local과 remote에 있는 모든 branch 이름확인)
git push origin --delete old_name (old branch 삭제)
git push origin new_name (new branch 올리기)
 

- Local에 있는 branch 삭제하기
git branch -d 브랜치이름
git branch -D 브랜치이름 (merge 안된 내용이 있어도 강제 삭제)

 
 
# Git remote

- Remote에 있는 branch에 checkout하기
git fetch origin
git checkout [브랜치이름]
https://stackoverflow.com/questions/1783405/how-do-i-check-out-a-remote-git-branch

 

- Remote에 있는 branch 삭제하기

git push origin --delete 이름
* 단 Github에서는 master 브랜치를 삭제하려면, 웹사이트의 setting에 들어가서 default branch를 다른 걸로 변경한다음 삭제해야함.

 

- Remote 다루기
git remote -v : 현재 remote 목록 확인
git remote add orgin https://github.com/bi-lab/deeplearning_tutorial.git : remote 추가
git remote rename origin upstream: remote의 이름을 변경한다.
git remote remove origin : remote 삭제

- Remote에 권한이 없어서 403에러가 뜨는 경우(실제로 권한이 없을 수도 있다.)
git remote set-url origin https://YOURID@github.com/USERNAME/REPOSITORY.git

- Remote Repository에 새로 업데이트된 정보가 있는 지 확인하기: 
https://stackoverflow.com/questions/2514270/how-to-check-for-changes-on-remote-origin-git-repository

git remote update : remote의 정보를 가져오기만한다.(합치진 않음, git fetch origin 과 거의 같은 것으로 보임?)
git status

 

- 여러 개의 remote 다루기

git remote add new_origin url : remote 서버 추가

git remote remove new_origin : remote 서버 삭제

git branch -u new_origin/branch : 어떤 remote branch를 트랙할지

git push new_origin branch

git remote push all branch : 모든 remote repo에 push하기

https://jigarius.com/blog/multiple-git-remote-repositories

 

# Git 의 기타 명령어
git status : 파일들의 현재 상태 및 변경 사항등을 확인할 수 있다. 가령 add가 안된 파일 등이 나온다.
git log : commit log 살펴보기
git log --all --graph : commit log 에서 branch 정보를 그림으로 확인. 아주 유용함.
git tag 버전넘버 고유식별자(a1d2c3) : 버전 이름 달기
git fetch

remote에 있는 최신 데이터를 내 로컬에 다운로드함. 그 이외 추가적인 작업은 전혀 안 함.(이걸해야 remote에만 존재하는 branch에 checkout을 할 수 있음.)

git merge : 서버에서 가져온 최신 내용을 내 local repo와 합침

 

 

# Git status 속도가 매우 느려졌을 때 해결 방법

git gc

이걸 하고나니 속도가 몇십배 더 빨라졌다.

https://stackoverflow.com/questions/4994772/ways-to-improve-git-status-performance/37423765

 

 

 

# Detached Head에 대한 이해
HEAD: 내 현재 워크스페이스가 가리키는 commit을 의미함. Detached HEAD는 내 HEAD가 가장최신 지점을 가리키지 않는다는 의미임. 즉 몸통을 보고있다.

보통의 경우 branch를 checkout하면 항상 가장 마지막 commit 으로 이동되므로 HEAD가 항상 끝에 있다. 그런데 checkout을 branch가 아니라 특정 과거 commit 으로 돌아가는 경우 detached HEAD상태가 된다. Detached HEAD상태에서는 내가 새로 생성항 commit이 그 어떤 branch에도 속하지 못하고, 혼자서만 존재하게 된다. 즉 HEAD에만 새로운 commit을 붙일수가 있는 것인데 몸통에서 commit하면 갈데가 없는것.

- checkout으로 commit을 이동하다가 branch에서 떨어져 나온 commit 발생 시 해결 법:
우선 이런 상황에서는 다음과 같은 경고 메시지가 뜬다.
head detached from <~~~>
Warning: you are leaving 1 commit behind, not connected to any of your branches.

이런 현상이 발생하는 것은 checkout으로 branch가 아니라, 특정한 commit에서 작업하다가 새로운 commit을 생성하면, 그것이 특정한 branch에 속하지 않은 상태로 저장되기 때문이다. 반드시 모든 commit은 branch에 속해야하기 때문에 이를 해결하기 위해서는 우리가 원하는 branch로 checkout한 다음 아래와 같이 cherry-pick으로 낙동강 오리알이 된 commit들을 붙여야한다.

## 해결법 1: detached된 commit을 HEAD로 통합해서 가져오는 방법
git reflog : commit을 포함해 내가 HEAD를 이동한 흔적을 확인할 수 있다. 이 명령어로 branch에 안 붙은 commit의 id를 확인한 다음

git checkout <붙일 branch이름>
git cherry-pick <commit> <commit> <commit> ... : 이 명령어를 통해서 현재의 branch로 해당 commit을 가져온다.

http://stackoverflow.com/questions/9984223/what-happens-to-git-commits-created-in-a-detached-head-state

 

## 해결법 2: HEAD를 날려버리고, 과거 commit을 HEAD로 만들고 싶을 때
git checkout 과거commit : 이러면 detached HEAD상태가 됨
git branch -b tmp_master : 임시로 카피를 떠둠
git branch -D master: HEAD를 날림
git branch -M tmp_master master: master로 이름을 변경함

그런데 이렇게 번잡하게 안하고, git reset --hard HEAD~1 으로도 할 수 있을 것 같은데 정확히는 모르겠다.

 

 

# untracked file 한방에 지우기

“The following untracked working tree files would be overwritten by checkout” 문제 해결
git clean -n : 뭐가 지워질지 보여준다.
git clean -d  -f .  : 지운다.


https://koukia.ca/how-to-remove-local-untracked-files-from-the-current-git-branch-571c6ce9b6b1

 

 

# 유용한 git alias

말도 안되는 git alias 덕후의 예제...

https://johngrib.github.io/wiki/git-alias/

 

## commit 할 때마다 hash 값 남기기

comm = "! git commit && git describe --always > git_hash.txt"

 

## Git 의 log 쉽고이쁘게 보기
http://blog.kfish.org/2010/04/git-lola.html

vim ~/.gitconfig 에다가 다음의 설정을 입력한 후 저장한다.

[alias] lol = log --graph --decorate --pretty=oneline --abbrev-commit lola = log --graph --decorate --pretty=oneline --abbrev-commit --all

lolan = log --graph --decorate --pretty=oneline --abbrev-commit --all --name-status

[color] branch = auto diff = auto interactive = auto status = auto

또는 다음을 실행해도 된다.

 

git config --global alias.lol "log --graph --decorate --pretty=oneline --abbrev-commit"

git config --global alias.lola "log --graph --decorate --pretty=oneline --abbrev-commit --all"
git config --global alias.lolan "log --graph --decorate --pretty=oneline --abbrev-commit --all --name-status"

사용법: 

git lol / git lola / git lolan

 

## skip-worktree등록하기

[alias]

hide   = update-index --skip-worktree

unhide = update-index --no-skip-worktree

hidden = "!git ls-files -v | grep ^[hsS] | cut -c 3-"  

 

https://riptutorial.com/ko/git/example/14851/%EC%B6%94%EC%A0%81-%EB%90%9C-%ED%8C%8C%EC%9D%BC%EC%9D%98-%EB%B3%80%EA%B2%BD-%EC%82%AC%ED%95%AD%EC%9D%84-%EB%AC%B4%EC%8B%9C%ED%95%A9%EB%8B%88%EB%8B%A4---%EA%B7%B8%EB%A3%A8%ED%84%B0%EA%B8%B0-

 

 

 

# git tag

git tag는 특정한 commit hash에 유니크한 이름을 짓는 것으로 이해할 수 있다. 보통 release version을 관리하기 위해 사용되고, 다음과 같은 명령어를 사용할 수 있다.

git tag -l : tag 리스트 보기

git tag tag이름: Lightweight tag 생성하기(상세 설명을 달지 않는다)

git tag -a tag이름 -m "코멘트" : Annotated tag 생성하기

git show tag이름 : tag관련 문서 보기

https://git-scm.com/book/ko/v2/Git%EC%9D%98-%EA%B8%B0%EC%B4%88-%ED%83%9C%EA%B7%B8

 

 

# git 협업 이슈 해결
## OS가 다른 환경에서 공동 작업시 생기는 CRLF문제
- Window는 line ending으로 \r\n (CRLF)를 사용하는 반면, Unix나 Mac OS에서는 \n (LF)만을 사용한다.

- 만약 Mac에서 작업한 코드를 윈도우에서 pull하게 되면 변경된 모든 파일의 line ending이 달라서 파일 전체에 대해 수정이 일어 났다고 인식해서 git diff를 하게되면 지옥이 펼쳐진다.

- 이러한 문제를 다음의 문서를 참조하면 거의 대부분 해결이 가능하다.

* 한글자료: https://www.lesstif.com/pages/viewpage.action?pageId=20776404
* 영어자료: https://stackoverflow.com/questions/170961/whats-the-best-crlf-carriage-return-line-feed-handling-strategy-with-git

 

- 위 해결 방법의 결론을 요약하면 다음과 같이 2가지 선택지가 있다.
1. 윈도우 사용자는
git config --global core.autocrlf true

Unix나 Mac 사용자는
git config --global core.autocrlf input

을 빠짐 없이 입력하면 아무런 문제가 생기지 않는다.(무조건 git repo에 LF만 올리는 방식)

2. 그러나 위 방법의 불편함을 없애려면, .gitattributes 파일을 repo에 추가시키면 알아서 각 contributor의 설정을 덮어 씌워서 해결된다.

그리고 다음 github에서 기본 공통 파일을 받을 수 있다.
https://github.com/alexkaratarakis/gitattributes

 

- 그런데 위의 세팅을 개발 처음단계에서 부터 했으면 괜찮은데, 개발 중간에 추가하게 된 경우 모든 파일의 line ending을 초기화해주어야한다. 다음을 참조

https://help.github.com/en/articles/dealing-with-line-endings#refreshing-a-repository-after-changing-line-endings


- 다음은 line ending을 무시하고 diff 및 merge를 하는 방법이다,
git diff master origin/master --ignore-space-at-eol
git merge origin/master -s recursive -Xignore-space-at-eol

 

## Conflict Resolve 하기(Resolve: HEAD, ===== 등을 제거하고 conflict난 코드를 1개로통합하는 작업을 의미함)
 
git pull : Automerge가 불가능한 파일들에서 conflict가 발생했다고 뜬다.
git status : Unmerged path에 conflict가 발생한 파일을 확인한다.
conflict가 일어난 파일을 에디터로 열어서 head <<< / >>> source 이런 부분을 다 지우고 제대로된 하나의 파일로 만들어준다. 이때 vim의 여러 단축기 (ex) 한줄 삭제 : dd)를 활용하면 좋다. 그리고나서 파일을 다 수정한다음,
git add -A
git commit -m "conflict merged"
git push origin master
 
 
## 3-way Conflict Resolve하기
Sublime_merge 에서 resolve를 클릭하면 [Commit A/Conflict Merge상태/Commit B] 세가지 화면이 보여지는 3-way merge tool이 실행된다. 이를 사용해서 수동으로 merge하면 된다.


 
- 아직 push안된 commit의 message 수정하기
git commit --amend
git commit --amend -m "새로 덮어 쓸 메시지"

 

- gitignore 사용하기

.gitignore 파일을 이용해 특정한 확장자의 파일들이 add되는 것을 방지할 수 있다. 예를 들어 pyc같은 백업 파일 등이 이에 해당한다.

*.swp
*.pyc

등을 작성해서 .gitignore파일을 workspace에 넣어놓으면 알아서 add대상에서는 제외된다.

 

 

- Git config 설정하기
git config -l : 전체 config 목록 확인
git var -l : 전체 var 목록 확인

vim ~/.gitconfig

git config --global http.proxy ~~~
git config --global --unset http.proxy 

 

- Git config를 각 디렉토리마다 설정하기
git config --local user.email "이메일주소"
git config --local user.name "이름"

 

# Git 아키텍쳐 관리

반드시 최소한의 project 단위로 repo를 생성해야한다. 그렇지 않으면 두개의 project에서 동시에 작업이 일어났는데, 그것이 하나의 repo에 모두 저장이 되면 context가 꼬여서 도저히 작업을 할 수 없게된다.

또한 git repo내부에 또다른 git 을 생성하면 submodule의 개념으로 다른 repo에서 해당 작업을 알아서 clone해서 가져온다. 그러나 일반적으로는 사용하지 않는 방식. 거대한 프로젝트에서 사용

 

 

 

# Subtree

git remote add [origin_name] [github_url]

git subtree add --prefix [위치할 경로] [origin_name] [remote_branch_name] --squash

git reset --hard HEAD~1  : 방금 실행한 subtree add 명령을 취소한다.

 

git subtree pull --prefix  [위치할 경로] [origin_name] [remote_branch_name] --squash

또는 git push -s subtree [origin_name] [remote_branch_name]

 

git subtree push --prefix [위치할 경로] [origin_name] [remote_branch_name]  : subtree project에 push하기

또는 git pull -s subtree [origin_name] [remote_branch_name]

 

 

* 에러해결

- Working tree has modifications.  Cannot add.

이 에러는 현재 시점에서 commit이 안된 수정사항이 있거나 뭔가 branch가 더럽혀진 상태일 때 발생한다. 따라서 변경사항을 전부 commit하거나, 아래와 같이 checkout을 해주어야한다.

git checkout <current branch>

https://stackoverflow.com/questions/3623351/git-subtree-pull-says-that-the-working-tree-has-modifications-but-git-status-sa

 

* subtree add 명령어 추가 설명

--squash: subproject의 git history를 1개의 commit으로 요약해서 가져온다. 보통 --squash를 해주는게 기본적이다.

--prefix: subproject가 위치할 경로를 지정할 수 있다. 이때 경로의 이름에 '-'가 들어가면 안된다. Python 모듈의 이름에는 '-'을 쓸 수 없기 때문이다.

 

* subtree 삭제하기

git rm -r <subtree_directory>

rm -r <subtree_directory>

그냥 해당 directory를 삭제해버리면 된다.

https://stackoverflow.com/questions/49028258/git-remove-subtree-or-change-directory-of-subtree

 

* 명령어에 대한 상세 튜토리얼

https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec

 

* subtree directory 변경하기

git mv old_dir new_dir

https://stackoverflow.com/questions/49028258/git-remove-subtree-or-change-directory-of-subtree

 

* subtree 내에서의 코드 변경사항만 확인하기

git log -- subtree_dir

https://stackoverflow.com/questions/10918244/git-subtree-without-squash-view-log

 

 

# Submodule

## Submodule 그냥 따라하기(submodule add할 때, --force를 하지 않으면, repository에 간혹 submodule 내용이 올라가지 않아서, 다운받아지지 않는 경우가 있어서 매우 불편하다.)

git status --ignore-submodules=dirty

git submodule add --force [submodule_url]

git submodule update --init --recursive

git add .git commit -m "submodule added"
git push origin master

 

 

## Submodule 추가하는 다양한 방법
git submodule add --force [submodule_url] : remote repo를 "repo이름"을 기본 경로로하여 clone해서 submodule로 추가함

git submodule add --force [submodule_url] [submodule_path]: remote repo를 지정한 경로에 clone해서 submodule로 추가함

git submodule add --force -b [branch이름] [submodule_url [submodule_path]: remote repo의 특정 branch를 지정한 경로에 clone해서 submodule로 추가함

* 위의 submodule add 명령을 한다음에는, 반드시 다음 명령을 수행해야함

git submodule update --init --recursive

그 후 기존 repo에 git add / commit / push 실행

https://chrisjean.com/git-submodules-adding-using-removing-and-updating/
https://www.activestate.com/blog/2014/05/getting-git-submodule-track-branch

 

 

## Submodule들만 업데이트하기
개별 submodule에 들어가서 git pull을 하거나, 혹은 다음을 실행

git submodule foreach git pull origin master

https://stackoverflow.com/questions/25957125/git-submodule-permission-denied

 

## clone하면서 submoudule까지 한 번에 받아오기

git clone --recurse-submodules [GIT_URL]

https://stackoverflow.com/questions/3796927/how-to-git-clone-including-submodules

## git pull 하면서 한번에 submodule도 업데이트하기
git pull --recurse-submodules

https://stackoverflow.com/questions/1030169/easy-way-to-pull-latest-of-all-git-submodules

 

 

## Submodule 삭제하기: 안타깝게도, 현재 submodule은 수동으로 직접 삭제해줘야한다.
1. vim .gitmodules
-> 해당 submodule에 대한 내용삭제 후 저장
(만약 한개의 submodule만 존재하면, 그냥 .gitmodules 파일을 삭제해도 됨)

2. vim .git/config
-> 해당 submodule에 대한 내용삭제 후 저장

3. 수동 파일 삭제

git rm --cached 

[submodule_path]


rm -rf .git/modules/[submodule_path]
rm -rf [submodule_path]

4. git commit
git add .
git commit -m "submodule removed"

https://gist.github.com/myusuf3/7f645819ded92bda6677


## Submodule 내용을 변경된 경우를 git status에서 표시하지 않는 방법
Submodule 디렉토리에 들어간다음 git checkout . 을 해서 변경사항을 모두 없애주어도 된다.
https://stackoverflow.com/questions/4873980/git-diff-says-subproject-is-dirty/39803366


만약 단순히 status에서 표시만하지 않으려면 .gitmodules에 

ignore = dirty 을 추가하여 변경 사항을 무시할 수 있음.

https://stackoverflow.com/questions/3240881/git-can-i-suppress-listing-of-modified-content-dirty-submodule-entries-in-sta



혹은 일괄로 처리하고 싶은 경우, submodule을 추가하기 전에 먼저 git status --ignore-submodules=dirty을 실행하면 된다.

 

 

## Submodule의 문제와 해결

local repo의 위치를 변경할 경우, submodule의 gitdir을 일일이 수정해줘야한다. 그렇지 않으면 git add 실행시 fatal: Not a git repository: 이런 에러가 계속해서 뜬다.해결방법: vim submodule/.git 

을 입력해서, 일일이 경로를 수정해준다.

https://stackoverflow.com/questions/41718822/how-to-resolve-fatal-not-a-git-repository

 

- ignore옵션에는 다음과 같은 것들이 있다.

all : 해당 submodule에 대한 모든 것을 무시한다.
dirty: commit 되지 않은 모든 변화를 무시한다.

untracked: untracked file에 대해서만 무시한다.

none: 무시하지 않는다.

https://git-scm.com/docs/gitmodules

 

 

## Read권한 사용자가 Pull Request(PR 보내기)

아래의 링크에 잘 설명되어 있다. 핵심은 다음과 같다.

https://wayhome25.github.io/git/2017/07/08/git-first-pull-request-story/

 

1. fork를 해서 read 권한만 갖고 있는 repo를 내 계정으로 밑으로 복사해온다.

2. PR을 보내고 싶은 원본 repo를 remote add 로 추가한다.

3. Local repo에서 commit을 생성하고, 새로운 branch에 push한다.
4. fork된 github에 가면 "New pull request"을 클릭하여, 원본 repo에 PR을 요청한다.

 

 

# 기타 팁
리베이스 머지하면 갈라졌던 브랜치를 옮겨서 한줄기로 만드는 일을 한다. 구체적으로는 다른브랜치에 적용된 커밋을 가져와서 적용하는게 리베이스 임

깃허브는 컨트리뷰션하기위해서는 포크해서 고대로 복사해온다음 코드 수정하고 그 해닿레포가 내꺼를 풀하게만드는거라 풀 리퀘스트를 요청하는개념 임

머지할때 --노-에프에프 옵션을 반드시 줘야 브랜치를 머지한기록이 남아서 되돌릴수가 있음

 

- 주요 참고 자료
https://backlogtool.com/git-guide/kr/stepup/stepup3_1.html

rogerdudler.github.io/git-guide/index.ko.html

http://bcho.tistory.com/773

http://blog.outsider.ne.kr/572

http://bi.snu.ac.kr/~jkim/basic.html#1

 

댓글