[英]confused on the combination of Head ref and git rebase --interactive
[英]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
到目前为止一切都很好。 现在只需运行git merge --ff-only editing
(甚至只是git merge editing
)。 但是您可能想要对performance_limit_routes
和performance_testing
做一些事情。
我认为您在这里唯一的错误是您对git rebase
的心理模型中的一个小错误。 git rebase
的含义可以很简单地说:
你有一些提交的集合。 你喜欢这些提交中的大部分内容,或者其中一些提交的一些内容。 但是对于提交的集合,您至少有一点不喜欢。
一旦你完成了任何 Git 提交,实际上是不可能更改的。 但是:当我们谈论分支B上的提交时(对于某些B ),我们的意思是: Git 通过使用名称B找到一个特定的提交,然后从提交到提交向后工作。 (稍后我将对此进行更多说明,但请记住,每个分支名称仅记录单个提交的原始哈希 ID。)
这意味着,如果我们要将原始提交复制到一些新的和改进的提交中,那么让 Git强制名称B指向最后一个副本,而不是最后一个原始提交,那为什么,任何不是注意提交的原始哈希 ID 将看到新副本,而不是原始副本。
简而言之,这就是git rebase
所做的。 它需要一些原始提交,您主要喜欢它们但不喜欢它们的某些内容,并将它们复制到新的和改进的提交中。 然后它使我们用来查找提交的分支名称找到新的和改进的提交,而不是旧的(和糟糕的?)提交。
因此,您的这两个命令的序列:
git checkout editing
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_routes
和performance_testing
以及refs/stash
。 这些是指旧的提交。 最初的 19 次(如果我再次计算正确的话)提交仍然存在,你和 Git仍然可以找到它们。
这是一个问题吗? 也许,也许不是。 如果没有,你不需要做任何事情。 如果是这样,你需要做点什么。 这可能就像“删除这些分支,这样没人会再看到它们”一样简单(并使用git stash drop
删除您的存储)。 是否要保留这些提交,如果是,是否要复制(或变基)其中任何一个,都取决于您。 请参阅下面的“血腥细节”。
要使用 Git,我们需要知道——就像我们的骨子里一样,甚至不去想它——Git 就是关于提交的,并且每个提交都有编号(带有一个看起来随机的哈希 ID,比如6be7f1a
和010141c
等等,除了这些是缩写的——完整的要长得多)并存储所有文件的快照和一些元数据。
所有文件的快照在概念上非常简单:它就像一个 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
这里的名字main
和origin/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
开始并向后工作,我们去L
, K
, H
, G
等,所以我们仍然停在H
。
我发现对我和其他许多人有用的一个技巧是:要理解两点语法,请从绘制图形开始。 然后,选择两个点左侧的提交。 把它涂成红色。 跟随它的父级,向后,并将那一个涂成红色,然后继续涂上红色,直到你完全用完提交。 然后,选择两个点右侧的提交。 如果它根本没有被绘制,则将其绘制为绿色,然后将其返回到其父项并将其绘制为绿色; 继续前进,直到您用完提交或点击红色的提交。 立即停在任何涂成红色的地方。
当你完成这个“临时绘制提交颜色”过程时,绿色的就是X..Y
语法选择的那些。 (我们在下一次Git 操作之前删除所有颜色,这可能使用也可能不使用两点语法。)
上述练习的原因——你应该在几张图上做; 将它们画在纸上、白板或其他任何东西上,然后给它们上色等等,直到你对它的工作原理有所了解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 main
或git 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
是一个新的提交,它是复制C
和D
的压缩结果,但只进行一次提交。
现在所有这些都完成了, 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 checkout
和git merge
(带或不带--ff-only
)这样做,给你:
CD-E'-F'-GH <-- editing, main (HEAD)
/
I
/
...--A--B
\
C--D--E--F--G--H <-- origin/editing
\
J <-- performance
请注意,此处的origin/editing
和performance
仍然指向未重新设置的提交。 如果任何事情都不需要提交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.