Git Study Note
Study Resources
Basic Knowledge
Local:
- working directory
- index / staging files
- repository
Remote:
- remote repository
Normal Operations
Init a empty git repo in local:
$ git init
Config:
$ git config user.name baurine
$ git config user.email [email protected]
Commit:
$ touch README.md
$ git add .
$ git commit -m 'Init repo'
Create an empty repository in server, then push local repo to remote:
$ git remote add origin [email protected]:baurine/study-note.git
$ git push -u origin master
Clone an existed repo:
$ git clone [email protected]:baurine/study-note.git
Fetch code from remote:
$ git fetch origin && git rebase [origin/master]
// or
$ git pull [--rebase]
Fetch code and delete removed branches:
$ git fetch -p
Commit for PR:
$ git checkout master
$ git pull
$ git checkout feature/support_sns_login
$ git rebase master
$ git push [-u] origin feature/support_sns_login
// then create PR in the server
// or a more simple way
$ git fetch
$ git rebase origin/master feature/support_sns_login
$ git push [-u] origin feature/support_sns_login
$ git branch -f origin/master master
Make sure pull the latest code from remote and rebase the latest code before you pushing the code to remote for avoiding code conflicit.
Merge branch code in local and directly push, skip PR:
$ git checkout master
$ git merge --no-ff feature/support_sns_login
$ git push origin master
Make sure use --no-ff
option for merge
to avoid fast forward.
Delete the local useless branch:
$ git branch -d feature/support_sns_login
Tips
Git help
$ git help [command]
$ git help reset
Git config
$ git config user.name baurine
$ git config user.email [email protected]
$ git config --global core.editor vim
$ git config --list
$ git config user.email
- /etc/gitconfig:
git config --system
- ~/.gitconfig:
git config --global
- .git/config:
git config
Git log
$ git log --color --graph --abbrev-commit --pretty=oneline
$ git log --color --graph --abbrev-commit --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'
$ git log -p // 显示 diff
$ git log -p filename // 仅显示某个文件的修改历史
$ git blame filename // 查看文件的每一行是哪个提交最后修改的
Git remote
Display remotes:
$ git remote
origin
$ git remote -v
origin [email protected]:baurine/study-note.git (fetch)
origin [email protected]:baurine/study-note.git (push)
Display a remote info:
$ git remote show origin
Add a remote:
$ git remote add github [email protected]:baurine/study-note.git
$ git remote -v
github [email protected]:baurine/study-note.git (fetch)
github [email protected]:baurine/study-note.git (push)
origin [email protected]:baurine/study-note.git (fetch)
origin [email protected]:baurine/study-note.git (push)
Remove a remote:
$ git help remote
$ git remote remove github
Reset a remote url:
$ git remote set-url origin [email protected]:baurine/study-note.git
Tracking remote branch for local branch
Automatically tracking with push -u
command:
$ git push -u origin master
Then local master branch will auto track with remote master branch.
If you forget -u
option, you can manually set it later:
$ git branch --set-upstream-to=origin/master master
or
$ git branch -u origin/master [master]
-u
is short for --set-upstream-to=
Keep track forked repo with upstream repo
How to fork and clone a github repository
$ git clone [email protected]:baurine/stack-overflow-copy-paste.git
$ git remote add upstream [email protected]:eggheadio-github/stack-overflow-copy-paste.git
$ git fetch upstream
$ git branch --set-upstream-to=upstream/master master
$ git checkout master
$ git pull
Ignore the file that has alreday commited
http://segmentfault.com/q/1010000000430426
$ git rm --cached logs/xx.log
$ echo 'xx.log' >> .gitignore
Git rebase
Rebase current branch to another branch:
$ git rebase another_branch
Rebase branch_a
to branch_b
, and then switch to branch_a
:
$ git rebase branch_b branch_a
Modify commit history:
$ git rebase -i HEAD~n
Meet conflicit when rebase, you need resolve it at first, then use git add .
stage the changes, git rebase --continue
to continue rebase process, give up by git rebase --abort
.
$ git rebase -i HEAD~n
// meet conflict, resolve
$ git add .
$ git rebase --continue
Branch & Checkout
List all branch:
$ git branch
Create a new branch that bases current branch:
$ git branch new_branch
Create a new branch that bases current branch, and switch to new branch:
$ git checkout -b new_branch
Switch to another branch:
$ git checkout another_branch
Reset some files by git checkout
:
$ git checkout -- file_name
$ git checkout . // reset all modified files
Force change a branch point to another node by git branch -f
, it is really very useful, very cool:
$ git branch -f staging origin/develop // Force make staging branch same with origin/develop branch
$ git branch -f develop HEAD // Force make develop branch point to current branch HEAD
Delete local branch:
$ git branch -d branch_name
Delete remote branch:
$ git push origin :branch_name
Reset
Reset local branch by git reset
:
$ git reset --[soft|mix|hard] <commit-hash>
What's the differences with soft, mix, hard:
$ git help reset
--soft
: Only reset the code in repository, will not affect working tree and index files, likes revert thegit commit
.--mix
: Will reset the code both in repository and index files, only not affect the code in working tree, likes revert thegit commit
andgit add
.--hard
: Reset the code all in repository, index files, and working tree.
--hard
is dangerous but most use, in fact rarely to use --soft
and --mix
.
Reset remote branch by push -f
:
$ git checkout master
$ git reset --hard HEAD~3
$ git push -f origin master
Reset after mis-operation by git reflog
:
$ git reflog
$ git reset --hard HEAD@{n}
Tag
$ git tag
$ git tag 1.0.1
$ git push origin --tags
Git alias
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
$ git config --global alias.last 'log -1'
$ git config --global alias.lg "log --color --graph --abbrev-commit --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"
The alias configuration stores in ~/.gitconfig
:
$ cat ~/.gitconfig
submodule
$ git submodule add [email protected]:xxx/yyy.git
在某个 repo 目录 (比如 zzz) 下执行此命令后,将会在此目录下 clone [email protected]:xxx/yyy.git,并在 repo 的根目录生成 .gitmodules 文件,文件中将记载 zzz/yyy 目录是一个 submodule。
[submodule "zzz/yyy"]
path = zzz/yyy
url = [email protected]:xxx/yyy.git
将此 repo 推送到 github 上去后,在 github 上查看时,可以点击 zzz/yyy 目录链接,它会自动跳转到 [email protected]:xxx/yyy.git 项目主页。
clone --bare
想把一个本地的 git repo 快速地拷贝到别的地方,又不想带上无关的文件,比如一些 build 生成的文件,可以试试 git clone --bare
,比如
$ git clone --bare ./a_git_repo ./a_git_repo.git
生成的 a_git_repo.git
目录,将是一个裸仓库,相于是 ./a_git_repo/.git
目录,但 repo 的 remote 信息将丢失。
由裸仓库重新生成可以工作的本地仓库:
$ git clone ./a_git_repo.git
将会生成 ./a_git_repo
目录。
最近得知了一个新的命令 git bundle
,貌似有相同的作用。
Others
Archive a local repo:
$ git archive --format zip --output xxx.zip branch
Clean a local repo:
$ git help clean
$ git clean -dxf // clean all untracked files, includes ignored by .gitignore, dangerous
$ git clean -dxnf // only list but not really delete those files untracked
-d
: folder-f
: force-x
: include files ingored by .gitignore-n
: only list but not really do
# merge project-a to project-b
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a
为了避免合并时产生冲突,在合并前需要对 project-a 进行目录上的一些调整,一般情况下这种合并,我们是希望将 project-a 放到 project-b 的某个单独目录下,比如这个目录名叫 demo-a,那么合并前,在 project-a 的根目录下,新建 demo-a 目录,把其它所有文件放到此目录下,然后提交,再进行合并就没问题了。
Commit Message
Note for LearnGitBranching
Overview about what I learned from it:
- Freely change any branches by
git branch -f
, awesome! - Differences between
^
and~
afterHEAD
- Modify commit history by
rebase -i
rebase br1 br2
=git checkout br2 && git rebase br1
- What's the real meaning for local branch
origin/br
- Make local branch track with remote branch:
git checkout -b br origin/br
orgit branch -u origin/br [br]
git pull --rebase
=git fetch && git rebase
git push/fetch/pull origin <source>:<dest>
Local Part
HEAD Meaning:
$ git checkout <sha1>
Relative Refs:
- Moving upwards one commit at a time with
^
Moving upwards a number of times with
~<num>
$ git checkout HEAD^ | $ git reset HEAD^ $ git checkout master^ | $ git reset master^ $ git checkout master^^ | $ git reset master^^ $ git checkout master~2 | $ git reset master~2
The former only changes the HEAD pointer, but the branch self doesn't change; while the latter will change both the HEAD pointer and branch self.
Branch Forcing:
branch -f
$ git branch -f master HEAD~3
Interactive Rebase:
rebase -i
$ git rebase -i HEAD~4
Juggling Commits #1:
(cool~!)
$ git rebase -i HEAD~2
$ (交换 C2 C3 顺序,使 C2 在 HEAD)
$ git commit --amend // (在 C2 上补充提交)
$ git rebase -i HEAD~2
$ (再次交换 C2 C3 顺序,使 C3 重新在 HEAD)
// (从而实现了修改历史提交,但前提是这些提交之间不要有太多冲突)
Juggling Commits #2:
(用 cherry-pick 实现)
$ git checkout master
$ git cherry-pick C2
$ git commit --amend
$ git cherry-pick C3
Git Tags:
(tag 是固定的,不变的,而 branch 是随着提交变更的)
$ git tag v1 C1 // C1 表示某个节点
$ git tag v0 C1
$ git tag v1 C2
$ git checkout v1
Rebasing over 9000 times:
$ // 当前在 master 分支
$ git rebase master bug_fix
// 此命令的结果将导致切换到 bug_fix 分支, 并将 bug_fix 分支 rebase 到 master 分支上 (cool~!)
// = git checkout bug_fix; git rebase master
$ git rebase bug_fix side
$ git rebase side another
$ git rebase another master
Multiple Parents:
(^n
和 ~n
的区别)
(哦,~n
, ~
后面的 n 是 HEAD 的第前 n 个节点,~
默认是指 ~1
,^n
,^
后面的 n 是指,如果一个 merge 节点,有两个父节点,那么 ^1
表示第一个父节点,^2
表示第二个父节点,最多两个父节点,默认 ^
是指 ^1
)
$ git checkout HEAD~
$ git checkout HEAD^2
$ git checkout HEAD~2
// (以上三步可以合并成 git checkout ~^2~2, so coooooool~!!!)
练习:
$ git branch -f bugWork HEAD~^2~
// (cool, 一步完成以前 n 步做的事情)
Remote Part
以上都是 local 操作,以下进入第二章,remote 操作。
$ git clone remote_repo
origin/master
的含义:<remote name>/<branch name>
,一般情况下用 "origin" 作为 remote name.
$ git checkout origin/master; git commit
此时, origin/master 分支并不会随着一次提交往前移动 (往前移动的是 HEAD 节点,因此 HEAD 和 origin/master detach 了),origin/master 是固定的,它只随着远程服务器的更新而更新。
origin/master 反映的是远程分支的状态,因此在本地是不可修改的。
三个 master 分支:
master
: 本地 master 分支,工作分支origin/master
: 本地 origin/master 分支,作为远程 master 分支的镜像,不可修改,通过git fetch origin master
得到更新remote/origin/master
: 远程 master 分支,通过git push origin master
得到更新
Git Fetch:
同步远程分支,即将本地的 origin/br 更新,与远程的 remote/origin/br 同步。
这个操作不会对本地的 br 分支有影响。
Git Pull:
$ git checkout master
$ git pull
// =
$ git fetch; git merge origin/master;
(这种方法不好,除非本地在 master 上没有提交过代码。同步别人的代码应该是 rebase,合并自己的代码用 merge)
Git Push:
注意,如果 git push
后面没有加上任何参数,那么它的默认参数将取决于配置中的 push.default
选项,这个值在不同的 git 版本中略有不同,但不是大问题。
$ git fetch; git rebase origin/master; git push
// (这才是正确姿势)
上面的命令有简写模式:
$ git pull --rebase; git push
// (wow,原来如此,这才是我一直想要的啊!)
Merging Feature Branches:
Some developers only push and pull when on the master branch -- that way master always stays updated to what is on the remote (origin/master).
$ git fetch
$ git rebase origin/master side1
$ git rebase side1 side2
$ git rebase side2 side3
$ git rebase side3 master
$ git push
由这个教程想出一个算法题,如何实现 git rebase 操作 (不考虑冲突的情况, 将两个分支按 rebase 的方式合并成一个分支), 解决两个问题: 1. 寻找共同的父节点,2. 将一个分支的尾结点指向另一个分支的头结点。
一般数据结构中树是由父结点指向子结点的,git 里是子结点指向父结点。
Remote-Tracking Branches:
Way 1:
$ git checkout -b totally_not_master origin/mater
// (Create a new branch named totally_not_master and sets it to track origin/mater)
Way 2:
$ git branch -u origin/master [foo]
// =
$ git branch --set-upstream-to=origin/master [foo]
// (if you are now in foo branch, foo can omit)
// (-u is short for --set-upstream-to)
练习题: (4 步法)
$ git checkout -b side origin/master
$ git commit
$ git pull --rebase
$ git push
Git Push Arguments:
$ git push <remote> <place>
Example:
$ git push origin master
将本地 master 分支的代码推送到远程 origin 同名分支即 master 分支,即 origin/master。
这个操作不会影响当前 HEAD 节点,假如它之前在 develop 分支,那么现在它还在 develop 分支。
$ git checkout C0; git push origin master;
// 将本地 master 分支推送到远程的 master 分支
// HEAD 节点仍然是 C0
如果要将本地分支推送到远程仓库的非同名分支,那么 <place>
参数要写成 <source>:<destination>
的形式:
$ git push origin <source>:<destination>
$ git push origin foo^:master
// 执行成功之后, 远程的 master 分支更新到与 foo^ 节点一致
// 本地的 origin/master 分支与远程 master 一样,指向 foo^ 节点
// 而本地的 master 分支和 foo 分支没有任何变化
$ git push origin master:new_branch
// 执行成功后,远程新建了 new_branch 分支,本地出现了 origin/new_branch 分支
// 而本地的 master 和 origin/master 分支无变化
// 远程的 master 也无变化
Git Fetch Arguments
参数和 git push
一样。
$ git fetch <remote> <place>
$ git fetch <remote> <source>:<destination>
Example:
$ git fetch origin foo
下载远程 origin 的 foo 分支的代码,使本地的 origin/foo 分支与之同步,这里尤其要注意,改变的本地的 origin/foo 分支代码,并不会影响本地的 foo 分支代码。
$ git fetch origin foo
// 执行成功后,本地的 origin/foo 与远程的 foo 同步,其它分支没有变化,特别是本地的 foo 分支
$ git fecth origin foo~1:bar
// 这个方式尽量不要使用,这里只是演示效果
// 执行成功后,本地的 origin/bar 分支与远程的 foo~1 节点同步
// 本地的 origin/foo 和其它分支都不会有变化
$ git fetch
// 同步所有分支
Git Pull Arguments
$ git pull origin foo
// =
$ git fetch origin foo; git merge origin/foo
$ git pull origin bar~1:bug_fix
// =
$ git fetch origin bar~1:bug_fix; git merge origin/bug_fix
Git pull 是 fetch + merge 操作的简写,它会影响本地所在的当前分支。
Git Misc
signed-off
commit 时自动在结尾加上像这样的签名:Signed-off-by: name <email>
,使用 -s
或 --signed-off
参数,比如:
$ git commit -s
$ git commit --signed-off
$ git commit -s --amend
为了更加自动化,可以为 commit -s
设置 alias,比如:
$ git config --global alias.c 'commit -s'
然后就可以这样提交代码了:git c
个人项目可以不用加签名,一般用于团队协作,尤其是使用 squash and merge 这种 PR 协作方式,这种方式有一个缺点,如果一个 PR 是由多人合作完成的,最终 merge 时只会记录最后一次提交者作为作者。所以可以使用 signed-off 把协作者也记录在 commit message 中。(但不知道 GitHub 会不会记录到 contributor 中)