Git指令学习笔记

本文详细介绍了Git的基础操作,如提交、分支创建与切换、合并方法(merge和rebase)、高级概念如HEAD、标签、远程仓库操作(fetch、pull、push)以及解决远程库提交历史偏离和拉取请求的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习网站及文本来源:https://learngitbranching.js.org/?locale=zh_CN


基础篇

提交

有两个提交记录——初始提交 C0 和其后可能包含某些有用修改的提交 C1。
指令:git commit
在这里插入图片描述

创建、切换分支

指令:git branch newbranch,这个指令只是创建一个分支,本身还在主分支上。需要用git checkout newbranch切换分支。
可用一条指令git checkout -b newbranch来代替以上两条指令。
在这里插入图片描述

合并分支

方法一:git merge

指令:git merge someonebranch,合并某一个分支到所在分支(默认主分支)
在这里插入图片描述

方法二:git rebase

Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。

Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。

指令:git rebase main,当前分支重新设立起点到main分支上。

注意,提交记录 C3 依然存在(树上那个半透明的节点),而 C3’ 是我们 Rebase 到 main 分支上的 C3 的副本。
在这里插入图片描述
现在唯一的问题就是 main 还没有更新,下面咱们就来更新它吧……

git checkout main
git rebase bugFix

在这里插入图片描述

高级篇

HEAD

HEAD 是一个对当前所在分支的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。

HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。

下面咱们通过实际操作看一下。我们将会观察提交前后 HEAD 的位置。

执行命令:

git checkout c1
git checkout main
git commit
git checkout c2

看到了吗? HEAD 指向了 master,随着提交向前移动。

分离的HEAD

分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。在命令执行之前的状态如下所示:

HEAD -> master -> C1

HEAD 指向 master, master 指向 C1

执行命令:git checkout c1

现在变成了

HEAD -> C1

相对引用(^和~)

通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用git log来查查看提交记录的哈希值。

并且哈希值在真实的 Git 世界中也会更长(译者注:基于 SHA-1,共 40 位)。例如前一关的介绍中的提交记录的哈希值可能是fed2da64c0efc5293610bdd892f82a58e8cbc5d8。舌头都快打结了吧…

比较令人欣慰的是,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2 而不是上面的一长串字符。

正如我前面所说,通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。这个就很厉害了!

相对引用非常给力,这里我介绍两个简单的用法:

  • 使用 ^ 向上移动 1 个提交记录
  • 使用 ~ 向上移动多个提交记录,如 ~3

git checkout main^

在这里插入图片描述
现在咱们切换到 main 的父节点

我们也可以将 HEAD 作为相对引用的参照。下面咱们就用 HEAD在提交树中向上移动几次。

git checkout C3
git checkout HEAD^
git checkout HEAD^
git checkout HEAD^

等同于

git checkout c3
git checkout HEAD~3

在这里插入图片描述

你现在是相对引用的专家了,现在用它来做点实际事——强制修改分支位置

我使用相对引用最多的就是移动分支。可以直接使用 -f 选项让分支指向另一个提交。

例如:git branch -f main HEAD~3

上面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。

在这里插入图片描述

这就对了! 相对引用为我们提供了一种简洁的引用提交记录 C1 的方式, 而 -f 则容许我们将分支强制移动到那个位置。

撤销变更

在 Git 里撤销变更的方法很多。和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。我们这个应用主要关注的是后者。

主要有两种方法用来撤销变更 —— git reset,还有就是 git revert。接下来咱们逐个进行讲解。

1、git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

让我们来看看演示:
执行命令:git reset HEAD~1

在这里插入图片描述

漂亮! Git 把 master 分支移回到 C1;现在我们的本地代码库根本就不知道有 C2 这个提交了。

2、git revert

为了撤销更改并分享给别人,我们需要使用 git revert。来看演示:
执行命令:git revert HEAD

在这里插入图片描述

奇怪!在我们要撤销的提交记录后面居然多了一个新提交!这是因为新提交记录 C2’ 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2’ 的状态与 C1 是相同的。

revert 之后就可以把你的更改推送到远程仓库与别人分享啦。

移动提交变更

git cherry-pick

如果你想将一些提交复制到当前所在的位置(HEAD)下面的话, Cherry-pick 是最直接的方式了。我个人非常喜欢 cherry-pick,因为它特别简单。

咱们还是通过例子来看一下!

这里有一个仓库, 我们想将 side 分支上的工作复制到 main 分支,你立刻想到了之前学过的 rebase 了吧?但是咱们还是看看 cherry-pick 有什么本领吧。

执行:git cherry-pick c2 c4

