Git 在处理功能远程分支困境时变基

Git rebase while working on feature remote branch dilema

我看过很多关于 git 功能分支上 rebase 的帖子和博客,但仍然感到 100% 困惑。

场景:我在处理来自 master 的新创建的功能分支时执行了以下步骤。

% git clone <repository>
% git checkout -b feature_branch origin/master
% git add .
% git commit -m "initial commit"
% git push
% git feature_branch [-u|--set-upstream-to]
% git checkout master
% git pull
% git rebase
% git checkout feature_branch
//make some changes locally
% git add .
% git commit 
% git push

在这个阶段,我收到消息说您的远程分支位于本地存储库之前。请先拉。何时以及如何更改我的远程功能分支?

我知道这里有 master 的本地仓库和我的功能分支,当我拉取东西时,它们来自远程 origin/master。因此,当我在本地进行更改时,我假设我的本地 feature_branch 领先于远程 feature_branch。

在这种情况下,我可能在哪里采取了错误的步骤?在初始本地 feature_branch 上工作的实际步骤应该是什么,然后转到 remote 进行备份,以防在开发过程中出现问题。我想使用 rebase 来保持 git 历史记录干净。那么步骤是怎样的呢?上面我说的?

我在这里做了几个假设;我会记下来的。

以下是命令序列,以及我假设的内容和它们随后执行的操作:

% git clone <repository>

我假设这有效,然后你也 cd 进入新的克隆(你没有显示这个)。

% git checkout -b feature_branch origin/master

这会在您的新本地存储库中创建一个新的 b运行ch 名称 feature_branch,select 与 origin/master 相同的提交。名字 origin/master——refs/remotes/origin/master 的缩写——实际上不是 b运行ch 名字,而是你的 Git对他们(origin的)master的记忆,你的Git最后一次更新自己的记忆:在这种情况下,你运行 git clone.

他们可能已经或可能还没有一个名为 feature_branch 的 b运行ch!您在本地创建了您的 feature_branch。如果他们确实有 feature_branch,你的与他们的没有关系,除非碰巧他们的 master 也与他们的 master(即你的 origin/master)完全相同。

% git add .

这不会做任何事情,因为您没有修改任何文件,并且工作树中的所有文件都来自提交。

% git commit -m "initial commit"

这样也没什么用。您现有的 feature_branch 仍然与他们的 master、您的 origin/master.

持平

如果您确实在 git add .git commit 之前修改了一些文件,这会在您的 feature_branch.

上进行一次新提交

% git push

这会给你一个错误,在现代 Git 中,还有很多建议建议 git branch --set-upstream-togit push -u 或类似的东西:

% git feature_branch [-u|--set-upstream-to]

这不是命令。如果这实际上是:

 git branch --set-upstream-to origin/feature_branch

你在这里没有错误,这意味着他们已经有自己的feature_branch。这似乎是这里最有可能的问题。

(如果你实际上 运行,而是:

git push -u origin feature_branch

那将 创建 一个新名称,feature_branch,在 origin 的 Git 存储库上,基于您的 [=32] =],我们以后可能不会看到任何错误。)

% git checkout master
% git pull

做这两件事真的没有任何意义,他们可能什么也没做。如果他们确实做了一些事情,通常 是无害的——尽管这取决于谁在 origin.[=123= 上对 Git 存储库做什么。 ]

% git rebase

此时,您仍在 master 上,它又什么都不做。

% git checkout feature_branch
//make some changes locally
% git add .
% git commit 

在这里,我们终于有所作为:我们在本地添加一个新提交到名为 feature_branch 的本地 b运行ch。但是如果他们有自己的特征b运行ch,你的本地b运行ch和他们的现在已经分道扬镳了,并且:

% git push

这会抱怨你需要使用 git pull 或类似的东西来适应他们的工作。

此时您可能想要做的是:

git rebase --onto origin/feature_branch origin/master

这将复制您在 feature_branch 上所做的实际新提交,该提交从 origin/master 延伸出去。该提交的新改进副本将在 他们的 最后一次提交 他们的 feature_branch 之后,这是你的 origin/feature_branch.

作为绘图,我们可能有这样的东西:

          K   <-- feature_branch (HEAD)
         /
...--G--H   <-- master, origin/master
         \
          I--J   <-- origin/feature_branch

这张图的意思是:

  1. 您当前的 b运行ch 是 feature_branch。就是这里的特殊名称HEAD

  2. 您当前的 提交 K。任何提交的真实哈希 ID 都是唯一的,但又大又丑,人类无法处理:它看起来像 运行dom 乱码。因此,当我们对存储库中发生的事情进行简化绘图时,我们通常会通过用更简单的名称替换提交来得到很好的服务。这就是我在这里所做的。

  3. 他们的 master(你的 origin/master)和你的 master 都 select 提交 H.

  4. 他们的feature_branch——你的origin/feature_branch——select比H提交J.

