2024.06.06 2024.06.06\ 2024.06.06
Resources
强推:Pro Git - Book (git-scm.com).中文版.
强烈推荐网址:https://learngitbranching.js.org/?locale=zh_CN.
LearnGit Game:
基础(Git 主要命令)
Git Commit(提交)
提交记录保存的是你的目录下所有文件的快照。
提交记录尽可能地轻量,因此将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。
保存了提交的历史记录。这也是为什么大多数提交记录的上面都有 parent 节点。
Git Branch(分支)
只是简单地指向某个提交记录 —— 仅此而已。
因为即使创建再多的分支也不会造成储存或内存上的开销,
并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。
使用分支其实就相当于在说:“我想基于这个提交以及它所有的 parent 提交进行新的工作。”Git Switch(切换分支)
在 Git 2.23 版本中,引入了一个名为
git switch
的新命令,最终会取代git checkout
,因为checkout
作为单个命令有点超载(它承载了很多独立的功能)。创建:
git branch <name>
。创建同时切换分支:
git checkout -b <your-branch-name>
。Git Merge(合并)
在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个 parent 节点。翻译成自然语言相当于:“我要把这两个 parent 节点本身及它们所有的祖先都包含进来。”
切换分支:用
git checkout bugFix
命令切换到该分支。【*
标识当前分支】合并:
git merge <name>
。【示例里为什么还要合并回去?,保证两个分支都为最新的同步241227即答】Git Rebase(合并)
取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。合并至:
git rebase <main>
。若初始在bugFix
上。【也可以 rabase 两个名称】
流程:创建并转到分支 bugFix,同时 main 有新提交,然后在 bugFix 上rebase
到 main。
高级(Git 的超棒特性)
分离 Head
一种状态。在Git树上移动。如果想看 HEAD 指向,可以通过
cat .git/HEAD
查看。HEAD 通常情况下是指向分支名的(如 bugFix)。
在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。
执行命令:
git checkout c1
.【从指向 main 到指向 c1】相对引用 (^与~)
引用提交记录
如C1
的方式。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用
git log
来查看提交记录的哈希值。
比较令人欣慰的是,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2
而不是上面的一长串字符。
正如我前面所说,通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。这个就很厉害了!相对引用非常给力,这里我介绍两个简单的用法:
- 使用
^
向上移动 1 个提交记录【main^^
相当于 main 的爷爷节点,^2
是 parent2】- 使用
~<num>
向上移动n
个提交记录,如~3
【默认为1】强制移动分支:
git branch -f a b
移动 a 到 b。撤销变更
和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。
两种方法:一是
git reset
(远程无效,仅自己),还有就是git revert
。
git reset
通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset
向上移动分支,原来指向的提交记录就跟从来没有提交过一样。(译者注:在 reset 后,
C2
所做的变更还在,但是处于未加入暂存区状态。)
新提交记录C2'
引入了更改 —— 这些更改刚好是用来撤销C2
这个提交的。也就是说C2'
的状态与C1
是相同的。流程:
git reset HEAD^
,checkout pushed
、revert pushed
。
移动提交记录(自由修改提交树)
要讨论的这个话题是“整理提交记录” —— 开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”, 而接下来就讲的就是它的实现方式,非常清晰、灵活,还很生动。
看起来挺复杂, 其实是个很简单的概念。
git cherry-pick【抓取任何】
抓过来放到当前分支下:
git cherry-pick c2 c4
知道这些提交记录的哈希值 如 c2 时, 用 cherry-pick 再好不过。【最简单】
交互式的 rebase -i
若 hash 值未知。
交互式 rebase 指的是使用带参数
--interactive
的 rebase 命令, 简写为-i
。
Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。当 rebase UI界面打开时, 你能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成,关闭就意味着你不想要这个提交记录)- 合并提交。
流程:
git rebase -i HEAD~4
杂项(Git 技术、技巧与贴士大集合)
本地栈式提交
流程:
checkout main, cherry-pick c4
或rebase -i HEAD~3, rebase bugFix main
提交的技巧
- 先用
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前- 然后用
git commit --amend
来进行一些小修改【修改最前端的记录】- 接着再用
git rebase -i
来将他们调回原来的顺序最后我们把 main 移到修改的最前端(用你自己喜欢的方法),就大功告成啦!技巧1流程:
rebase -i HEAD~2, commit --amend, rebase -i HEAD~2, rebase HEAD main
技巧2流程:
git checkout main, cherry-pick c2, commit --amend, cherry-pick c3
Git Tags
有没有什么可以永远指向某个提交记录的标识呢,比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,有没有比分支更好的可以永远指向这些提交的方法呢?
就像是提交树上的一个锚点,标识了某个特定的位置。
把 v1 标签打在 c2 上:
git tag v1 c2
流程:
checkout c2, tag v1 c2, tag v0 c1
Git Describe
一个命令用来描述离你最近的锚点(也就是标签),它就是
git describe <ref>
!它输出的结果是这样的:
<tag>_<numCommits>_g<hash>
tag
表示的是离ref
最近的标签,numCommits
是表示这个ref
与tag
相差有多少个提交记录,hash
表示当前提交记录哈希值的前几位。当
ref
提交记录上有某个标签时,则只输出标签名称。流程:
rebase main bugFix, rebase bugFix side, side another, another main
a远程仓库
远程仓库并不复杂, 在如今的云计算盛行的世界很容易把远程仓库想象成一个富有魔力的东西, 但实际上它们只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录
话虽如此, 远程仓库却有一系列强大的特性
- 首先也是最重要的的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后,即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。
- 还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)
现在用网站来对远程仓库进行可视化操作变得越发流行了(像 GitHub), 但远程仓库永远是这些工具的顶梁柱, 因此理解其概念非常的重要!
Git Clone
技术上来讲,
git clone
命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com)远程分支 o/master
本地仓库多了一个名为
o/main
的分支,即为远程分支。【
<remote name>/<branch name>
】这个分支就叫main
,远程仓库的名称就是o
(其实默认为origin
)远程分支反映了远程仓库(在你最后一次和它通信时)的状态。
远程分支有一个特别的属性,在你切换到远程分支时,自动进入分离 HEAD 状态。
Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。Git Fetch
从远程仓库获取数据。同时更新远程分支。【可以理解为单纯的下载操作】
git fetch
通常通过互联网(使用http://
或git://
协议) 与远程仓库通信。
git fetch
并不会改变你本地仓库的状态。它不会更新你的main
分支,也不会修改你磁盘上的文件。理解这一点很重要,因为许多开发人员误以为执行了
git fetch
以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。Git Pull
可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:
git cherry-pick o/main
,rebase
、merge
。由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的
git pull
。
git pull
就是 git fetch 和 git merge 的缩写!
Version Control(Git)
Version control systems (VCSs) are tools used to track changes to source code (or other collections of files and folders).
追踪源代码改动.
现代的版本控制系统可以帮助您轻松地(甚至自动地)回答以下问题:
- Who wrote this module?
- 这个文件的这一行是什么时候被编辑的?By whom? Why was it edited?
- 最近的1000个版本中,when/why 导致了单元测试失败?
Because Git’s interface is a leaky abstraction, learning Git top-down (starting with its interface / command-line interface) can lead to a lot of confusion.
抽象泄露问题,导致自顶向下很难学。
而优雅的底层设计则非常容易被人理解。自底向上。
目的:搞懂 Git 的数据模型。
Git’s data model
Snapshots
In Git terminology, a
file
is called a “blob
”, and it’s just a bunch of bytes. Adirectory
is called a “tree
”, and it maps names to blobs or trees.【文件称 blob,目录称 tree】A
snapshot
is the top-level tree that is being tracked.【快照是追踪最顶层的目录】
Modeling history: relating snapshots
历史记录建模。For many reasons, Git doesn’t use a simple model like this.【Git 没有采用】
Git calls these snapshots “
commit
”s.【快照 = 提交】箭头:it’s a “comes before” relation, not “comes after”。
o <-- o <-- o <-- o <---- o ^ / \ v --- o <-- o
the
o
s correspond to individual commits (snapshots).【提交不可更改】
Data model, as pseudocode
It’s a clean, simple model of history. 历史模型的伪代码。
// 文件就是一组数据 a file is a bunch of bytes type blob = array<byte> // 一个包含文件和目录的目录 a directory contains named files and directories type tree = map<string, tree | blob> // 每个提交都包含一个父辈,元数据和顶层树 a commit has parents, metadata, and the top-level tree type commit = struct { parent: array<commit> author: string message: string snapshot: tree }
24.07.16 − − E n d . 24.07.16--End. 24.07.16−−End.