在这里插入图片描述
这就是了!我们只需要提交记录 C2 和 C4,所以 Git 就将被它们抓过来放到当前分支下了。 就是这么简单!

交互式rebase

当你知道你所需要的提交记录(并且还知道这些提交记录的哈希值)时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。

但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点, 我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法了。

交互式 rebase 指的是使用带参数 --interactive 的 rebase 命令, 简写为 -i

如果你在命令后增加了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。

当 rebase UI界面打开时, 你能做3件事:

  • 调整提交记录的顺序(通过鼠标拖放来完成)
  • 删除你不想要的提交(通过切换 pick 的状态来完成,关闭就意味着你不想要这个提交记录)
  • 合并提交。

接下来咱们看个实例

执行:git rebase -i HEAD~4

在这里插入图片描述
在这里插入图片描述

标签Git Tag

相信通过前面课程的学习你已经发现了:分支很容易被人为移动,并且当有新的提交时,它也会移动。分支很容易被改变,大部分分支还只是临时的,并且还一直在变。

你可能会问了:有没有什么可以永远指向某个提交记录的标识呢,比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,有没有比分支更好的可以永远指向这些提交的方法呢?

当然有了!Git 的 tag 就是干这个用的啊,它们可以(在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签)永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。

更难得的是,它们并不会随着新的提交而移动。你也不能切换到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。

执行:git tag v1 c1

在这里插入图片描述

我们将这个标签命名为 v1,并且明确地让它指向提交记录 C1,如果你不指定提交记录,Git 会用 HEAD 所指向的位置。

由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是git describe

