Git
学习linux下的超强工具链(ง •̀_•́)ง‼。
Reference materials include:
- Learn Git Branching: 这个网站把git知识点做成关卡一样,然后教你操作,还有图形界面帮你理解
- Git文档
1 基础指令
1.1 commit
git commit
:新建一次提交
Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样。Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录,条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。Git 还保存了提交的历史记录。这也是为什么大多数提交记录的上面都有 parent 节点的原因 —— 对于项目组的成员来说,维护提交历史对大家都有好处。
1.2 branch
git branch <branchName>
:创建一个到名为 branchName 的分支,指向当前的提交记录
早建分支!多用分支!
即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。分支其实就相当于在说:“我想基于这个提交以及它所有的 parent 提交进行新的工作。”
1.3 checkout
git checkout <branchName>
:切换到指定分支上git checkout -b <your-branch-name>
:创建一个新的分支同时切换到新创建的分支
!注意,在 Git 2.23 版本中,引入了一个名为git switch
的新命令,最终会取代git checkout
,因为 checkout 作为单个命令有点超载(它承载了很多独立的功能)。【更多关于新命令git switch
的内容】
1.4 merge and rebase
git merge
:将两个分支合并到一起的方法①git merge bugFix
:把 bugFix 合并到 main 里,main 现在会指向了一个拥有两个 parent 节点的提交记录,这意味着 main 包含了对代码库的所有修改git checkout bugFix; git merge main
: 因为 main 继承自 bugFix,Git 什么都不用做,只是简单地把 bugFix 移动到 main 所指向的那个提交记录。
这种情况发生在“新建一个分支,在其上开发某个新功能,开发完成后再合并回主线”。在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个 parent 节点。翻译成自然语言相当于:“我要把这两个 parent 节点本身及它们所有的祖先都包含进来。”
git rebase
:将两个分支合并到一起的方法②
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。Rebase 的优势就是可以创造更线性的提交历史,如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
2 高级指令(Git的一些特性)
2.1 HEAD
HEAD指的就是 .git/HEAD
文件。HEAD
是当前分支引用的指针,它总是指向某次commit,默认是上一次的commit。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。如果想看 HEAD 指向,可以通过 cat .git/HEAD
查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD
查看它的指向。
2.2 log
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,可以使用 git log
来查查看提交记录的哈希值。并且哈希值在真实的 Git 世界中也会更长(基于 SHA-1,共 40 位,比如:fed2da64c0efc5293610bdd892f82a58e8cbc5d8
)。不过 Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2
而不是上面的一长串字符。
2.3 分支树上的移动和撤销
除此之外,Git 还引入了相对引用。你可以从一个易于记忆的地方(比如 bugFix
分支或 HEAD
)开始计算。
- 使用操作符
^
向上移动 1 个提交记录- 把这个符号加在引用名称的后面,表示让 Git 寻找指定提交记录的 parent 提交。比如
main^
相当于“main
的 parent 节点”;main^^
是main
的第二个 parent 节点。git checkout main^
- 或者直接使用
HEAD^
往上移动:git checkout HEAD^
- 把这个符号加在引用名称的后面,表示让 Git 寻找指定提交记录的 parent 提交。比如
- 使用 操作符
~
向上移动多个提交记录,如git checkout HEAD~4
- 强制修改分支位置
- 直接使用
-f
选项让main分支强制指向HEAD的第三级parent提交。例如:git branch -f main HEAD~3
- 直接使用
2.4 撤销变更
主要有两种方法用来撤销变更 C1 -> C2
① git reset HEAD^
:通过把分支记录回退几个提交记录来实现撤销改动,你可以将这想象成“改写历史”;但在本地分支中使用这种方法对大家一起使用的远程分支无效。
② git revert HEAD^
:在撤销的提交记录后面提交新纪录,因为新提交记录 C2'
引入的更改看作是撤销 C2
这个提交的。也就是说 C2'
的状态与 C1
是相同的。revert 之后就可以重新提交。
3 移动提交记录(自由修改提交树)
Git剩余的 10% 的内容在处理复杂的工作流时非常重要——“整理提交记录”。比如开发人员有时会希望把这个提交放到这里, 那个提交放到刚才那个提交的后面。
3.1 git cherry-pick
当你知道你所需要的提交记录,并且还知道这些提交记录的哈希值时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。
git cherry-pick <提交号>...
如果你想将一些提交复制到当前所在的位置(HEAD
)下面的话, cherry-pick 是最直接的方式。
3.2 交互式的 rebase
但是如果你不清楚你想要的提交记录的哈希值,希望从一系列的提交记录中找到想要的记录。那我们可以利用交互式的 rebase:使用带参数 --interactive
的 rebase 命令, 简写为 -i
。比如git rebase -i HEAD~4
如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。在实际使用时,所谓的 UI 窗口一般会在文本编辑器(如 Vim)中打开一个文件。
当 rebase UI界面打开时, 你能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成,关闭就意味着你不想要这个提交记录) - 合并提交
4 Git 技术、技巧与贴士大集合
4.1 只取一个提交记录:本地栈式提交
在解决某个特别棘手的 Bug时,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。这些调试和打印语句都在它们各自的提交记录里。此时你选择通过 fast-forward 快速合并到 main
分支上,但这样的话 main
分支就会包含代码中的调试语句。实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样,我们可以使用
git rebase -i
git cherry-pick
来达到目的。
4.2 提交的技巧#1
接下来这种情况也是很常见的:你之前在 newImage
分支上进行了一次提交,然后又基于它创建了 caption
分支,然后又提交了一次。此时你想对某个以前的提交记录进行一些小小的调整,尽管那个提交记录并不是最新的了。我们可以通过下面的方法来克服困难:
- 先用
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前 - 然后用
git commit --amend
来进行一些小修改 - 接着再用
git rebase -i
来将他们调回原来的顺序 - 最后我们把 main 移到修改的最前端(用你自己喜欢的方法)
4.3 提交的技巧#2
4.4 git tag
4.5 git describe
5 高级话题
5.1 多次Rebase
5.2 两次parent节点
5.3 纠缠不清的分支
6 远程
6.1 Push & Pull —— Git 远程仓库
远程仓库并不复杂, 在如今的云计算盛行的世界你可以把远程仓库作为你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录。
6.1.1 git clone
直到现在, 教程都聚焦于本地仓库的操作(branch、merge、rebase 等等)。但我们现在需要学习远程仓库的操作 —— 我们需要一个配置这种环境的命令, 它就是 git clone
。 从技术上来讲,git clone
命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com)。
6.1.2 远程分支
执行 git clone
命令后,我们的本地仓库多了一个名为 o/main
的分支, 这种类型的分支就叫远程分支。
- 为什么有
o/
?- 这是由于远程分支的命名规范:
<remote name>/<branch name>
- 因此,如果你看到一个名为
o/main
的分支,那么这个分支就叫main
,远程仓库的名称就是o
。大多数的开发人员会将它们主要的远程仓库命名为origin
,并不是o
。这是因为当你用git clone
某个仓库时,Git 已经帮你把远程仓库的名称设置为origin
了。不过origin
对于我们的 UI 来说太长了,因此不得不使用简写o
:) 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为origin
!
- 这是由于远程分支的命名规范:
说了这么多,让我们看看实例。
6.1.3 git fetch(从远程仓库获取数据)
Git 远程仓库的操作实际可以归纳为两点:向远程仓库传输数据以及从远程仓库获取数据。既然我们能与远程仓库同步,那么就可以分享任何能被 Git 管理的更新(因此可以分享代码、文件、想法、情书等等)。从远程仓库获取数据的指令 —— git fetch
。
1.git fetch
完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如 o/main
):git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
远程分支反映了远程仓库在你最后一次与它通信时的状态,git fetch
就是你与远程仓库通信的方式了!git fetch
通常通过互联网(使用 http://
或 git://
协议) 与远程仓库通信。
2.git fetch
不会做的事
git fetch
并不会改变你本地仓库的状态。它不会更新你的 main
分支,也不会修改你磁盘上的文件。所以, 你可以将 git fetch
的理解为单纯的下载操作。
6.1.4 git pull
既然我们已经知道了如何用 git fetch
获取远程的数据, 现在我们学习如何将这些变化更新到我们的工作当中。以往的一些命令也可以实现:
git cherry-pick o/main
git rebase o/main
git merge o/main
等等
实际上,由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的
git pull
。相当于git fetch; git merge o/main
=git pull
。我们用fetch
下载了C3
, 然后通过git merge o/main
合并了这一提交记录。现在我们的main
分支包含了远程仓库中的更新(在本例中远程仓库名为origin
)。