《精通Git 第2版》
一、入门
1.1 关于版本控制
本地
-> 集中式(例如SVN)
-> 分布式(例如Git)
1.2 Git基础
快速,而非差异;几乎所有操作都在本地执行;Git的完整性;Git通常只增加数据;三种状态(已提交、已修改、已暂存);三个主要区域(Git目录、工作目录和暂存区)
1.3 安装Git
安装 | 方式 |
---|---|
Liunx | yum install git-all 或apt install git-all |
Mac | 通过安装Xcode命令行工具安装 |
Windows | 官网下载 |
源码 | 参考官方文档 |
1.4 Git配置
层级 | 文件位置 | 配置参数 |
---|---|---|
系统级 | /etc/gitconfig |
--system |
用户级 | ~/.gitconfig 或~/.config/git/config |
--global |
仓库级 | Git目录.git/config |
Windows中查找$HOME目录中查找配置文件
配置用户身份
|
|
配置个人编辑器,需要输入消息的时候会用到编辑器
|
|
检查个人配置
|
|
二、Git基础
2.1 获取Git仓库
在现有目录中初始化仓库
|
|
克隆现有仓库
|
|
2.2 记录变更
查看文件状态
|
|
跟踪新文件 或 暂存已修改文件
|
|
通过编辑.gitignore
文件忽略文件变更
- 空行或则以#开始的行会被忽略
- 支持标准的glob模式,*号代表0个或多个字符,[abc]代表方括号内任意单个字符,?号匹配单个
- 以/开头的模式可用于禁止递归匹配
- 以/结尾的模式表示目录
- 以感叹号开始的模式表示取反
查看已暂存和未暂存的变更
|
|
提交变更
|
|
移除文件
|
|
移动文件
|
|
查看提交历史
|
|
具体的格式化格式参数参考:git log 使用及格式化参数详解
限制提交历史的输出范围
选项 | 描述 |
---|---|
-(n) |
只显示最新的n次提交 |
--since , --after |
只显示指定日期之后的提交 |
--util ,--before |
只输出指定日期之前的提交 |
--author |
只输出指定作者与指定字符串匹配的提交 |
--commiter |
只输出提交这与指定字符串匹配的提交 |
--grep |
只输出提交信息包含指定字符串的提交 |
-S |
只输出包含“添加或删除指定字符串”的更改的提交 |
2.3 撤销操作 reset/checkout
补充提交内容或修改提交信息(只会产生一个提交记录)
|
|
暂存区 -> 工作区
|
|
撤销对文件修改(工作区保持与仓库同步,危险操作,丢失修改内容)
|
|
想保留修改内容但要需要短时间使用仓库内容,可参考储藏(stash)或分支机制更好
2.4 远程仓库 remote
显示远程仓库
|
|
添加远程仓库:git remote add [shortname] [url]
|
|
从远程仓库获取和拉去数据: git fetch [remote-name]
会从远程仓库中获取所有本地仓库没有的数据。克隆仓库的时候会自动添加远程仓库
|
|
将数据推送到远程仓库:git push [remote-name] [branch-name]
|
|
检查远程仓库:git remote show [remote-name]
|
|
重命名远程仓库
|
|
删除远程仓库
|
|
2.5 标记 tag
特定的历史版本标记为重要版本
列举标签
|
|
补加标签
|
|
共享标签:默认情况下git push不会将标签传输到远程服务器:git push origin [tag-name]
|
|
检出标签:无法真正检出标签,可以通过标签名称创建新的分支方式:git checkout -b [branch-name] [tag-name]
|
|
2.6 Git别名
通过设置git命令别名简化命令:git config --global alias.[别名] [原始命令]
|
|
使用别名
|
|
取消设置别名
|
|
三、分支机制
3.1 分支机制
Git的分支只不过是一个指向某次提交的轻量级的可移动指针。Git的特殊指针HEAD,指向当前所在的本地分支的指针。
创建新分支
|
|
切换分支,HEAD指针指向切换的目标分支
分支切换会更改工作目录文件,如果当前状态下无法干净地完成恢复操作,就不会允许你切换分支。
|
|
分支实际上就是一个简单文件,其中只包含了该分支所指向提交的长度为40个字符的SHA-1校验和。
3.2 分支与合并操作
3.2.1 直接父节点合并操作
创建并切到换对应分支增加-b
选项
|
|
在iss53上提交c3
master分支合并iss53分支的最新修改c3
|
|
直接上游合并:如果分支是待合并分支的直接上游,Git会简化直接移动指针,git称为
fast-forward
。
删除分支
|
|
3.2.2 不同分支节点合并
合并之前初始状态
切换到master分支并合并iss53
|
|
三方合并:使用待合并分支的最新快照,以及共同祖先的提交快照,基于合并解构创建新的快照,再创建一个提交指向新建的快照。
3.2.3 基本的合并冲突处理
修改同一文件的同一部分导致无法合并时,git merge
不会创建提交,需要处理冲突后手动提交
3.3 分支管理
分支查看
|
|
如果未合并到当前分支时对分支进行
-d
选项删除将不被允许,可使用-D
强制删除
3.4 分支工作流
分支 | 说明 |
---|---|
长期分支 | 按稳定程度划分 |
主题分支 | 短期、用于实现特定功能的分支 |
3.5 远程分支
3.5.1 理解远程分支
远程分支是指向远程仓库的分支的指针,有点像书签,提示你上一次连接服远程仓库时每个分支的位置。使用{remote}/{branch}
表示,例如origin/master
orgin并非特殊名称,与master分支名称一样
git fetch
查询远程服务器分支信息,从服务器取得本地未包含的数据,最后把origin/master
指针移动到最新位置
|
|
3.5.2 推送
推送到远程分支:git push (remote) (branch)
|
|
不用每次都键入密码:基于HTTPS进行数据推送默认每次都进行身份验证,可使用
git config --global credential.helper cache
命令进行凭证缓存,暂时保存到内存几分钟。
在git fetch
之后只是拿到了远程分支的数据,如果需要在工作区拿到这些数据,需要进行git merge
|
|
3.5.3 跟踪分支
跟踪分支是与远程分支直接关联的本地分支。基于远程分支创建的本地分支会自动成为跟踪分支(tracking branch
),或则有时候也会叫做上游分支(upstream branch
)。
基于远程分支创建跟踪分支
|
|
针对已有分支设置跟踪分支,通过增加-u
或则--set-upstream-to
|
|
查看分支跟踪情况
|
|
如果已经设置好上游分支,则可以通过
@{upstream}
或@{u}
来使用他,例如git merge @{u}
来代替git merge origin/master
。
3.5.4 拉取
解读git fetch
和git pull
命令区别
命令 | 内容 | 结果 |
---|---|---|
git fetch |
拉取本地没有的远程所有最新更改数据 | 完全不会更改工作目录 |
git pull |
等同于git fetch 之后执行git merge |
会尝试将远程分支上的修改合并到本地 |
推荐显式使用
git fetch
之后执行git merge
,git pull
的机制比较迷惑。
3.5.5 删除远程分支
通过git push
的--delete
选项删除远程分支
|
|
3.6 变基
3.6.1 变基基础
变基rebase和合并merge的区别:rebase获得更加简洁的提交历史。
在master分支上git merge experiment
操作的结果
在expriment分支上git rebase master
操作的结果
3.6.2 有趣的变基操作
通过rebase部分操作到目标分支,暂不合并未经测试的内容
例如合并client分支但不包含c3部分到master分支
|
|
命令含义:切换到client分支,找出client和server的共同祖先并提交,把client分支自共同祖先以来的所有工作在master分支重现。
接下来就可以针对master分支合并client分支
不需要切换当前分支执行变基操作可使用git rebase [basebranch] [topicbranch]
,例如读取server变更,在master分支上重现。
|
|
3.6.3 变基操作的潜在危害
不要对已经存在于本地仓库之外的提交执行变基操作。1
变基:实际上是抛弃了已有的某些操作,随后创建了新的对应提交。
3.6.4 只在需要的时候执行变基操作
git pull --rebase
指定pull操作使用rebase方式,可使用配置修改默认方式
|
|
情况 | 结果 |
---|---|
变基操作用于推送前的整理和处理提交的手段,并且仅对本地还没有公开的提交进行变基 | 不会存在问题 |
对已经推送了的提交执行变基操作(别人已经基于提交作了自己的开发) | 遇到大麻烦 |
3.6.5 变基与合并的对比
总结:对本地尚未推送的更改进行变基操作,从而简化提交历史,但决不能对任何已经推送到服务器的变更进行变基操作。
四、Git服务器
4.1 协议
协议 | 样例 | 优点 | 缺点 |
---|---|---|---|
本地 | git clone file:///src/project.git |
简单易用 | 本地网络之外的无法访问 |
HTTP | git clone https://example.com/project.git |
账号+密码方式方便、协议速度快、传输效率高 | 基于HTTP的Git服务不方便搭建 |
SSH | git clone ssh://user@server/project.git |
应用广泛、安全、传输加密 | 不能实现对仓库的匿名访问 |
Git | 速度最快 | 缺少用户验证机制 |
4.2 在服务器上搭建Git
- 将裸仓库防止在服务器上
- 小型团队配置(通过SSH访问)
4.3 GitWeb
Git自带的图形化界面
|
|
4.4 GitLab
开源、功能完善、现代化的Git服务器软件。
用户 | 组 | 项目 | 钩子 |
---|
五、分布式Git
5.1 分布式工作流
集中式工作流:推送变更之前都必须合并上一个人的修改(常见SVN)
集中式管理者工作流:创建项目的公开克隆,推送自己的修改,请求主项目维护人员合并你的变更。(常见Github或GitLab)
司令官与副官工作流:多仓库工作流的一个变体,通常由涉及上百名协作人员的大型项目使用。
5.2 为项目做贡献
5.2.1 提交准则
- 不要出现与空白字符相关的错误;(使用
git diff --check
鉴别并列出可能存在的空白字符错误) - 尽量使每次提交在逻辑上都是一个独立的变更集;(使用
git add --patch
部分暂存文件) - 创建高质量的提交信息。
5.2.2 工作流推荐
- 私有小团队:每次push之前先fetch+merge
- 私有管理团队:使用分支独立管理提交内容
- 派生的公开项目:创建fork仓库并提交变更,生成拉取请求(
pull request
)
5.3 维护项目
- 使用主题分支(可为分支指定命名空间
git branch sc/ruby_client master
) - 应用来自电子邮件的补丁
- 检出远程分支(将派生仓库的分支作为远程分支在本地合并)
- 确定引入内容
- 在分支contrib中查看排除master分支的日志:
git log contrib --not master
- 查看与其他分支的对比:
git diff master
- 查看与特定版本的对比:
git diff 36c7db
- 在所处分支的最新提交和两个分支的共同祖先之间进行diff操作:三点语法
git diff master...contrib
- 在分支contrib中查看排除master分支的日志:
- 整合所贡献的工作结果:合并工作流
- 为发布版打标签:参考#2.5 标记tag
- 生成构建编号:使用
git describe
命令为提交生成一个可读性的名称2
- 准备发布:创建一个包含代码最新快照的归档文件
git archive
- 简报:快速得到上次发布或发送电子邮件之后新增内容的各类变更日志
git shortlog
六、GitHub
略
七、Git工具
7.1 选择修订版本
7.1.1 单个修订版本
短格式的SHA-1:只要能确定唯一就行SHA-1
|
|
分支引用:使用分支代表最后一次提交版本
|
|
reflog:保存在本地的HEAD和分支引用的日志
|
|
祖先引用:通过特定版本查询父查询,通过^
和~
配合数字指定
|
|
7.1.2 提交范围
双点号:那些不在同一分支上的提交
|
|
多点:通过^
或则--not
来指定不包含在其中的提交
|
|
三点号:指定仅包含在其中任意一个引用中的所有提交
|
|
7.2 交互式暂存
通过git add -i
或git add --interactive
进入交互式shell显式管理暂存区
|
|
暂存补丁:暂存文件的部分修改
- 方式1:在交互式提示符下输入
5
或则p
,针对每个文件的每个区块进行暂存选择。 - 方式2:命令行上使用
git add --patch
或git add -p
来启动部分暂存
补丁模式的其他命令:
- 重置部分文件:
reset --patch
- 检查部分文件:
checkout --patch
- 储藏部分文件:
stash save -patch
7.3 储藏与清理
7.3.1 储藏基础操作
未完成提交情况下需要切换分支,使用
git stash
储藏未提交的变更
将新的储藏内容推入栈中
|
|
查看存储在栈中的储藏
|
|
重新应用栈中的某个储藏
|
|
apply选项只会尝试应用储藏过的内容,而这些内容仍然保存在栈上
删除储藏内容
|
|
7.3.2 灵活运用储藏
- 不跟踪已经用git add命令暂存过的内容:
git stash --keep-index
- 储藏未跟踪的文件(默认不储藏未跟踪文件):
git stash --include-untracked
(或-u
)
7.3.3 从储藏中建立分支
git stash branch testchanges
:建立新分支testchanges,检出储藏区的内容并应用,成功后删除储藏区内容
7.3.4 清理工作目录
命令 | 清理结果 | 选项说明 |
---|---|---|
git clean |
删除所有未跟踪文件(默认不包含被忽略的未跟踪文件) | -x 连被忽略的一并删除;-i 交互式处理 |
git stash --all |
删除全部内容,并以储藏形式保存 |
7.4 签署工作
通过加密提交及验证提交内容,验证提交源是否可信,略
7.5 搜索
7.5.1 git grep
在任何提交树或工作目录中方便地差好啊某个字符串或正则表达式3
|
|
7.5.2 Git日志搜索
git log
通过提交信息,甚至diff内容查找特定的提交
查询常量ZLIB_BUF_MAX
最初时什么时候出现的
|
|
查询文件zlib.c中函数git_deflate_bound的所有改动
|
|
7.6 重写历史
- 修改最近一次变更:
git commit --amend
,如果需要变更提交文件先在变更到暂存区再执行命令 - 修改多个提交信息:利用变基交互式实现,
git rebase -i HEAD~3
- 重排提交:执行多个变基操作时变基排序实现
- 压缩提交:执行多个变基操作时,指定
squash
应用该变更及之前的变更并将提交信息合并 - 拆分提交:参考资料4
- 大面积修改提交记录:
git filter-branch
,参考资料5
7.7 重置揭秘
7.7.1 三棵树
三棵树
树 | 用途 |
---|---|
HEAD | 最近提交的快照,下次提交的父提交 |
索引 | 预计的下一次提交的快照 |
工作目录 | 沙盒 |
7.7.2 reset
reset参数简介
参数 | 移动HEAD | 更新索引 | 更新工作目录 |
---|---|---|---|
--soft |
✓ | ||
--mixed 或缺省 |
✓ | ✓ | |
--hard |
✓ | ✓ | ✓ |
|
|
--hard
是reset命令仅有的一个危险用法,也是Git造成数据损坏的极少数情况之一。
通过reset实现压缩提交: 不清空暂存区reset到目标快照,然后提交
|
|
7.7.3 checkout
git checkout [branch]
与git reset --hard [branch]
对比
命令 | 是否影响工作区 | 更新HEAD方式 |
---|---|---|
checkout | × | 移动HEAD,指向其他分支 |
reset | ✓ | 移动HEAD指向的分支 |
利用checkout恢复文件内容,会变更暂存区并覆盖工作目录
|
|
7.8 合并的高级用法
7.8.1 冲突的基本处理
中止合并:会尝试恢复到合并之前的状态(保留了未暂存和未提交的变更)
|
|
忽略空白字符:可将空白字符不同大致的冲突视为相同
|
|
手动合并:获取冲突文件的个人版本、他人版本以及公用版本,并手动合并这个单独的文件。合并后可使用git diff
配合不同参数查看对比
|
|
查询导致合并冲突的日志:通过git log配合--merge
查看那些提交导致了冲突
|
|
组合式差异格式:在出现合并冲突后执行git diff输出格式
检出冲突内容git_checkout
--ours
和--theirs
作为选项时,仅检出目标方的内容,例如非文本文件时完全信任一方的变更
git checkout
的--conflict
选项指定文件冲突标记样式,可使用git config --global merge.conflictstyle diff3
更改全局默认冲突标记方式
--conflict 值 |
样式 |
---|---|
merge或缺省 | 仅提供自己的版本和他人版本 |
diff3 | 提供自己的版本、他人版本和公用版本的内容 |
|
|
7.8.2 撤销合并
master分支已经合并了topic分支,但需要撤销
修复引用:移动分支,使其指向你希望指向的地方
|
|
会重写历史记录,导致无法追溯
还原提交:使用revert
命令生成一个新的提交,撤销已提交的所有变更
|
|
-m 1
指明哪一个父节点是应该保留的主线,这里表示第一个父节点保留并撤销第二个父节点内容,新生成的提交和合并之前一致,但保留了提交历史
还原提交方式的撤销因为已经包含了合并分支的内容,所以再次执行合并时无法合并目标分支。可对还原执行撤销。
7.8.3 其他类型的合并
ours和theirs合并:配合-Xours
或-Xtgeirs
选项在合并时如果有冲突直接应用自己的还是三方的
|
|
7.9 rerere
rerere(reuse recorded resolution):重用记录过的解决方案。用于处理相同类型的冲突自动处理
|
|
7.10 使用Git调试
文件标注:使用blame
命令针对每一行查看最后修改信息
|
|
二分查找:通过当前错误版本与正常版本进行二分查找快速定位发生问题的提交
|
|
7.11 子模块
添加子模块,添加后仅暂存,需要push才会提交到远程仓库,子模块信息存储在.gitmodules文件中
|
|
关于子模块的相关命令:也可以进入对应的目录当成一个git仓库执行命令执行
|
|
发布子模块变更:主仓库推送但子模块未推送会存在问题,可在push时使用--recurse-submodules
选项设置为check或on-demand(尝试将子模块推送),如果子模块变更未推送就推送失败
|
|
子模块技巧之foreach命令: 在每个子模块中执行命令
|
|
子模块之分支切换问题:分支切换时子模块依然存在,需要手动处理
7.12 打包
将指定仓库打包为文件发送给别人,方便别人本地重建
|
|
7.13 replace
替换某个版本保存的数据git replace e52wgs 12g5sw
八、自定义Git
8.1 配置Git
基本
配置 | 说明 |
---|---|
core.editor | 默认文本编辑器,比如commit的提交信息输入 |
commit.template | 提交信息模板 |
core.pager | 分页输出的处理,可选more或less,也可以设置空串来关闭 |
user.signgkey | 签名 |
core.excludesfile | 全局忽略文件配置 |
help.autocorrent | 命令自动推测 |
Git的配色
配置 | 说明 |
---|---|
color.ui | 是否启用彩色终端输出 |
color.* | 针对具体项配置彩色输出,例如diff、status等 |
外部合并工具
|
|
格式化与空白字符
配置 | 说明 |
---|---|
core.autocrlf | 处理CRLF为LF,消除操作系统差异 |
core.whitespace | 配置针对空白字符的处理 |
8.2 Git属性
配置读取配置的位置,以及相关策略,略
8.3 Git钩子
在重要的事情发生时触发自定义的脚本,分为客户端钩子和服务端钩子
九、Git与其他系统
9.1 作为客户端的Git
9.1.1 Git与Subversion
使用git检出svn代码
|
|
-T trunk -b branches -t
告诉Git该仓库遵循基本分支与标签惯例
提交代码并推送到svn仓库
|
|
拉取最新的代码(如果存在冲突则不允许推送,需要先在本地合并),使用git svn rebase
因为单分支问题
|
|
在svn仓库中使用git分支
|
|
在git中使用svn命令
|
|
忽略文件
|
|
9.1.2 Git与其他
目标 | 使用工具 |
---|---|
Mercurial | 使用git-remote-hg 作为桥接实现对mercurial仓库的管理 |
Perfoce | 使用Git Funsion 作为桥接实现对Perforce仓库的管理。 |
TFS | git-tf 或git-tfs |
9.2 迁移到Git
略
十、Git内幕
10.1 底层命令和高层命令
执行git init
之后的初始.git
目录
文件(夹) | 说明 |
---|---|
description | 仅供GitWeb使用 |
config目录 | 项目特定的配置选项 |
info | 保存了一个全局性排除文件 |
hooks | 客户端或服务端钩子脚本 |
HEAD | 指向当前已检出的分支 |
index | 保存暂存区信息 |
objects | 保存个人数据库的所有内容 |
refs | 存储的指针 |
通过底层命令维护版本树、资源池和引用等内容