Git Describe 能帮你在提交历史中移动了多次以后找到方向;当你用git bisect(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,或者是当你坐在你那刚刚度假回来的同事的电脑前时, 可能会用到这个命令。

在这里插入图片描述
在这里插入图片描述

远程仓库

克隆

直到现在,教程都聚焦于本地仓库的操作(branch、merge、rebase 等等)。但我们现在需要学习远程仓库的操作 —— 我们需要一个配置这种环境的命令, 它就是 git clone。 从技术上来讲,git clone 命令在真实的环境下的作用是在本地创建一个远程仓库的拷贝(比如从 github.com)。

在这里插入图片描述

就是它了! 现在我们有了一个自己项目的远程仓库。除了远程仓库使用虚线之外,它们几乎没有什么差别 。

远程分支

既然你已经看过 git clone 命令了,咱们深入地看一下发生了什么。

你可能注意到的第一个事就是在我们的本地仓库多了一个名为 o/main 的分支, 这种类型的分支就叫远程分支。由于远程分支的特性导致其拥有一些特殊属性。

远程分支反映了远程仓库(在你上次和它通信时)的状态。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.

远程分支有一个特别的属性,在你切换到远程分支时,自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。

切换到远程分支并做一次提交
git checkout o/main
git commit

在这里插入图片描述

正如你所见,Git 变成了分离 HEAD 状态,当添加新的提交时 o/main 也不会更新。这是因为 o/main 只有在远程仓库中相应的分支更新了以后才会更新。

为什么有 o/?
你可能想问这些远程分支的前面的 o/ 是什么意思呢?好吧, 远程分支有一个命名规范 —— 它们的格式是:

  • remote name/branch name

因此,如果你看到一个名为 o/main 的分支,那么这个分支就叫 main,远程仓库的名称就是 o。
大多数的开发人员会将它们主要的远程仓库命名为 origin,并不是 o。这是因为当你用 git clone 某个仓库时,Git 已经帮你把远程仓库的名称设置为 origin 了
不过 origin 对于我们的 UI 来说太长了,因此不得不使用简写 o 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为 origin!

Git Fetch

Git 远程仓库相当的操作实际可以归纳为两点:向远程仓库传输数据以及从远程仓库获取数据。既然我们能与远程仓库同步,那么就可以分享任何能被 Git 管理的更新。

本节课我们将学习如何从远程仓库获取数据 —— 命令如其名,它就是 git fetch

你会看到当我们从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库。

在这里插入图片描述
执行:git fetch

在这里插入图片描述

就是这样了! C2,C3 被下载到了本地仓库,同时远程分支 o/main 也被更新,反映到了这一变化。

git fetch 做了些什么git fetch 不会做的事
git fetch 完成了仅有的但是很重要的两步:从远程仓库下载本地仓库中缺失的提交记录、更新远程分支指针(如 o/main)git fetch 并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。
git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。理解这一点很重要,因为许多开发人员误以为执行了 git fetch 以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。
如果你还记得上一节课程中我们说过的,远程分支反映了远程仓库在你最后一次与它通信时的状态,git fetch 就是你与远程仓库通信的方式了!

所以, 你可以将 git fetch 的理解为单纯的下载操作。

Git Pull

既然我们已经知道了如何用 git fetch 获取远程的数据, 现在我们学习如何将这些变化更新到我们的工作当中。

其实有很多方法的 —— 当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:

  • git cherry-pick o/main
  • git rebase o/main
  • git merge o/main
  • 等等

实际上,由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的 git pull。

多说不说,来看下面的例子:

在这里插入图片描述

我们先来看看 fetch、merge 依次执行的效果。

在这里插入图片描述

我们用 fetch 下载了 C3, 然后通过 git merge o/main 合并了这一提交记录。现在我们的 main 分支包含了远程仓库中的更新(在本例中远程仓库名为 origin)

如果使用 git pull 呢?

在这里插入图片描述

同样的结果!这清楚地说明了 git pull 就是 git fetch 和 git merge 的缩写!

Git Push

OK,我们已经学过了如何从远程仓库获取更新并合并到本地的分支当中。这非常棒……但是我如何与大家分享我的成果呢?

嗯,上传自己分享内容与下载他人的分享刚好相反,那与 git pull 相反的命令是什么呢?git push!

git push 负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦 git push 完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了!

你可以将 git push 想象成发布你成果的命令。它有许多应用技巧,稍后我们会了解到,但是咱们还是先从基础的开始吧……

注意 —— git push 不带任何参数时的行为与 Git 的一个名为 push.default 的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是 upstream。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。

这里我们准备了一些远程仓库中没有的提交记录, 咱们开始先上传吧!

在这里插入图片描述

执行:git push

在这里插入图片描述

过去了, 远程仓库接收了 C2,远程仓库中的 main 分支也被更新到指向 C2 了,我们的远程分支 (o/main) 也同样被更新了。所有的分支都同步了!

远程库提交历史的偏离

现在我们已经知道了如何从其它地方 pull 提交记录,以及如何 push 我们自己的变更。看起来似乎没什么难度,但是为何还会让人们如此困惑呢?

困难来自于远程库提交历史的偏离。在讨论这个问题的细节前,我们先来看一个例子……

假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。

这种情况下, git push 就不知道该如何操作了。如果你执行 git push,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,亦或由于你的提交已经过时而直接忽略你的提交?

因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。

在这里插入图片描述

什么都没有变,因为命令失败了!git push 失败是因为你最新提交的 C3 基于远程分支中的 C1。而远程仓库中该分支已经更新到 C2 了,所以 Git 拒绝了你的推送请求。

那该如何解决这个问题呢?很简单,你需要做的就是使你的工作基于最新的远程分支。

有许多方法做到这一点呢,不过最直接的方法就是通过 rebase 调整你的工作。咱们继续,看看怎么 rebase!

git fetch
git rebase o/main
git push

在这里插入图片描述

我们用 git fetch 更新了本地仓库中的远程分支,然后用 rebase 将我们的工作移动到最新的提交记录下,最后再用 git push 推送到远程仓库。

还有其它的方法可以在远程仓库变更了以后更新我的工作吗? 当然有,我们还可以使用 merge。

尽管 git merge 不会移动你的工作(它会创建新的合并提交),但是它会告诉 Git 你已经合并了远程仓库的所有变更。这是因为远程分支现在是你本地分支的祖先,也就是说你的提交已经包含了远程分支的所有变化。

咱们们用 merge 替换 rebase 来试一下……

在这里插入图片描述

我们用 git fetch 更新了本地仓库中的远程分支,然后合并了新变更到我们的本地分支(为了包含远程仓库的变更),最后我们用 git push 把工作推送到远程仓库。就是先pull后push

很好!但是要敲那么多命令,有没有更简单一点的?

当然 —— 前面已经介绍过 git pull 就是 fetch 和 merge 的简写,类似的 git pull --rebase 就是 fetch 和 rebase 的简写!

锁定的main,pull request拉取请求

如果你是在一个大的合作团队中工作,很可能是main被锁定了,需要一些Pull Request流程来合并修改。如果你直接提交到本地main,然后试图推送修改,你将会收到这样类似的信息:

! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)

远程服务器拒绝直接push提交到main,因为策略配置要求 pull requests 来提交更新。

你应该按照流程,新建一个分支,push这个分支并申请pull request,但是你忘记并直接提交给了main。现在你卡住并且无法推送你的更新。

解决办法:新建一个分支,推送到远程服务器。然后reset你的main分支和远程服务器保持一致,否则下次你pull并且他人的提交和你冲突的时候就会有问题。

开发过程中的分支使用原则

在这里插入图片描述
在这里插入图片描述

建立本地和远程的联系

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值