请注意,我已将后来的提交绘制在右侧。每个提交都向后链接到更早的提交,因此提交 H 向后链接到某个更早的提交 G,它向后链接到我懒得尝试绘制的更早的提交。同时,提交 J 向后链接到 I,后者向后链接到 H.

这种向后链接是 Git 提交的真正意义所在。每次你进行 new 提交时,Git 打包每个文件的快照,就像它现在的形式一样(在 git add 将副本复制到Git:您可以看到和工作的副本 on/with 实际上并不 Git 中,它有不可见的秘密副本)。 Git 添加 元数据 ,例如您的姓名和电子邮件地址以及当前日期和时间。 Git 制作此元数据,使其包含 当前 提交的哈希 ID——在本例中为提交 K——并将所有内容写出以生成新提交,我们可以称之为 L:

            L
           /
          K
         /
...--G--H
         \
          I--J

现在,作为它的最后一个技巧,git commit 将该提交的原始哈希 ID 写入 current b运行ch name,给我们:

          K--L   <-- feature_branch (HEAD)
         /
...--G--H   <-- master, origin/master
         \
          I--J   <-- origin/feature_branch

我不必在此处单独绘制 L,因为我不需要绘制任何指向提交 K 的箭头,但如果你再画一个 b 运行ch name temp 指向 K,我会把 L 放在单独的行上:

            L   <-- feature_branch (HEAD)
           /
          K   <-- temp
         /
...--G--H   <-- master, origin/master
         \
          I--J   <-- origin/feature_branch

这是(打算)同一张图,但添加了名称 temp

(您可能想要 运行 git log --all --graph --decorate --onelinePretty Git branch graphs 的答案中显示的任何其他命令,以查看查看您拥有的提交集的其他方式。 )

git rebase的重点是复制提交

提交的好处在于它永远无法更改……根本。您无法更改 任何有关现有提交的内容。甚至 Git 本身也做不到。

提交的坏处在于它永远无法更改。提交 K——你在 feature_branch 开始时所做的那个:

...--G--H   <-- master, origin/master, feature_branch (HEAD)
         \
          I--J   <-- origin/feature_branch

卡在原处,向后指向现有提交 H。你可能喜欢关于它的所有 else,但你不喜欢它指向 H 的事实,因为 他们的 最新 feature_branch 提交是提交 J。所以你需要一个新的改进版本 K.

rebase 命令是关于进行这些新的和改进的提交。它可以 复制 旧提交 K 到新的和改进的 K'。然后它有你的 Git 存储库 放弃 旧的 K 并使用新的和改进的 K' 代替。

使用 rebase 的坏处是:

  • 你必须注意它复制了多少提交。有时默认值是正确的,有时则不是。

  • 因为它复制提交,任何else拥有原件的人*也必须放弃旧的新的和改进的。这需要他们的努力。

如果你没有给出旧的,没有其他人拥有旧的,所以变基是安全的。否则,您需要告诉拥有它们的人您正在这样做。

无论如何,给定:

          K   <-- feature_branch (HEAD)
         /
...--G--H   <-- master, origin/master
         \
          I--J   <-- origin/feature_branch

运行宁:

git rebase --onto origin/feature_branch origin/master

告诉你的Git:复制K,在H处停止复制,并将副本放在J之后.

如果你的提交图 我画的那个,一个更简单的 rebase 会产生同样的效果——但是你的提交图可能看起来像这样:

          K   <-- feature_branch (HEAD)
         /
...--G--H   <-- master, origin/master
      \
       I--J   <-- origin/feature_branch

在这种情况下,更简单的命令(我仍然没有显示)会尝试将 H 复制到一个新的和改进的 H',您不想这样做。

在这两种情况下, 变基之后,您新的和改进的 K' 将在 J 之后出现,例如:

          K   [abandoned]
         /
...--G--H   <-- master, origin/master
         \
          I--J   <-- origin/feature_branch
              \
               K'  <-- feature_branch (HEAD)

因为我们——和Git——find 从 b运行ch 名称开始提交,跟随箭头到提交,然后工作向后(在这些图中向左),没有人会再次看到提交 K。它仍然在那里——没有人也没有什么能改变它——但它也可能已经消失了。

(最终——默认情况下至少 30 天后——Git 会注意到没有人可以 找到 K,并真正将其剥离, 但你也不会注意到这一点。)