Git 是一个免费的开源分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有内容。Git 的使用要比 Svn 复杂很多,Git 命令也很多,这里做个简单记录,方便查阅。
你要相信,机会不来是因为你还没准备好。
git 基础
- 文件状态: 未跟踪(untracked)、已提交(committed)、已修改(modified)、已暂存(staged)
- 工作区域: 远程仓库、工作目录、暂存区域
- 在工作目录修改文件 –> committed ot modified
- 将文件快照放入暂存区域 –> modified to staged
- 提交更新 –> staged to commited
配置
git 配置有三个作用域,其应用优先级:local > global > system。
--system
配置文件为/etc/gitconfig
下,所有用户的配置--global
配置文件为~/.gitconfig
或~/.config/git/config
下,当前用户的配置--local
配置文件为proj/.git/config
,当前仓库的配置
查看配置
- 查看所有配置项
git config [--system|--global|--local] --list
- 查看特定配置项
git config [--system|--global|--local] user.name
如果不指定作用域,则按优先级获取,即在 local
取不到该项的值再从 global
取,还是取不到再从 system
取,如果 system
中也没配置,则返回空。
设置配置
1 | git config --global user.name xxx |
如果不指定作用域,则默认为 --local
。
删除配置项
1 | git config --global --unset user.name |
如果不指定作用域,则默认为 --local
。
短命令
有些命令经常使用,如果可以使用更短的命令代替会方便很多,git alias 提供了给命令设置别名的功能,打开系统配置文件 /etc/gitconfig
,添加下面的配置项。
1 | [alias] |
忽略文件
在 .gitignore
中指定哪些文件不需要跟踪。空目录默认不会跟踪。
- *.[oa] 所有以
.o
或.a
结尾的文件 - *~ 所有以
~
结尾的文件 - build/ 所有
build
目录及目录下所有文件 - /build/ 根目录下的
build
目录及目录下所有文件 - build 所有
build
文件 - /build 根目录下的
build
文件 - doc/*.txt
doc
根目录下的所有文本文件 - doc/*/.txt
doc
目录下的所有文本文件
查看帮助
1 | git help [verb] |
git 仓库
本地仓库
本地仓库就是本地的一个文件夹,在这个文件夹下生成一个 .git
目录,这个文件夹就变成了一个本地 git 仓库。创建一个本地仓库有两种方式,第一种是在该目录下执行 git init
即可,这种方式创建创建的本地仓库不与任何远程仓库关联。
1 | cd xxxdir |
第二种是使用 git clone
直接拉取一个远程仓库到本地,会自动根据远程仓库名在本地创建一个同名的文件夹,且文件夹下有 .git
目录,这种方式创建的本地仓库默认会与拉取的远程仓库关联。
1 | git clone git@github.com:xxx/xxx.git |
远程仓库
远程仓库是指放在远程服务器上的 respository
,通常是放在使用 Git 远程版本控制的软件源代码托管平台上,比如 Github, Gitlab, Gitee 等等网站。远程仓库需要我们到托管平台上创建。
关联远程仓库
git remote add <remote-name> <url>
通过 git remote add
命令可以将本地仓库关联到远程仓库,一个本地仓库可关联多个远程仓库。remote-name
是我们给远程仓库取的名字,之后对该远程仓库的所有操作都使用这个名字,url
则是远程仓库的地址。
使用
get clone
拉取远程仓库到本地,默认就会关联这个远程仓库,使用的是默认名字origin
。
取消关联远程仓库
git remote remove/rm <name>
查看关联的远程仓库
git remote
git remote -v/--verbose
显示 urlgit remote show <name>
显示某一远程仓库的详细信息
重命名远程仓库
git remote rename <old-name> <new-name>
git 分支
分支简介
- git 分支本质上仅仅是指向提交对象的可变指针,提交对象提交时分支会自动向前移动;
git init
会默认创建一个名为master
的分支,这个分支和普通分支没什么区别;HEAD
才是特别的分支,它指向当前所在的本地分支,可看作当前分支的别名。切换分支时HEAD
会指向另一个分支,同时把本地目录的文件替换为另一个分支指向的文件。
分支创建
git branch <name>
分支切换
git checkout <name>
git checkout -b <name>
创建新分支并切换到该分支,等价于git branch <name>
git checkout <name>
checkout
即查看的意思,如果参数带的是分支名,即表示查看这个分支的内容——切换到该分支;如果参数带的是文件名,即查看远程上该文件的内容,即检出该文件到本地。
查看分支
- 查看所有分支:
git branch
- 查看所有分支最后一次提交:
git branch -v
- 查看所有已合并分支:
git branch --merged
- 查看所有未合并分支:
git branch --no-merged
- 查看所有分支包含远程分支和已删除分支:
git branch -a
删除分支
git branch -d <name>
只能删除那些已经合并的分支git branch -D <name>
能删除已合并或未合并的分支git push <remote> --delete <branch>
删除远程分支
重命名分支
git branch -m <old-name> <new-name>
如果<new-name>
分支已存在则重命名不成功git branch -M <old-name> <new-name>
强制重命名,即使分支<new-name>
已存在
分支合并
git merge <other>
将 other
分支合并到当前分支,合并后 other
分支的状态就是 merged
,可放心删除。合并时,先切换到要合并到的分支,再 merge 要合并的分支,比如要将 hotfix
分支合并到 master
分支,则
1 | git checkout master |
合并的时候有两种情况,一是要合并到的分支 master
是要合并的分支 hotfix
的直接祖先,如下图所示,这种情况 merge 的时候会出现 Fast-forward
(快进) 的提示,这种情况的合并很简单,不需要任何额外的处理,因为不会有冲突,hotfix
本身就是从 master
分化出来的,内容只会比 master
新,不存在两个分支同时修改的内容。
第二种情况是要合并到的分支不是要合并的分支的直接祖先,如下图一所示,把分支 iss53
合并到分支 master
,这两个分支在某个时间点出现了分叉,无直接关系,只有共同的祖先 C2
快照。这种情况 Git 会自动拿 C2, C4, C5
三个快照进行合并生成一个新的快照 C6
,然后让 master
分支指向 C6
快照,如下图二所示。
merge
操作是 Git 自动完成的,通过快照对比修改本地文件,如果没冲突则自动将修改过的文件加入到暂存区,文件状态变为 staged
;有冲突的文件则会为 conflict
,需要我们手动处理冲突,处理完成后标记为 modified
,然后手动添加到暂存区变为 staged
。merge 之后只是我们本地的文件做了修改,所以还需要执行 git commit
将修改提交到远程。
关联远程分支
本地分支在两种情况下会自动关联远程分支,一是使用 git checkout <branch>
从远程检出分支到本地,检出的本地分支自动与远程分支关联,二是在本地新创建的分支,默认是没有与远程分支关联的,当有 push 新的 commit 到远程的时候,就会将本地分支关联到远程的同名分支,git push <remote> <branch>
会将 <branch>
关联到 <remote>/<branch>
,如果远程分支不存在,则会创建一个。
有时候需要修改本地分支关联的远程分支,比如修改了远程仓库的地址,从 remote1
变为了 remote2
,原来的 master
分支关联的是 remote1/master
,需要将其变更为 remote2/master
才能正常进行 push 或 pull 操作。
手动关联远程分支:git branch --set-upstream-to=origin/<branch> <branch>
1 | git remote remove origin |
同步远程分支
git remote show origin
可以查看远程的所有分支以及本地分支和远程的关联,如下图所示。
可以看到远程的分支现在有两种,一种是 tracked(已跟踪)
,即有用的分支,另一种是 stale(陈旧)
,即远程已删除的分支,但本地还保留着,可使用 prune(精剪)
命令来删除。
git remote prune origin
在本地删除陈旧的远程分支。
分支变基
分支采摘
git merge
可以把一个分支的所有最新改动合并到另一个分支,而如果想只采摘一个分支的几个提交到另一个分支,则可以使用 git cherry-pick
。
git cherry-pick <commit>
把某个 commit 应用到当前分支,产生一个新的 commit;git cherry-pick <branch>
把某个 branch 的最新一次 commit 应用到当前分支,产生一个新的 commit;git cherry-pick <commit1> <commit2>
依次采摘两个 commit;git cherry-pick <commit1>..<commit2>
采摘 (commit1, commit2] 之间的所有 commit,不包括 commit1;git cherry-pick <commit1>^..<commit2>
采摘 [commit1, commit2] 之间的所有 commit,包括 commit1;
查看分支树
使用 log
使用 git log --graph
可以通过查看 log 的方式来查看分支的关系树。
git log –graph –decorate –oneline –simplify-by-decoration –all
-–oneline
每条 commit 一行显示,一般这是必须的,否则整个树会十分庞大,不方便查看-–decorate
显示每条 commit 的引用,如分支、tag 等-–simplify-by-decoration
只显示被分支或 tag 引用的 commit-–all
显示所有的分支
使用 gitk
gitk –-simplify-by-decoration –-all
也可以使用 gitk 工具来查看 log 信息,可以更直观的看分支的关系树。gitk 默认在 Git bash 安装的时候就会安装,在 git cmd 输入 gitk
即可打开 gitk 工具。
常用命令
检查文件状态
git status
git status -s/--short
-s
只列出所有差异文件及其状态,状态以符号的形式显示在文件前面,??
表示未加入暂存区的新文件,A
表示已加入暂存区的新文件,M
表示已修改,D
表示已删除。符号为红色表示未添加到暂存区,绿色表示已添加到暂存区。其中 ??
只会显示为红色,A
只会显示为绿色。
跟踪文件
git add <file>
添加文件到暂存区
- 跟踪新文件,从
untracked
到staged
,??
toA
(红变绿); - 跟踪修改的旧文件,从
modified
到staged
,M
toM
(红变绿); - 跟踪删除的旧文件,从
modified
到staged
,D
toD
(红变绿); - 当然也可以跟踪未修改的旧文件,只是没意义而已,跟踪之后也不会有什么变化;
- 每次文件修改后,提交更新前都要重新跟踪,因为提交更新时是暂存区的版本,而不是本地目录的版本。
取消跟踪
git reset HEAD <file>
将文件从暂存区移除,保留文件状态,如果是新文件则从已跟踪重新变成未跟踪,旧文件已修改仍是已修改,已删除仍是已删除。
HEAD
可以省略,但如果是是取消跟踪已删除文件则不能省略,因为在本地找不到该文件,也可以用--
来代替HEAD
。
删除文件
删除文件 git rm
及下面介绍的移动文件 git mv
是对 Linux 命令的“扩展”,如果文件未跟踪,即在版本中不存在,是本地新增的文件,则用 Linux 命令即可,而且只能用 Linux 命令,用 git 命令会报找不到文件的错误。
1 | $ touch test |
git rm <file>
先使用 Linux 命令删除本地文件,再跟踪删除的文件,即添加到暂存区,相当于下面几条命令。
1 | $ rm package.json |
- 文件未跟踪
rm <file>
- 文件未修改未删除
git rm <file>
- 文件已修改
git rm -f <file>
强制删除,无论文件原来是否在暂存区,都从本地删除并重新加入暂存区
- 文件已修改
git rm --cached <file>
从版本中移除,保留在本地目录成为未跟踪文件
- 文件已删除,未加入暂存区
git rm <file>
加入暂存区,相当于只执行
git rm
的第二步操作 - 文件已删除,已加入暂存区,无法再进行任何删除操作
移动文件
git mv <old-name> <new-name>
文件重命名并将修改加入暂存区,其实就是将旧文件删除,然后建一个新文件,再将两个文件添加到暂存区,等价于下面几条命令。
1 | $ mv <oldfile> <newfile> |
同 git rm
一样,如果是版本中不存在的新文件,不能使用 git mv
来重命名(也没意义),直接使用 Linux 命令 mv
即可。
推送到远程仓库
git push <remote> <branch>
当前 <remote>-<branch>
没人抢先推送时才能推送成功,否则必须先拉取远程分支内容合并后再推送。
从远程仓库拉取
git fetch <remote>
拉取远程仓库的所有分支的引用,不会进行合并,必须手动合并分支;git pull <remote> <branch>
拉取某个远程分支的内容并合并到本地分支。
查看 diff
- 查看未暂存文件的修改信息
git diff
- 查看已暂存文件的修改信息
git diff -cached
orgit diff -staged
- 查看距离某次 commit 的修改信息
git diff <commit>
- 查看单个文件的修改信息
git diff -- <file>
- 查看单个距离某次 commit 的修改信息
git diff <commit> -- <file>
- 如果要查看某个文件的修改信息,直接带上文件名即可
git diff <file>
,不带文件名则查看所有有更改文件的 diff; - 只列出有更改的行及上下文,删除的行在最前面加上
-
并显示为红色,新增的行在最前面加上+
并显示为绿色;
提交更新
- 在编辑器中编辑提交信息
git commit
- 直接输入提交信息
git commit -m <statement>
- 跳过暂存区域直接提交
git commit -a
查看提交日志
git log
-p
显示每次提交的内容差异-3
只显示最新的 3 次提交--stat
显示简略的统计信息--pretty
指定显示的格式--author=[xxx]
按提交作者筛选
默认查看所有提交日志,如果要查看某个文件的提交日志,带上文件名即可 git log -- <file>
。
git shortlog
列出所有提交记录,只显示 commit 说明,按作者分类;git shortlog --author=[xxx]
只列出某位作者或某几位作者的日志,这个命令比较常用,比如查看自己的提交日志。
储藏文件
有时候在本地做了一些修改后,想切换到其它分支或者从远程拉取最新代码,而本地的修改暂时又不想提交,则可以将本地修改储藏起来,储藏之后本地又恢复成干净状态(即无修改状态),这时候 pull 代码或 checkout 分支都不会有冲突出现。之后需要的时候再从储藏中将文件恢复到本地,恢复的时候如果修改的文件有更新,则可能会出现冲突,所以一般是想更新远程较新文件且这些文件在本地没修改,本地修改的是远程无更新文件,才会使用储藏操作。
- 储藏本地修改文件
git stash
- 查看储藏列表
git stash list
- 应用储藏
git stash apply
如果要恢复指定的储藏,带上名字即可,比如
git stash aaply stash@{1}
,默认从最近一次的储藏进行恢复,等价于git stash apply stash@{0}
。 - 取消储藏
git stash show -p | git apply -R
git 没有提供
stash unapply
的命令,但是可以通过取消该储藏的补丁达到同样的效果,如果要取消指定的储藏,带上名字即可,git stash show -p stash@{1} | git apply -R
。 - 移除储藏
git stash drop
stash apply
只是应用储藏,应用之后储藏的内容还在栈中,需要手动移除,drop
默认移除最近一次的储藏,git stash drop stash@{1}
移除上上次的储藏。 - 应用并移除储藏
git stash pop
stash pop
应用储藏的同时将其从栈中移除,等价于stash apply
+stash drop
,git stash pop stash@{1}
可应用并移除上上次的储藏。 - 移除所有储藏
git stash clear
撤销操作
撤销暂存
git reset HEAD <file>
撤销修改
git checkout -- <file>
将文件修改为上次提交的样子或者刚放入工作目录时的样子,直接拿版本快照中的文件来覆盖本地,因此很危险。
撤销提交
git reset [--soft/--mixed/--hard] HEAD^
恢复到当前版本状态,--soft
只撤销 commit,文件仍在暂存区中,--mixed
撤销 commit 和 add,文件恢复到工作目录,--hard
最粗暴,不仅撤销 commit 和 add,还将本地修改直接撤销了,即完全恢复到上次提交的状态,要慎用。默认参数是 --mixed
,也是最常用的参数。
重新提交
git commit --amend
第二次提交会覆盖上一次的提交,如果第二次提交没有新的修改,就会使用第一次的快照,只覆盖提交信息。
数据恢复
有时候会丢失一些 commit 信息,比如强制删除了一个没 merge 的分支,则这个分支上的 commit 就丢失了,或者 hard-reset 到某个版本,则这个版本之后的 commit 就丢失了。这些丢失的数据是可以找回的,因为这些操作并不是真正的删除,只是从 git 仓库中移除了,数据还保留在 .git
目录下;git 会不定时的执行 git gc
命令,将不被引用且存在数月的垃圾删除,所以如果是误操作丢失了数据,不要慌,只要不是太久都可以找回的。
找回 commit 其实很简单,比方在这个 commit 上创建一个新分支就把它恢复过来了,关键是得知道我们弄丢的是哪个 commit,git log
只会显示当前的所有 commit,不包含已删除的,这时候可以用 git reflog
,可以查看所有分支的所有操作记录,包括已删除的 commit 和 reset 的操作,reflog 只显示简单的 commit 信息,这时可以再执行下 git log -g
来输出 reflog 的正常日志,这样就能看到我们要恢复的是哪个 commit。
1 | git reflog |
reflog 数据保存在 .git/logs
目录下,如果这个目录被删了,则 git reflog
就帮不了我们了,这时候可以使用 git fsck
命令,该命令会检查仓库的数据完整性和一致性,如果指定选项 --full
,该命令显示所有未被其它对象引用的所有对象。通过 git fsck --full
也能找到被删除的 commit 信息。
有时候会误删 stash,比如想应用并移除储藏的时候,脑子一热将 git stash pop
打成了 git stash drop
,这时储藏没应用,却已经从栈中移除了。但别慌,只是从栈中移除了,stash 的数据还是在的,只是无法在 git stash list
中列出来,即没有 stash 名字而已,但我们仍可以通过 commit 信息来恢复。其实在移除储藏的时候,从移除的结果就可以看到这条储藏的 commit 信息,如果马上意识到自己误操作,这时想恢复十分简单,直接使用这条 commit 信息进行恢复即可。
1 | $ git stash list |
这是在刚移除 stash 的时候恢复,这时我们知道 commit 信息,如果隔了一段时间不知道 commit 信息了,也可以通过 git fsck
来列出所有 commit,但列出来的 commit 是无序的,只能通过 git show [commit]
来一条条查看 commit 的 diff,找到需要的 commit 之后再进行恢复,相对会麻烦挺多。
规范
commit 规范
标题:#[type] message
一般使用 #[type]
来表示这次提交内容的类型,比如 #feature
代码功能开发,#bugfix
代表 bug 修复,或者在一个博客系统中, #newpost
代表新文章,#newpage
代表新页面,#newdraft
代表新草稿,#amend
代表文章修改,#feature
代表其它修改。
默认
#
为 commit 信息的注释符号,通过配置git config core.commentchar *
将其设置为*
或其它符号。
单一职责,一次 commit 只处理一件事,方便查看和回退,多提交几次 commit 并没有不好的。
分支命名规范
master
主干分支,一般不在这个分支上直接改东西;feature/xxx
功能开发分支;bugfix/xxx
bug 修复分支;release/xxx
发行分支。