ProGit-读书简记

2016年12月07日

写在前面


Git是一个非常好的版本管理工具,最早是根据廖雪峰等人的博客进行的学习,一直只用到了一些常用的命令做简单的备份回退等。最近结合《ProGit》和《Git权威指南》进行了查漏补缺和更系统深入的学习,下面是结合ProGit一书做的读书简记。

起步


1 初次运行Git前的配置

Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。这些变量存储在三个不同的位置:

①. /etc/gitconfig 文件: 包含系统上每一个用户及他们仓库的通用配置。 如果使用带有 --system 选项的git config 时,它会从此文件读写配置变量。

②. ~/.gitconfig 或 ~/.config/git/config 文件:只针对当前用户。 可以传递 --global 选项让 Git读写此文件。

③. 当前使用仓库的 Git 目录中的 config 文件(就是 .git/config):针对该仓库。

每一个级别覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。在 Windows 系统中,Git 会查找 $HOME 目录下(一般情况下是 C:\Users\$USER)的 .gitconfig 文件。Git 同样也会寻找 /etc/gitconfig 文件,但只限于 MSys 的根目录下,即安装 Git 时所选的目标位置。

2 git config --list命令可以列出所有Git当时能找到的配置。

基础


1 Git不同于其他版本控制系统,Git是直接记录快照,而不是差异比较。

2 Git有工作目录、暂存(索引)区域、Git仓库三种工作状态(有时还有远程仓库),关系见下图。

3 用git status查看当前文件状态,git status --short或者git status -s可输出更简洁的状态。

新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。你可能注意到了 M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。

4 .gitignore文件中可以指定忽略模式,支持正则表达式。要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

5 查看已暂存和未暂存的修改。git diff比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。若要查看已暂存的将要添加到下次提交里的内容(和工作目录中当前文件的差异),可以用git diff --cached命令。(Git 1.6.1 及更高版本还允许使用 git diff --staged,效果是相同的,但更好记些。)

6 'git commit -a'可以跳过暂存区直接提交(跳过git add),但尽量避免使用。

7 移除文件。使用git rm移除。如果要移除的文件已经修改过且暂存了,必须使用-f选项强制删除。如果想要把文件从Git仓库中删除,但保留在工作目录中,使用git rm --cached file

8 重命名使用git mv file_from file_to

9 查看提交历史使用git log命令。下图是其常用选项:

一些例子如下:

git log -p -2 #显示最近两次提交每次提交的差异
git --stat    #在每次提交的下面列出额所有被修改过的文件、有多少文件被修改了以及被修改
git log --pretty=format:"%h %s" --graph #提交对象的简短哈希字串 提交说明 图显

限制输出长度的选项有:

例如:

git log  --since=2.weeks  #列出所有最近两周内的提交
git log -Sfunction_name   #找出添加或移除了某一个特定函数的引用的提交

10 远程仓库的使用。可以使用git remote --help查看详细使用说明。

git remote -v  #显示远程仓库信息;
git remote add [shortname] [url]  #添加一个新的远程Git仓库
git fetch [remote-name]  #从远程仓库中抓取,不同于git pull,并不会合并,
git push [remote-name] [branch-name]  #推送到远程仓库
git remote show [remote-name]  #查看远程仓库 
git remote rename [ori-name]  [dst-name]  #重命名
git remote rm [shortname]  #移除

11 打标签。

git tag  #列出标签
git tag -a v1.4 -m 'my version 1.4' #使用-a创建附注标签,-m指定存储在标签中的信息
git show v1.4  #查看标签信息与对应的提交信息
git tag v1.4-lw  #创建轻量标签
git tag -a v1.2 9fceb02  #后期打标签,9fceb02是之前的一个提交
git push origin v1.5  #共享标签
git push origin --tags  #一次推送多个标签
git checkout -b [branchname] [tagname]  #在特定的标签上创建一个新分支

12 Git别名。

git config --global alias.co checkout  #为checkout起别名co
git config --global alias.last 'log -1 HEAD' #git last 显示最后一次提交
git config --global alias.visual '!gitk'  #想要执行外部命令,而不是一个 Git 子命令。在命令前面加入 ! 符号

