1

ProGit-读书简记

 3 years ago
source link: http://lanbing510.info/2016/12/07/ProGit.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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仓库三种工作状态(有时还有远程仓库),关系见下图。

git.jpg

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命令。下图是其常用选项:

gitlog.png

一些例子如下:

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

限制输出长度的选项有:

gitlog-restrict.png

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对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。

commit-and-tree.png

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 常见的分布式工作流程有:

① 集中式工作流

centralized_workflow.png

② 集成管理者工作流

integration-manager.png

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

benevolent-dictator.png

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来做一次演习,看看要做什么
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只移动分支,不更新暂存(索引)和工作目录;

reset-soft.png

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

reset-mixed.png

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

reset-hard.png

④ 通过路径来重置。

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 自身来 指向另一个分支。结合下图会更清晰的理解:

reset-checkout.png

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

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

git-reset-checkout.png

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 目录来开启它,但是设置选项更干净并且可以应用到全局。

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] 阮一峰的网络日志


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK