繁体   English   中英

Git交互式rebase HEAD不指向最后一次提交

[英]Git interactive rebase HEAD not point at last commit

我刚刚使用以下命令运行了一个交互式变基:

git checkout editing
git rebase -i main
# do interactive rebasing, specificically squashing
git checkout main

在这一点上,我希望所有(压缩的)提交都被复制/重播到 main 上,并且 HEAD 指向最后一次编辑的提交,现在在 main 中。 但似乎 HEAD 仍然指向之前对 main 的最后一次提交。 注意:编辑基于对 main 的倒数第二次提交,010141c(更新说明)不在编辑中。

我对 git 没有信心,我不确定我是否做错了,或者我是否只需要让 HEAD 以某种方式指向最近的提交。

这是git log --graph --oneline --all --decorate的输出:

* 24b9ac9 (editing) Only show single selected stop
* 1ccc747 Scroll to stop with button in stop_time
* 68ce7c4 Scroll item into view when clicked
* 0f76a85 Add list of stops
* 50fbec3 Add stop_time fields
* 6be7f1a Add agency fields
* 6c5227a Add trip fields
* f71a471 Add dropdowns
* 5d1ae61 Fix formatting issue
* d7d00c7 Add Newtype radiogroup
* 6f0ed7a Start adding updatable fields to routes
* 160c0f8 Fix text fields not updating
* 175b763 Add new trip using data method
* 29d32da Delete and then restore trips
* 010141c (HEAD -> main, origin/main, origin/HEAD) Update notes
| * f1f127d (origin/performance_limit_routes, performance_limit_routes) Limit routes
| | *   535f628 (refs/stash) WIP on performance_testing: a06ab13 Improve performance by not impl Lens and Data for MyStopTime
| | |\  
| | | * 5aea13d index on performance_testing: a06ab13 Improve performance by not impl Lens and Data for MyStopTime
| | |/  
| | * a06ab13 (origin/performance_testing, performance_testing) Improve performance by not impl Lens and Data for MyStopTime
| |/  
| * d9d9f89 Fix performance by reducing number of stop_times
| * 9c12ca4 Backup
| * 2c23d01 (origin/editing) Only show single selected stop
| * 3313e88 Scroll to stop with button in stop_time
| * c1a329f Scroll item into view when clicked
| * 712eb18 Add list of stops
| * d3333e6 Add stop_time fields
| * 116ef9f Add agency fields
| * 9eb0f43 Add trip fields
| * 8d1453e Add dropdowns
| * 3f4a0ca Fix formatting issue
| * 61613d4 Add Newtype radiogroup
| * 8ca937b Start adding updatable fields to routes
| * 02fca9b Backup
| * f2d756a Fix text fields not updating
| * cf6907e Backup
| * f9c4115 Add new trip using data method
| * 6ab663e WIP
| * caac18e WIP
| * 7a70e4a Delete and then restore trips
| * 2457455 WIP
|/  
* ff0cef7 Replace list expander checkbox with custom widget

TL;博士

到目前为止一切都很好。 现在只需运行git merge --ff-only editing (甚至只是git merge editing )。 但是您可能想要对performance_limit_routesperformance_testing做一些事情。

我认为您在这里唯一的错误是您对git rebase的心理模型中的一个小错误。 git rebase的含义可以很简单地说:

  • 你有一些提交的集合。 你喜欢这些提交中的大部分内容,或者其中一些提交的一些内容。 但是对于提交的集合,您至少有一点喜欢。

  • 一旦你完成了任何 Git 提交,实际上是不可能更改的。 但是:当我们谈论分支B上的提交时(对于某些B ),我们的意思是: Git 通过使用名称B找到一个特定的提交,然后从提交到提交向后工作。 (稍后我将对此进行更多说明,但请记住,每个分支名称仅记录单个提交的原始哈希 ID。)

这意味着,如果我们要将原始提交复制到一些新的和改进的提交中,那么让 Git强制名称B指向最后一个副本,而不是最后一个原始提交,那为什么,任何不是注意提交的原始哈希 ID 将看到新副本,而不是原始副本。

简而言之,这就是git rebase所做的。 它需要一些原始提交,您主要喜欢它们但不喜欢它们的某些内容,并将它们复制到新的和改进的提交中。 然后它使我们用来查找提交的分支名称找到新的和改进的提交,而不是旧的(和糟糕的?)提​​交。

因此,您的这两个命令的序列:

  1. git checkout editing
  2. git rebase -i main

方法:

  • 列出git log main..editing将显示的所有提交。

  • 切换到 main 顶部的提交(此处为010141c )。

  • 复制每个提交,和/或压缩一些提交,按照“pick”命令的指示,当您使用交互式变基命令表更新它们时。

  • 使分支名称editing选择最后复制的提交。

你剩下一堆提交(14,如果我算对的话),它们在分支editing中“领先于” main 签出main会使您通过名称main提交010141c ,因此您的git log的输出在这里非常有意义。

要使这些新的和改进的提交成为分支main的一部分,您现在只需要运行:

git merge --ff-only editing

--ff-only选项在这里不是绝对必要的,我只是喜欢自己使用它。 (我经常使用它,所以我创建了一个别名git mff ,它运行git merge --ff-only 。我的目标是防止自己通过拼写错误或其他任何错误。)

您看到的“重复项”在那里,因为您已经告诉git log显示所有内容,并且它确实做到了:包括performance_limit_routesperformance_testing以及refs/stash 这些是指的提交。 最初的 19 次(如果我再次计算正确的话)提交仍然存在,你和 Git仍然可以找到它们

这是一个问题吗? 也许,也许不是。 如果没有,你不需要做任何事情。 如果是这样,你需要做点什么。 可能就像“删除这些分支,这样没人会再看到它们”一样简单(并使用git stash drop删除您的存储)。 是否要保留这些提交,如果是,是否要复制(或变基)其中任何一个,都取决于您。 请参阅下面的“血腥细节”。

关于这一切如何运作的血腥细节

要使用 Git,我们需要知道——就像我们的骨子里一样,甚至不去想它——Git 就是关于提交的,并且每个提交都有编号(带有一个看起来随机的哈希 ID,比如6be7f1a010141c等等,除了这些是缩写的——完整的要长得多)并存储所有文件的快照和一些元数据。

所有文件的快照在概念上非常简单:它就像一个 tar 或 WinRAR 或 zip 存档。 但是,它以特殊的仅 Git 格式存储,文件内容被压缩并(重要地)去重复 这意味着只有您所做的第一次提交实际上必须存储所有文件:除非第二次提交替换了所有文件,否则第二次提交会重新使用第一次提交的一些文件,并且这些文件实际上是通过重复数据删除共享的诡计。 例如,您可能有 1000 次提交,但只有三个实际版本的README.md ,在这种情况下,存储库中只有三个版本的README.md ,在所有提交之间共享。

元数据是按提交存储的(即从不共享1 ),存储诸如提交人的姓名和电子邮件地址之类的内容。 这是您在较长的git log输出中看到的内容。 它们存储提交消息,其中第一行是“主题行”,您也可以在git log输出中看到:例如, Add agency fields (例如来自提交6be7f1a )。

但是——这对于 Git 自身的操作至关重要——每个提交的元数据存储了一个先前提交哈希 ID 的列表 大多数提交在此列表中仅包含一个哈希 ID。 合并提交,例如535f628 (refs/stash) WIP on performance_testing: a06ab13 Improve performance by not impl Lens and Data for MyStopTime在列表中具有多个哈希 ID:此合并提交是您的git stash结果。 2

让我们忽略合并提交并专注于普通提交。 每个这样的提交都存储了前一个提交的哈希 ID,我们(和 Git)将其称为提交的级。 我们也说提交指向它的父节点。 因此,如果我们使用大写字母来代表真正的提交——例如,使用H表示“哈希”——我们可以像这样绘制提交

            <-H

这里H是一个提交,例如24b9ac9 (editing) Only show single selected stop 它有这个箭头(指针)伸出它,指向下一个提交,我们称之为G (或者更确切地说是1ccc747 Scroll to stop with button in stop_time )。 现在让我们画G

        <-G <-H

当然, G有一个箭头指向另一个更早的提交F (或68ce7c4 Scroll item into view when clicked ):

... <-F <-G <-H

这将永远持续下去,或者更确切地说,直到我们到达您的存储库中的第一个提交,它有一个的父级列表,因为没有更早的提交。

分支名称,在这种情况下是editing ,只是指向链中的最后一个提交,如下所示:

...--F--G--H   <-- editing

请注意,我们可以有多个名称,和/或多个名称指向任何一个特定的提交:

...--B   <-- main (HEAD), origin/main
      \
       C--...--H   <-- editing

这里的名字mainorigin/main指向我为绘图目的调用B的提交。 C点提交回B H最终返回到C ,然后返回到B ,然后从那里继续返回,依此类推。

当我们以这种方式绘制的东西看起来像这样时,我们会得到类似分支的东西

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

Git 的git log --all --decorate --oneline --graph正在绘制这些相同类型的图,但垂直绘制它们,每行一次提交:

* L (branch2) subject for commit L
* K  subject for commit K
| * J (branch1) subject for commit J
| * I subject for commit I
|/
* H subject for commit H
* G subject for commit G

等等。 带括号的名称向您显示哪些名称指向该行上的提交。 | 和其他行用作连接器以跳过其他行。 星号*表示“这是一个提交”。

存在其他 Git 图形绘制软件,它们的功能略有不同。 查看Pretty Git 分支图了解查看图表的多种方式。 该图非常重要,因为它是Git 查找提交的方式 我们可以给 Git 一个原始的哈希 ID(或缩写),或者一个指向哈希 ID 的名称。 这就是git log的起点。 然后git log将使用存储在每个提交中的哈希 ID 来查找先前的提交——或者,对于合并提交,使用所有父级来查找所有先前的提交。


1元数据永远不会共享,因为每个提交都是唯一的 Git 在此处粘贴日期和时间戳以提供帮助,此外,除了日期和时间戳之外,两个提交在所有内容中都相同的情况极为罕见。 如上所述,元数据还包括父提交的哈希 ID,以及保存快照的树的哈希 ID。 树不必是唯一的,但父哈希将是唯一的。

这里有一个奇怪的情况:如果您有两个指向同一个提交的分支名称,并且您使用计算机本身对两个分支同时执行相同更新(或缺少更新)的快速git commit确切的第二个,您将获得一个共享的新提交,并且两个分支名称都将指向同一个新提交。 所以“从不共享”有点夸大其词,但唯一的共享案例很难命中,只会导致两个先前共享现有提交的分支名称也共享新提交。

(实际上,我在编写和运行脚本时曾经发生过这种情况,直到我推理出来之前我都感到很惊讶。)

2 git stash命令通过提交来工作。 这些提交不在任何分支上,这应该对您隐藏它们,但是正如您的git log输出所示,它们并没有很好地隐藏。

运行git stash至少进行两次提交,其中两个或三个提交中的一个具有合并提交的形式 但是,该合并提交中的内容根本不像常规的 Git 合并,并且将隐藏提交提供给常规 Git 命令可能会导致震惊、恐惧和/或眼泪:除非您是 Git 技工,否则您必须使用git stash以正确处理 stash 提交。

这是我极力劝阻用户不要使用git stash的几个原因之一:高压内部工作并没有充分禁止普通用户使用,他们可能会被击中。


有时我们想要一些提交,而不是所有提交

