git学习
git 学习,笔记参考 Peter Cottle 的:https://learngitbranching.js.org/?locale=zh_CN&NODEMO=
Git branch
git 的分支非常轻量,只是指向某一个提交记录。
也就是相当于一个指针,指向一个 commit,git checkout 是切换分支。
再次进行 git commit 的时候,main 会更新,但是分支不会更新。
Git merge
将两个分支合并到一起。在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个 parent 节点。
- **创建新分支 **
bugFix - **用 **
git checkout bugFix命令切换到该分支 - 提交一次
- **用 **
git checkout main切换回main - 再提交一次
- **用 **
git merge把bugFix合并到main
必须先创建分支,再切换分支,才能提交
注意的是 git merge 的时候需要 checkout 到被合并的分支(也就是主分支)上。
Git rebase
**第二种合并分支的方法是 **git rebase。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
bugFix 分支里的工作直接移到 main 分支上。移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的。提交记录 C3 依然存在(树上那个半透明的节点),而 C3’ 是我们 Rebase 到 main 分支上的 C3 的副本。
其实就是复制了一个副本 commit,然后这个副本将会合并到主分支上。与 merge 不同的是,rebase 是站在 bugFix 分支上的。
Head
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。
HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。
**如果想看 HEAD 指向,可以通过 **cat .git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向。
- 当你使用
git checkout <commit>或者在detached HEAD状态下时,HEAD会直接指向某个特定的提交,而不是分支。 - 当你执行
git checkout <branch>时,Git 会更新HEAD,使其指向新的分支。在这个过程中,HEAD会变成该分支的最新提交。
相对引用
**通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用 **git log 来查查看提交记录的哈希值。
比较令人欣慰的是,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2 而不是上面的一长串字符。
**使用相对引用的话,你就可以从一个易于记忆的地方(比如 **bugFix 分支或 HEAD)开始计算。
相对引用非常给力,这里我介绍两个简单的用法:
**使用 **
^向上移动 1 个提交记录**使用 **
~<num>向上移动多个提交记录,如~3**操作符 **
~后面可以跟一个数字(可选,不跟数字时与^相同,向上移动一次),指定向上移动多少次。**我使用相对引用最多的就是移动分支。可以直接使用 **
-f选项让分支指向另一个提交。例如:1
git branch -f main HEAD~3
上面的命令会将 main 分支强制指向 HEAD 的第 3 级 parent 提交。
个人心得,branch 本质上是分支管理,而 checkout 是切换
撤销变更
git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
虽然在你的本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的,所以要用 git revert 本质上是创建新的提交,新提交记录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。revert 之后就可以把你的更改推送到远程仓库与别人分享。
整理提交记录
**本系列的第一个命令是 **git cherry-pick, 命令形式为:
git cherry-pick <提交号>...
如果你想将一些提交复制到当前所在的位置(HEAD)下面的话, Cherry-pick 是最直接的方式了。
**但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点, 我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法了,交互式 rebase 指的是使用带参数 **--interactive 的 rebase 命令, 简写为 -i
如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。
来看一个在开发中经常会遇到的情况:我正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。这些调试和打印语句都在它们各自的提交记录里。最后我终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜!最后就差把 bugFix 分支里的工作合并回 main 分支了。你可以选择通过 fast-forward 快速合并到 main 分支上,但这样的话 main 分支就会包含我这些调试语句了。
但是可以通过 cherry-pick 和 rebase 去进行更改,从而把调试和 print 的记录删掉。
Git tag 与 git describe
git tag 就是给 commit 一个标签。
由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来****描述离你最近的锚点(也就是标签),它就是 git describe!
**Git Describe 能帮助在提交历史中移动了多次以后找到方向;当用 **git bisect(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,可能会用到这个命令。
git describe 的语法是:
1 | git describe <ref> |
<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会使用你目前所在的位置(HEAD)。
它输出的结果是这样的:
1 | <tag>_<numCommits>_g<hash> |
tag 表示的是离 ref 最近的标签, numCommits 是表示这个 ref 与 tag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。
**当 **ref 提交记录上有某个标签时,则只输出标签名称
远程分支
**既然你已经看过 **git clone 命令了,咱们深入地看一下发生了什么。
你可能注意到的第一个事就是在我们的本地仓库多了一个名为 o/main 的分支, 这种类型的分支就叫远程分支。由于远程分支的特性导致其拥有一些特殊属性。
远程分支反映了远程仓库(在你上次和它通信时)的****状态。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.
远程分支有一个特别的属性,在你切换到远程分支时,自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。
**你可能想问这些远程分支的前面的 **o/ 是什么意思呢?好吧, 远程分支有一个命名规范 —— 它们的格式是:
<remote name>/<branch name>
**因此,如果你看到一个名为 **o/main 的分支,那么这个分支就叫 main,远程仓库的名称就是 o。
**大多数的开发人员会将它们主要的远程仓库命名为 **origin,并不是 o。这是因为当你用 git clone 某个仓库时,Git 已经帮你把远程仓库的名称设置为 origin 了
**不过 **origin 对于我们的 UI 来说太长了,因此不得不使用简写 o :) 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为 origin!
**如何从远程仓库获取数据 —— 命令如其名,它就是 **git fetch。当我们从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库。
git fetch 完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- **更新远程分支指针(如 **
o/main)
git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
git fetch 并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。
理解这一点很重要,因为许多开发人员误以为执行了 git fetch 以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。
git pull 实际上就是 fetch+merge
如果你是在一个大的合作团队中工作, 很可能是 main 被锁定了, 需要一些 Pull Request 流程来合并修改。如果你直接提交(commit)到本地 main, 然后试图推送(push)修改, 你将会收到这样类似的信息:
1 | ! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.) |
远程服务器拒绝直接推送(push)提交到 main, 因为策略配置要求 pull requests 来提交更新.
你应该按照流程,新建一个分支, 推送(push)这个分支并申请 pull request,但是你忘记并直接提交给了 main.现在你卡住并且无法推送你的更新.