Git分支


1 Git保存数据保存的是文件的快照,进行提交操作是会提交一个对象,该对象包含一个指向暂存内容快照的指针,还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。内容快照使用的blob对象来保存。下图是个直观展示,该Git 仓库中有五个对象:三个blob对象(保存着文件快照)、一个树对象(记录着目录结构和blob对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。

2 常用命令:

git branch [branch_name]  #创建分支
git checkout [branch_name]  #切换分支
git checkout -b [branch_name]  #新建并切换分支
git branch -d hotfix  #删除hotfix分支
git merge hotfix  #合并hotfix分支,先切换到master分支。当合并有冲突后,git status查看,修改后add,重新合并。 
git branch --merged|--no-merged  #过滤这个列表中已经合并或尚未合并到当前分支的分支

3 远程分支。origin是运行git clone时默认的远程仓库名字。如果运行git clone -o lanbing510,那么默认的远程分支名字会是lanbing510/master。

git checkout --track origin/serverfix  #本地创建serverfix分支来跟踪远程仓库上的serverfix分支
git checkout -b sf origin/serverfix  #创建一个sf的本地分支来跟踪远程仓库serverfix分支
git checkout -u origin/serverfix  #设置已有分支来跟踪远程分支
git branch -vv  #查看设置是所有跟踪分支
git push origin --delete serverfix  #删除远程分支

4 变基。整个不同分支的修改有合并(merge)和变基(rebase)两种方法。merge可以保留记录,rebase使分支更清晰。使用变基友风险,谨记:不要对在你的仓库外有副本的分支执行变基。

git rebase --onto master server client  #取出 client 分支,找出处于 client 分支和 server 分支的共同祖先之后的修改,然后把它们在 master 分支上重演一遍
git rebase master serve #取出serve分支,在master分支上重演

分布式Git


1 常见的分布式工作流程有:

① 集中式工作流

② 集成管理者工作流

③ 司令官与副官工作流。典型工作流程如下:

2 向一个项目贡献

① 首先,你不会想要把空白错误(根据 git help diff 的描述,结合下面给出的图片,空白错误是指行尾的空格、Tab 制表符,和行首空格后跟 Tab 制表符的行为)提交上去。Git 提供了一个简单的方式来检查这点:在提交前,运行 git diff --check,它将会找到可能的空白错误并将它们为你列出来;

② 接下来,尝试让每一个提交成为一个逻辑上的独立变更集;

③ 最后一件要牢记的事是提交信息。有一个创建优质提交信息的习惯会使 Git 的使用与协作容易的多。一般情况下,信息应当以少于50个字符(25个汉字)的单行开始且简要地描述变更,接着是一个空白行,再接着是一个更详细的解释。

④ 一些常用命令

git log --no-merges issue54..origin/master  #要求 Git 只显示所有在后面分支(在本例中是origin/master)但不在前面分支(在本例中是 issue54)的提交的列表
git log origin/master --not issue54  #作用同上一条命令
git log refA refB --not refC  #refA 或 refB 包含的但是不被 refC 包含的提交
git log master...experiment  #三点语法,查看master 或者 experiment 中包含的但不是两者共有的提交
git log --left-right master...experiment  #还会显示出每次提交位于哪一侧
git request-pull origin/master myfork  #在派生项目中,生成拉取请求的内容
git merge --no-commit --squash featureB  #--squash 选项接受被合并的分支上的所有工作,并将其压缩至一个变更集,使仓库变成一个真正的合并发生的状态,而不会真的生成一个合并提交。这意味着你的未来的提交将会只有一个父提交,并允许你引入另一个分支的所有改动,然后在记录一个新提交前做更多的改动。同样 --no-commit 选项在默认合并过程中可以用来延迟生成合并提交
git format-patch -M origin/master  #通过邮件的公开项目,使用该命令,format-patch 命令打印出它创建的补丁文件名字。-M 开关告诉 Git 查找重命名
cat *.patch |git imap-send  #将patch通过邮箱发送出去,前提是.gitconfig中配置好了imap 

3 维护项目

一些常用命令:

git apply  xx.patch  #应用使用 git diff 或 Unix diff 命令(不推荐)创建的补丁
git apply --check xx.patch  #应用补丁之前检查是否可以顺利应用
git am xx.patch #应用使用format-patch生成的补丁
git diff master...contrib  #三点语法,显示自当前特性分支与 master 分支的共同祖先起,该分支中的工作。
git archive master --prefix='project/' | gzip > `git describe master`.tar.gz  #归档
git archive master --prefix='project/' --format=zip > `git describe master`.zip  #归档
git shortlog --no-merges master --not v1.0.1  #制作提交简报

Git进阶


1 储藏与清理

下面是储藏和清理的一些常用命令:

git stash  #储藏工作目录,准备干净的合并
git stash list  #查看储藏的东西
git stash apply  #应用储藏的东西
git stash apply stash@{2}  #应用更旧的储藏
git stash drop stash@{0}  #移动指定的储藏
git stash pop  #应用并丢弃储藏
git stash --include-untracked 或 -u  #同时储藏未跟踪文件
git stash --keep-index  #不储藏任何你通过git add命令已暂存的东西
git stash branch [branch_name]  #从储藏创建一个分支
git clean  #清理工作目录
git stash --all  #移除每一样东西并存放在栈中
git clean -x  #做一次完全干净的构建而移除所有由构建生成的.o 文件
git clean -f -d  #强制移除工作目录中所有未追踪的文件以及空的子目录,可以使用git clean -d -n来做一次演习,看看要做什么

2 搜索

git grep -n gmtime_r  #寻找gmtime_r并输出所找到的匹配行行号
git grep -count gmtime_r  #使 Git 输出概述的信息,仅仅包括哪些文件包含匹配以及每个文件包含了多少个匹配
git grep -p gmtime_r *.c  #-p选项看匹配的行是属于哪一个方法或者函数,该命令是查看哪个函数调用了gmtime_r
git grep --break --heading -n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0  #查看在旧版本 1.8.0 的 Git 代码库中定义了常量名包含 “LINK” 或者 “BUF_MAX” 这两个字符串所在的行,--break 和 --heading 选项来使输出更加容易阅读
git log -L :git_deflate_bound:zlib.c  #查看 zlib.c 文件中`git_deflate_bound` 函数的每一次变更

3 重写历史。

git commit --amend修正最后一次提交。

② 修改多个提交信息。通过交互式变基工具,可以在任何想要修改的提交后停止,然后修改信息、添加文件或做任何想做的事情。可以通过给 git rebase 增加 -i选项来交互式地运行变基。例如:

git rebase -i HEAD~3  #修改最近三次提交信息,运行命令后会进入交互式界面,按照提示进行即可。

git rebase -i 同样可以进行排序提交,压缩提交,拆分提交等操作,用到时可以详细参考ProGit的相应部分。

④ 如果想要通过脚本的方式改写大量提交的话可以使用 filter-branch 例如,全局修改你的邮箱地址或从每一个提交中移除一个文件。例:

git filter-branch --tree-filter 'rm -f passwords.txt' HEAD  #从整个提交历史中移除一个叫做 passwords.txt 的文件

4 重置操作(reset)。

git reset --soft只移动分支,不更新暂存(索引)和工作目录;

git reset --mixed 不指定--soft和--hard时的默认选项,更新暂存,不更新工作目录;

git reset --hard 更新暂存和工作目录;

④ 通过路径来重置。

git reset file.txt #等价于git reset --mixed HEAD file.txt ,用HEAD分支的file.txt更新暂存(索引)区域
git reset eb43bf file.txt #用eb43bf提交的file.txt更新索引

5 检出(checkout)。

① 不带路径的情况。运行 git checkout [branch] 与运行 git reset --hard [branch] 非常相似,它会更新所有三棵树使其看起来像 [branch],不过有两点重要的区别:

首先不同于 reset --hard,checkout 对工作目录是安全的,它会通过检查来确保不会将已更改的文件吹走;

第二个重要的不同点在于如何更新 HEAD。reset 会移动 HEAD 分支的指向,而 checkout 只会移动 HEAD 自身来 指向另一个分支。结合下图会更清晰的理解:

② 带路径。运行 checkout 的另一种方式就是指定一个文件路径,这会像 reset 一样不会移动 HEAD。它就像 git reset [branch] file那样用该次提交中的那个文件来更新索引,但是它也会覆盖工作目录中对应的文件。它就像是git reset --hard [branch] file(如果 reset 允许你这样运行的话)- 这样对工作目录并不安全,它也不会移动 HEAD。

6 重置和检出的总结速查。下面的速查表列出了命令对树的影响。"HEAD" 一列中的 "REF" 表示该命令移动了 HEAD 指向的分支引用,而"HEAD" 则表示只移动了 HEAD 自身。特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命令 之前请考虑一下。

7 高级合并

① 合并出现冲突后,Git索引会储藏了所有版本(共同的版本stage1,我们的版本stage2,他们的版本stage3)。

② 合并出现冲突后,可以打开冲突的文件,根据指示修改。

③ 一些常用命令:

git merge --abort  #尝试恢复到你运行合并前的状态。但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好
git merge -Xignore-all-space 或 -Xignore-space-change [branch_name]  #忽略任意数量的已有空白的修改 或 忽略所有空白修改
git show :1:hello.rb > hello.common.rb  #导出共同版本
git show :2:hello.rb > hello.ours.rb  #导出我们的版本
git merge-file -p hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb  #手动合并冲突修改后的文件,合并完毕后git clean来清理手动合并创建但不再使用的文件
git diff --ours  #合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么
git diff --base  #查看文件在两边是如何改动的
git checkout --conflict=diff3 hello.rb  #重新检出文件并替换合并冲突标记
git config --global merge.conflictstyle diff3  #通过设置 merge.conflictstyle 选项为 diff3 来做为以后合并冲突的默认选项
git log --oneline --left-right HEAD...MERGE_HEAD  #得到此次合并中包含的每一个分支的所有独立提交的列表
git log --oneline --left-right --merge  #只显示任何一边接触了合并冲突文件的提交
git revert -m 1 HEAD  #-m 1 标记指出 “mainline” 需要被保留下来的父结点
git merge -Xours [branch_name]  #选择特定的一边Ours并忽略另外一边Theirs而不是让你手动合并冲突
git merge-file --ours  #合并单个文件

8 rerere

git rerere功能是一个隐藏的功能。正如它的名字 "reuse recorded resolution" 所指,它允许你让 Git 记住解决一个块冲突的方法,这样在下一次看到相同冲突时,Git 可以为你自动地解决它。为了启用 rerere 功能,仅仅需要运行这个配置选项:git config --global rerere.enabled true也通过在特定的仓库中创建 .git/rr-cache 目录来开启它,但是设置选项更干净并且可以应用到全局。

9 打包

git bundle create repo.bundle HEAD master  #打包,如果你在打包时没有包含 HEAD 引用,你还需要在命令后指定一个 -b master 或者其他被引入的分支,否则Git 不知道应该检出哪一个分支
git clone repo.bundle repo  #解包
git bundle create commits.bundle master  #获取在我们的 master 分支而不在原始仓库中的提交

10 文件标注

git blame -L 12,22 simplegit.rb  #文件标注,展示文件中每一行最后一次修改的提交 -L 选项来限制输出范围在第12至22行
git blame -C -L 141,153 GITPackUpload.m  #在 git blame 后面加上一个-C,Git 会分析你正在标注的文件,并且尝试找出文件中从别的地方复制过来的代码片段的原始出处

小注


本文对个人用户基本用不到的钩子、合并树、凭证管理、搭建Git仓库、底层命令等内容没做总结,用到时可以直接阅读原文。二八原则,掌握80%常用的内容就可以熟练运用Git,有需要的时候再继续深入。最后,最好的学习方法就是实践实践再实践,一定要多思考多实践。

参考文献


[1] ProGit Version 2. Scott Chacon and Ben Straub.

[2] Git权威指南. 蒋鑫.

[3] 阮一峰的网络日志


版权声明:本文为博主原创文章,转载请注明出处 本文总阅读量    次