如果你运行:

git log editing

Git 将从使用名称editing和向后工作找到的提交开始。 Git 将永远运行下去,或者更确切地说,直到它用完提交(到达第一个提交)。 这不会显示从名称开始找到的提交editing 这包括从performance_limit_routes开始的提交。 那是因为这是其中一种分支情况,例如:

          I--J   <-- sort-of-like-editing
         /
...--G--H
         \
          K--L   <-- sort-of-like-performance_limit_routes

如果我们从L开始并向后工作,我们看不到IJ 如果我们从J开始并向后工作,我们看不到KL 当然,可以告诉 Git 的git log以所有名称开头——这就是--all的意思——然后我们会看到所有提交,但很多时候,这不是我们想要的,我们只使用一个名称并查看可以通过该名称找到的所有提交。

但有时我们不希望每次提交。 我们想要所有提交的某个选定子集 如果我们有:

          I--J   <-- sort-of-like-editing
         /
...--G--H   <-- main
         \
          K--L   <-- sort-of-like-performance_limit_routes

我们可以运行:

git log main..sort-of-like-editing

然后我们会看到 *only 提交IJ 这里的两点..语法意味着当您从名称main找到提交时停止

这可能会有点令人困惑,因为:

git log <hash-of-L>..sort-of-like-editing

会做同样的事情: git log在到达可以找到的提交时停止,而不仅仅是“两个点左侧的提交”。 L开始并向后工作,我们去LKHG等,所以我们仍然停在H

我发现对我和其他许多人有用的一个技巧是:要理解两点语法,请从绘制图形开始。 然后,选择两个点左侧的提交。 把它涂成红色。 跟随它的父级,向后,并将那一个涂成红色,然后继续涂上红色,直到你完全用完提交。 然后,选择两个点右侧的提交。 如果它根本没有被绘制,则将其绘制为绿色,然后将其返回到其父项并将其绘制为绿色; 继续前进,直到您用完提交或点击红色的提交。 立即停在任何涂成红色的地方。

当你完成这个“临时绘制提交颜色”过程时,绿色的就是X..Y语法选择的那些。 (我们在下一次Git 操作之前删除所有颜色,这可能使用也可能不使用两点语法。)

Rebase 想要复制“一些提交”

上述练习的原因——你应该在几张图上做; 将它们画在纸上、白板或其他任何东西上,然后给它们上色等等,直到你对它的工作原理有所了解git rebase是否使用相同的技巧来查找要复制的提交。

您要复制的原始提交集如下:

| * 2c23d01 (origin/editing) Only show single selected stop
| * 3313e88 Scroll to stop with button in stop_time
| * c1a329f Scroll item into view when clicked
| * 712eb18 Add list of stops
| * d3333e6 Add stop_time fields
| * 116ef9f Add agency fields
| * 9eb0f43 Add trip fields
| * 8d1453e Add dropdowns
| * 3f4a0ca Fix formatting issue
| * 61613d4 Add Newtype radiogroup
| * 8ca937b Start adding updatable fields to routes
| * 02fca9b Backup
| * f2d756a Fix text fields not updating
| * cf6907e Backup
| * f9c4115 Add new trip using data method
| * 6ab663e WIP
| * caac18e WIP
| * 7a70e4a Delete and then restore trips
| * 2457455 WIP

(我可以说是因为这里的origin/editing标签,这表明你在某个时候运行了git push origin editing或类似的,加上匹配的主题行。)

git rebase -i命令——它是 rebase 的“交互式”版本,可以让你调整复制的工作方式——需要知道要复制的提交 在 Git 中,这实际上是在呼唤两点语法。 所以git rebase在内部为你使用了这个两点语法:你甚至不必输入这两个点,它只是使用它们。

变基操作需要知道两件事 首先,它需要知道:我应该复制哪些提交,哪些不应该复制? 但是它也需要知道:我应该把副本放在哪里? 这就是git rebase如此聪明的地方(也许对它或你自己的好处来说太聪明了,但绝对聪明)。

