Git rebase,同时在一个分支中维护最新版本的文件 考虑到前后关系,让我们看看 I'-J'-K' 是如何工作的你想要什么

如何解决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
    

    并制作新的改进版本的提交 IJK,以便新提交来自 L 而不是来自 {{1 }}:

    H

    其中提交 I--J--K <-- feature / ...--G--H--L <-- mainline \ I'-J'-K' <-- new-and-improved-feature 是提交 I' 的“副本”(排序),IJ' 的副本,J 是一个K' 的副本。

不用过多担心复制过程的机制——尽管我会在这里提到它使用 K——让我们做最后一个观察,那就是方式我们(和 Git) find commits 是使用分支名称来查找链中最后的提交。当提交 git cherry-pickH最后提交时,我们发现它是因为我们有:

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-II 的哈希 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 中的快照而产生的。至少第一个樱桃采摘就是这种情况。随后的两个优先选择使用提交 HL 作为合并基础,I 提交是在上一个步骤中构建的提交。这就是一切变得有点棘手的地方。


你想要什么

我相信您想要的是,在每次挑选之后,您希望 new(复制)中的某个特定文件与某个特定早期提交中的某个特定文件完全匹配。 >

假设现有提交 J 具有所需的文件版本。我们要做的——避免依赖 Git 不移动名称 --ours,并让你选择任何提交——是创建一个临时的轻量级标签来标识这个提交:

K

注意:如果没有一个固定版本的文件应该进入每个复制的提交,您将需要不同的策略来定位 feature 的源提交 ,但其余的可以继续工作。

接下来,我们将使用 git tag temp-tag <hash-of-K-or-whatever> 。这将一组樱桃精选变成了可编辑的说明表。使用我们的编辑器,在 每个 checkout 命令之后,我们使用 git rebase -ipick 命令添加一行:

exec

(假设我们的小脚本已放入 x 并使其可执行)。

Git 将执行cherry-pick 命令,直至完成,包括进行新的提交(在我们的示例中为pick <hash> x /tmp/script /tmp/scriptI')。然后它会因为这个 J' 行而运行脚本。脚本:

  1. 从特定提交中提取特定文件:使用 K',我们从所需提交中获取所需文件,并将其放入 Git 索引和工作树中。 (索引副本很重要,但为了理智起见,更新工作树也很好。)

  2. 测试结果是否值得替换提示提交 (x)。这是我们的temp-tag。如果索引仍然与当前提交匹配,则无需更改。否则,我们将运行 git commit --amend,它将当前提交推开并创建一个新提交。使用 git diff-index --quiet HEAD,我们告诉 git commit --amend 简单地重复使用现有的提交消息。

    注意:在这种情况下,即使没有变化,--no-edit 实际上是安全,但这是白费力气。对于这个脚本和任务,这可能不是真正相关,但不执行大量不必要的工作似乎很好。

因此,这将确保在 rebase 期间每个替换提交本身都被替换,“更正”替换将单个文件换出到我们想要的文件。这样,当 Git 开始从旧分支中提取分支名称并将其放在替换提交的末尾时,每个替换提交都是实际所需的新的和改进的提交。

除了清理(删除轻量级 git commit 标记并删除脚本)之外,无需执行任何其他操作。

,

一种解决方法是我将文件复制粘贴到便笺簿,使用 -Xours 运行变基,然后粘贴便笺簿的最终结果。

我真的不喜欢这个解决方案(如果我们谈论的冲突不止一个文件,它不会一概而论),但似乎这是最快的方法。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-