Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样,但比复制粘贴优雅许多!
Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。
Git 还保存了提交的历史记录。这也是为什么大多数提交记录的上面都有 parent 节点的原因 —— 我们会在图示中用箭头来表示这种关系。对于项目组的成员来说,维护提交历史对大家都有好处。
前言:
学习网站如下:(网站包含不常用的10% Git 操作)Learn Git Branchinghttps://learngitbranching.js.org/?locale=zh_CN
请开始你的学习吧!
基础篇
一个(小型)Git 代码库。当前有两个提交记录 —— 初始提交 C0
和其后可能包含某些有用修改的提交 C1
。
1.Git commit
git commit 创建一个新的提交记录。
操作修改了代码库,并把这些修改保存成了一个提交记录
C2
。C2
的 parent 节点是C1
, parent 节点是当前提交中变更的基础
2.Git branch
即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。
3.分支与合并 - 1️⃣Git merge
例1:
例2:
main
继承自bugFix
,Git 什么都不用做,只是简单地把bugFix
移动到main
所指向的那个提交记录。
2️⃣Git Rebase
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
⭕注意:把 bugFix 分支里的工作直接移到 main 分支上。移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的。
现在 bugFix 分支上的工作在 main 的最顶端,同时我们也得到了一个更线性的提交序列。
⭕提交记录 C3 依然存在(树上那个半透明的节点),而 C3' 是我们 Rebase 到 main 分支上的 C3 的副本。
由于
bugFix
继承自main
,所以 Git 只是简单的把main
分支的引用向前移动了一下而已。
高级篇
1.HEAD
HEAD 是一个对当前所在分支的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。
HEAD 通常情况下是指向分支名的
如果想看 HEAD 指向,可以通过
cat .git/HEAD
查看, 如果 HEAD 指向的是一个引用,还可以用git symbolic-ref HEAD
查看它的指向。
分离的 HEAD
分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。在命令执行之前的状态如下所示:
HEAD -> main -> C1
HEAD 指向 main, main 指向 C1
2.相对应用
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用
git log
来查查看提交记录的哈希值。并且哈希值在真实的 Git 世界中也会更长(译者注:基于 SHA-1,共 40 位)。例如前一关的介绍中的提交记录的哈希值可能是
fed2da64c0efc5293610bdd892f82a58e8cbc5d8
。舌头都快打结了吧...但Git 对哈希的处理很智能。只需要提供能够唯一标识提交记录的前几个字符即可。
通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。这个就很厉害了!
使用相对引用的话,你就可以从一个易于记忆的地方(比如
bugFix
分支或HEAD
)开始计算。相对引用非常给力,这里我介绍两个简单的用法:
- 使用
^
向上移动 1 个提交记录- 使用
~<num>
向上移动多个提交记录,如~3
(1)操作符 (^)
把这个符号加在引用名称的后面,表示让 Git 寻找指定提交记录的 parent 提交。
main^
相当于“main
的 parent 节点”。main^^
是main
的第二个 parent 节点
例1:
例2:
(2)“~”操作符
在提交树中向上移动很多步的话,敲那么多
^
貌似也挺烦人的,于是又引入了操作符~
。该操作符后面可以跟一个数字(可选,不跟数字时与
^
相同,向上移动一次),指定向上移动多少次。
⭕使用相对引用最多的就是移动分支。可以直接使用
-f
选项让分支指向另一个提交。例如:
git branch -f main HEAD~3
- 会将 main 分支强制指向 HEAD 的第 3 级 parent 提交。
-f
则容许将分支强制移动到那个位置
3.撤销变更 reset,revert
撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。
git reset
通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset
向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
⭕注意:
git reset
很方便,但是这种“改写历史”的方法对使用的远程分支是无效的哦!为了撤销更改并分享给别人,我们需要使用
git revert
。
4.整理提交记录 Git Cherry-pick
git cherry-pick <提交号>...
将一些提交复制到当前所在的位置(
HEAD
)下面
5.交互式的 rebase
知道所需要的提交记录(并且还知道这些提交记录的哈希值)时,用 cherry-pick
不清楚想要的提交记录的哈希值——交互式的 rebase
交互式 rebase 指的是使用带参数
--interactive
的 rebase 命令, 简写为-i。
如果在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。
当 rebase UI界面打开时, 能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成,关闭就意味着你不想要这个提交记录)- 合并提交。 遗憾的是由于某种逻辑的原因,我们的课程不支持此功能,因此我不会详细介绍这个操作。简而言之,它允许你把多个提交记录合并成一个。
6.Git Tags
可以(在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签)永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。
它们并不会随着新的提交而移动。你也不能切换到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。
远程篇
1.远程仓库-Git clone
远程仓库却有一系列强大的特性
-
首先也是最重要的的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后,即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。
-
还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
git clone
命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com)。
⭕本地仓库多了一个名为 o/main
的分支, 这种类型的分支就叫远程分支。由于远程分支的特性导致其拥有一些特殊属性:
远程分支 反映了 远程仓库(在你上次和它通信时)的状态。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.
在你切换到远程分支时,自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。
o/
是什么意思?
命名规范 :
<remote name>/<branch name>
例:
o/main
的分支:这个分支就叫 main
,远程仓库的名称就是 o
。
大多数的开发人员会将它们主要的远程仓库命名为 origin
,并不是 o
。这是因为当你用 git clone
某个仓库时,Git 已经帮你把远程仓库的名称设置为 origin
了
⭕Git 变成了分离 HEAD 状态,当添加新的提交时
o/main
也不会更新。这是因为o/main
只有在远程仓库中相应的分支更新了以后才会更新。
2.Git Fetch
Git 远程仓库相当的操作实际可以归纳为两点:
- 向远程仓库传输数据
- 从远程仓库获取数据
Git Fetch: 当我们从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库
C2
,C3
被下载到了本地仓库,同时远程分支o/main
也被更新
1️⃣那么 git fetch 做了些什么?
git fetch
完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如
o/main
)
git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
远程分支反映了远程仓库在最后一次与它通信时的状态,git fetch
就是你与远程仓库通信的方式了 。
git fetch
通常通过互联网(使用 http://
或 git://
协议) 与远程仓库通信。
2️⃣git fetch 不会做的事
git fetch
并不会改变你本地仓库的状态。它不会更新你的 main
分支,也不会修改你磁盘上的文件。
⭕许多开发人员误以为执行了 git fetch
以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。
3.Git Pull
当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:
git cherry-pick o/main
git rebase o/main
git merge o/main
- 等等
因此由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了 Git Pull 这一个专门的命令来完成这两个操作
用
fetch
下载了C3
, 然后通过git merge o/main
合并了这一提交记录。现在main
分支包含了远程仓库中的更新(在本例中远程仓库名为origin
)
4.模拟团队合作
我们引入一个自造命令 git fakeTeamwork
“假装”你的同事、朋友、合作伙伴更新了远程仓库,有可能是某个特定的分支,或是几个提交记录。
fakeTeamwork
默认操作就是在远程仓库的 main 分支上做一次提交。
还可以指定提交的分支或是数量,只需要在命令后加上它们就可以了。
通过一个命令,我们就模拟队友推送了 3 个提交记录到远程仓库的 foo 分支。
5. Git Push
git push
负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。
⭕注意 :
git push
不带任何参数时的行为与 Git 的一个名为push.default
的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是upstream
。
6.偏离的工作
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。
这种情况下,
git push
就不知道该如何操作了。如果你执行git push
,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,亦或由于你的提交已经过时而直接忽略你的提交?因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你
push
变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。
什么都没有变,因为命令失败了!
git push
失败是因为你最新提交的C3
基于远程分支中的C1
。而远程仓库中该分支已经更新到C2
了,所以 Git 拒绝了你的推送请求。
解决办法:需要做的就是使你的工作基于最新的远程分支。
最直接的方法就是通过 rebase 调整你的工作。
1️⃣push 之前做 rebase
用
git fetch
更新了本地仓库中的远程分支,然后用 rebase 将我们的工作移动到最新的提交记录下,最后再用git push
推送到远程仓库。
git pull --rebase
就是 fetch 和 rebase 的简写
2️⃣merge 替换 rebase
git merge
不会移动工作(它会创建新的合并提交),但是它会告诉 Git 你已经合并了远程仓库的所有变更。这是因为远程分支现在是你本地分支的祖先,也就是说你的提交已经包含了远程分支的所有变化
用
git fetch
更新了本地仓库中的远程分支,然后合并了新变更到我们的本地分支(为了包含远程仓库的变更),最后我们用git push
把工作推送到远程仓库
git pull 就是 fetch 和 merge 的简写,类似的 git pull --rebase 就是 fetch 和 rebase 的简写!
7.远程服务器拒绝!(Remote Rejected)