您应该(并且确实)运行:

git checkout editing

首先 这为 Git 提供了提交复制终点

然后,您运行:

git rebase [options] main

这里的main名称git rebase提供了它需要知道的两件事:将副本放在哪里,以及不复制什么。

“不要复制的内容”部分是main ,因此复制的内容是main..editing (或main..HEAD ,因为HEAD表示当前分支或当前提交,具体取决于 Git 想要在内部提出的问题)。 放置副本的位置就在 main ( 010141c ) main的提交之后

Git 实际上使用“分离的 HEAD”模式进行复制,我们不会在这里详细介绍。 默认情况下,它使用git cherry-pick进行每个新提交:这就是交互式 rebase 命令表中“pick”的含义。 squash替换pick会修改樱桃采摘的方式,这样无论您复制多少次提交,您都只能获得一个最终提交; 最终提交的快照是通过组合每个复制的提交而生成的。

所有这些复制的结果就是我们可以这样画的东西。 让我先画一张更逼真的图:

          I   <-- main
         /
...--A--B
         \
          C--D--E--F--G--H   <-- editing (HEAD) [before rebase], origin/editing

我们现在运行git rebase maingit rebase -i main ,Git 会列出要复制的提交CDEFGH 然后 Git 签出提交I并开始复制,一次只cherry-pick一个(或樱桃选择和壁球或其他):

            CD-E'-F'-GH   <-- HEAD
           /
          I   <-- main
         /
...--A--B
         \
          C--D--E--F--G--H   <-- editing, origin/editing
                          \
                           J   <-- performance

其中刻度线表示副本, CD是一个新的提交,它是复制CD的压缩结果,但只进行一次提交。

现在所有这些都完成了, editing只需将名称从提交H中拉出并使其指向新副本GH

            CD-E'-F'-GH   <-- editing (HEAD)
           /
          I   <-- main
         /
...--A--B
         \
          C--D--E--F--G--H   <-- origin/editing
                          \
                           J   <-- performance

通过切换到 name editing ,Git 重新附加HEAD ,这样你就又回到了正常的日常 Git 使用模式,而不是在 rebase 的中间,现在 rebase 已经完成了。

如果你现在“快进” main ,Git 会将名字main指向提交GH 使用git checkoutgit merge (带或不带--ff-only )这样做,给你:

            CD-E'-F'-GH   <-- editing, main (HEAD)
           /
          I
         /
...--A--B
         \
          C--D--E--F--G--H   <-- origin/editing
                          \
                           J   <-- performance

潜在问题

请注意,此处的origin/editingperformance仍然指向未重新设置的提交。 如果任何事情都不需要提交J ,则可以将其删除。 提交J仍然存在,但再也找不到它了。

您的origin/editing是 Git 对分支editing的记忆,如在您称为origin的存储库中所见。 这意味着您需要强制更新该存储库中的名称editing ,或者可能只是将其完全删除。 然后你可以让你的 Git 更新或删除你的origin/editing

git push -f origin editing   # force-update their editing
git fetch                    # update my origin/editing

或者:

git push --delete origin editing  # delete their editing
git fetch --prune                 # delete my origin/editing

git fetch--prune选项意味着:像往常一样进行git fetch ,但如果它们末端的某些分支消失了,请删除我更改的分支名称副本)。

同时,您仍然至少有一个使用git stash 那是git log输出中的refs/stash 如果这个 stash 仍然有用,你可以应用它然后丢弃它; 如果没有,你可以放弃它。 (请记住检查您是否还有任何其他存储,因为git log只看到“顶部” stash@{0}存储。长时间保存存储通常是一个坏主意:很难知道它们应该去哪里, 在你创建它们之后的一两个月。这是避免git stash的另一个原因:只需创建一个新分支并提交即可。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM