如何解决Git rebase,同时在一个分支中维护最新版本的文件 考虑到前后关系,让我们看看 I'-J'-K' 是如何工作的你想要什么
我在本地分支中有一个文件,我希望能够对 origin/main
进行变基,同时确保在变基后,本地分支中的该文件与现在完全相同。>
有没有办法进行变基并保证这一点?如果在 rebase 期间我不必回答任何问题或解决此文件的任何冲突,那就更好了。
解决方法
TL;DR
使用临时标签来标记具有所需文件副本的提交。然后,使用 git rebase -i
并插入 x
命令以在每个 pick
之后运行一个简短的脚本。您可以选择在此脚本中准确地放入什么内容,但这(未经测试)可能正是您想要的:
#! /bin/sh
git checkout temp-tag -- path
git diff-index --quiet HEAD || git commit --amend --no-edit
这一切都完成后,删除临时标签(和脚本;写起来并不难,而且它有硬编码的标签和路径)。
长
要理解这个答案,首先要记住这个事实:在 Git 中,文件实际上并不在分支中。文件确实在提交中。
提交包含在分支中——或者换句话说,通过使用分支名称找到,然后通过 Git 存储在每个提交中的链接从提交到提交,向后工作。所以你可以从分支名称到提交,然后再到文件。但是“提交”步骤至关重要,因为每个提交都有每个文件的完整快照。
接下来,让我们看看 git rebase
做了什么以及它是如何做的。请记住,Git 就是关于提交,并且每个提交都有一个唯一的哈希 ID。任何现有提交的任何部分都不能更改。因此,由于 rebase 字面上不能改变任何现有提交,它必须通过复制旧的(和糟糕的,或至少在某种程度上不够)提交新的和改进的提交。这些新的和改进的提交在某些方面与旧提交相同,但在某些方面有所不同。
每个提交,由其唯一的哈希 ID 发现,包含两部分:
-
有一次提交的主要数据:与这次提交相关的源代码快照。这些不是变化。如果稍后检出该特定提交,则快照具有应该显示的每个文件。
-
除了数据之外,每个提交都有一些元数据,或者关于提交本身的信息:谁做的(姓名和电子邮件地址),什么时候(日期和时间戳)等等
元数据将“谁进行了这次提交”分为两部分:作者是最初提交者的姓名、电子邮件和时间戳,以及提交者 是进行此提交变体的人员的姓名、电子邮件和时间戳。所以当我们像这样复制一个旧的提交时,我们保留了原来的作者,但设置了一个新的提交者。如果你复制自己的提交,这意味着姓名和电子邮件并没有真正改变——旧的有你作为两者,而新的有你作为两者——但是 提交者时间戳 改变。
不过,最重要的是,每个提交都会记录其前一个或父提交的哈希 ID。变基的目的通常是进行一串这样的提交:
I--J--K <-- feature / ...--G--H--L <-- mainline
并制作新的改进版本的提交
I
、J
和K
,以便新提交来自L
而不是来自 {{1 }}:H
其中提交
I--J--K <-- feature / ...--G--H--L <-- mainline \ I'-J'-K' <-- new-and-improved-feature
是提交I'
的“副本”(排序),I
是J'
的副本,J
是一个K'
的副本。
不用过多担心复制过程的机制——尽管我会在这里提到它使用 K
——让我们做最后一个观察,那就是方式我们(和 Git) find commits 是使用分支名称来查找链中最后的提交。当提交 git cherry-pick
是 H
的最后提交时,我们发现它是因为我们有:
mainline
name ...--G--H <-- mainline
持有提交 mainline
的哈希 ID。所以 H
会提取提交 git checkout mainline
供我们使用或处理/使用。但随后我们或其他人做了一个新的提交,添加到 H
,我们称之为提交 mainline
,所以我们有:
L
name ...--G--H--L <-- mainline
现在包含提交 mainline
的哈希 ID。 L
命令将提取提交 git checkout mainline
供我们使用。为了find commit L
,我们必须让Git 打开commit H
并读取它的元数据。此元数据包含较早提交 L
的原始哈希 ID。
这对我们来说意味着一旦我们完成了这个:
H
我们可以将名称 I--J--K <-- feature
/
...--G--H--L <-- mainline
\
I'-J'-K' <-- new-and-improved-feature
从提交 feature
中删除,并将其粘贴到提交 K
上,如下所示:
K'
现在,当我们尝试查看分支 I--J--K ???
/
...--G--H--L <-- mainline
\
I'-J'-K' <-- feature
上有哪些提交时,我们将让 Git 通过使用 name feature
来定位提交 {{1} }.提交 feature
指向较早的提交 K'
,后者指向 K'
,后者又指向 J'
。一旦我们移动了分支名称,我们的变基将完成,并丢弃我们在构建 I',
序列时可能使用的任何时髦的特殊名称。
(练习:提交 L
会发生什么?这重要吗?我们怎么知道它们是否仍在存储库中?)
考虑到前后关系,让我们看看 I'-J'-K'
是如何工作的
我在上面简单地提到,I-J-K
使用 git rebase
来复制每个提交。反过来,cherry-pick 命令的工作原理是……嗯,从技术上讲,它是一个完整的三向合并,但是通过查看当我们只比较两次提交。
让我们从这张“之前”的照片开始:
git rebase
我们需要让 Git check out 提交 git cherry-pick
,这是我们希望新提交去的地方。如果我们以正常方式执行此操作,我们将创建一个新的分支名称,例如 I--J--K <-- feature
/
...--G--H--L <-- mainline
,使用:
L
(或与 Git 2.23 或更高版本中的 tmp
命令相同)。 Git 实际上为此使用了它所谓的 分离 HEAD 模式,特殊名称 git checkout -b tmp <hash-of-L>
直接指向提交:
git switch
或:
HEAD
产生这个:
git checkout <hash-of-L>
现在 Git 运行 git switch --detach <hash-of-L>
。在整个设置过程中,Git 保存了提交 I--J--K <-- feature
/
...--G--H--L <-- HEAD,mainline
、git cherry-pick hash-of-I
和 I
的哈希 ID。如果您在此处使用 J
,您将看到列出这些哈希 ID 的 K
命令。1git rebase --interactive
表示挑选命令。
挑选自己最终会将提交 pick
中保存的快照与提交 pick
中保存的快照进行比较。 这两个快照之间的区别实际上是一组可以应用于快照的指令。将这组指令应用于 H
中的快照会生成 I
中的快照。但是,如果我们将这些指令应用于 H
中的快照会怎样?
如果我们这样做——并假设它有效并且没有合并冲突2——并根据结果进行新的提交,我们将得到提交 I
。我们将让 Git 按原样保存原始作者信息和原始提交消息,并生成一组新的提交者信息并使用我们通过应用差异获得的快照。结果是:
L
Git 现在继续执行 I'
,通过比较 I--J--K <-- feature
/
...--G--H--L <-- mainline
\
I' <-- HEAD
-vs-git cherry-pick hash-of-J
并将其应用于 J
来复制提交 I
:
J
最后——因为只有三个提交——我们最后一次挑选了提交 I'
,它比较了 I--J--K <-- feature
/
...--G--H--L <-- mainline
\
I'-J' <-- HEAD
-vs-K
(和 J
- vs-K
如果您对cherry-pick的合并方面感兴趣)来构建提交J
,这给我们留下了:
J'
剩下的唯一任务是移动名称 K'
以指向当前提交 I--J--K <-- feature
/
...--G--H--L <-- mainline
\
I'-J'-K' <-- HEAD
以获取:
feature
这样就完成了 rebase 过程。
1您可以编辑的 K'
说明表具有缩写的哈希 ID。我一直不太确定为什么:Git 必须将它们扩展回来才能在内部使用它们。也许 Git 人员只是认为当有 7 或 12 个随机字符而不是 40 个时,他们看起来不那么令人生畏。对于 I--J--K ???
/
...--G--H--L <-- mainline
\
I'-J'-K' <-- feature (HEAD)
输出,这可能会出现在某人的电子邮件或其他东西中,当然——但在这里,他们是只是临时页面上的说明,如果您编辑它们,您可以在编辑器中使用“移动行”说明。
2合并冲突(如果有的话)也是由于比较 git rebase
中的快照与 git describe
中的快照而产生的。至少第一个樱桃采摘就是这种情况。随后的两个优先选择使用提交 H
和 L
作为合并基础,I
提交是在上一个步骤中构建的提交。这就是一切变得有点棘手的地方。
你想要什么
我相信您想要的是,在每次挑选之后,您希望 new(复制)中的某个特定文件与某个特定早期提交中的某个特定文件完全匹配。 >
假设现有提交 J
具有所需的文件版本。我们要做的——避免依赖 Git 不移动名称 --ours
,并让你选择任何提交——是创建一个临时的轻量级标签来标识这个提交:
K
注意:如果没有一个固定版本的文件应该进入每个复制的提交,您将需要不同的策略来定位 feature
的源提交 ,但其余的可以继续工作。
接下来,我们将使用 git tag temp-tag <hash-of-K-or-whatever>
。这将一组樱桃精选变成了可编辑的说明表。使用我们的编辑器,在 每个 checkout
命令之后,我们使用 git rebase -i
或 pick
命令添加一行:
exec
(假设我们的小脚本已放入 x
并使其可执行)。
Git 将执行cherry-pick 命令,直至完成,包括进行新的提交(在我们的示例中为pick <hash>
x /tmp/script
、/tmp/script
或I'
)。然后它会因为这个 J'
行而运行脚本。脚本:
-
从特定提交中提取特定文件:使用
K'
,我们从所需提交中获取所需文件,并将其放入 Git 索引和工作树中。 (索引副本很重要,但为了理智起见,更新工作树也很好。) -
测试结果是否值得替换提示提交 (
x
)。这是我们的temp-tag
。如果索引仍然与当前提交匹配,则无需更改。否则,我们将运行git commit --amend
,它将当前提交推开并创建一个新提交。使用git diff-index --quiet HEAD
,我们告诉git commit --amend
简单地重复使用现有的提交消息。注意:在这种情况下,即使没有变化,
--no-edit
实际上是安全,但这是白费力气。对于这个脚本和任务,这可能不是真正相关,但不执行大量不必要的工作似乎很好。
因此,这将确保在 rebase 期间每个替换提交本身都被替换,“更正”替换将单个文件换出到我们想要的文件。这样,当 Git 开始从旧分支中提取分支名称并将其放在替换提交的末尾时,每个替换提交都是实际所需的新的和改进的提交。
除了清理(删除轻量级 git commit
标记并删除脚本)之外,无需执行任何其他操作。
一种解决方法是我将文件复制粘贴到便笺簿,使用 -Xours
运行变基,然后粘贴便笺簿的最终结果。
我真的不喜欢这个解决方案(如果我们谈论的冲突不止一个文件,它不会一概而论),但似乎这是最快的方法。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。