Git:如何“粘贴”而不是合并分支

时间:2018-03-02 21:55:34

标签: git git-merge

Git哲学问题:“祖先”和“历史”(a.k.a.年代学)是不同的东西?...

看哪,我有以下git存储库:

      A---B---C develop
     /
D---E---F---G master

我希望主人能够准确地包含发展的主要内容:

      A---B---C develop
     /         \ 
D---E---F---G---C master

外,我不想合并。我希望它像 this

      A---B---C   develop
     /         \ 
D---E---F---G===C--- master

主人的头部不会有多个祖先,它将成为开发者的头脑。我的图表中的===表示按时间顺序连接,但不是祖先。

为了澄清,我不想使用git merge -X theirsgit merge -r ours因为那些仍然是真正的合并......我只是不想合并:我想要一个“粘贴”。但是,我还需要能够查看master上的git log并查看commit C,然后提交G,然后提交F等,即使C和G之间没有父系关系。另外,我的约束禁止我从擦除大师或发展,或搞乱他们的历史。有一种简单的方法可以做到这一点吗?

3 个答案:

答案 0 :(得分:3)

可以实现这样的目标,但不是Git如何运作,你不应该这样做。

您可以使用git reset移动master并拖动当前状态develop。您需要将master分支从其当前提交G移动到所需的提交C,然后将软重置master恢复为其原始提交,{{1用它拖动G的当前状态。然后,您可以提交这些更改,使用C上存在的更改创建提交(不是C)。

develop

此时,git checkout master git reset --hard develop git reset <commit id of C> # or git reset HEAD@{1} # the working directory now contains the difference between C and G git commit -m 'a message for C' master将包含相同的内容,但具有不同的提交历史记录。提交图将如下所示:

develop

A---B---C develop / D---E---F---G---C' master C'包含相同的内容但有不同的历史记录。

您可以先进行合并,然后完全按上述步骤执行,并创建一个新的提交,其中包含Cmaster作为祖先,但内容为develop

git checkout master git merge develop git reset - hard develop git reset HEAD @ {1} git commit -m&#39; C&#39;的提交消息。

这将为您提供以下内容,其中C是合并提交:

M

但同样,这并不是Git的真正意义,如果您打算放弃一个分支或另一个分支的所有更改,则没有理由这样做。

答案 1 :(得分:2)

编辑:让我先从哲学问题开始(我之前错过了)。

  

Git哲学问题:是&#34;祖先&#34;和&#34;历史&#34; (a.k.a.年代学)不同的东西?...

不是真的。好吧,也许:&#34;祖先&#34;和&#34;历史&#34;是相同的,或者至少是深刻的联系。年表... 经常无关紧要。 Git存储提交,提交是或至少形成历史。

每个提交都有一些父级:通常只有一个父级,但是合并提交有两个或更多,并且存储库中至少有一个提交 - 名为 root commit-has no 父母。通常,您通过选择现有提交,选择或创建(快照)以进入新提交并使用以下命令写出新提交来进行新提交:

  • 你作为作者和提交者,&#34;现在&#34;作为新提交的时间戳;
  • 你的树作为快照;
  • 任意提交消息;和
  • 您当前提交的ID - 可能还有更多提交ID - 作为新提交的父级。

一旦完成,提交将永久冻结:它实际上不能被更改,因为它的哈希ID - 其真正的名称&#34;就Git如何在数据库中找到它而言,它是其内容的加密校验和。以任何方式更改内容,甚至只更改一位,并获得不同的校验和,这意味着新的和不同的提交(同时旧的提交保留在存储库中)。

当您在不启动当前提交的情况下进行提交时,会发生 root 提交的特殊情况。这显然必须是新的空存储库中第一次提交的情况:没有先前的提交,因此第一次提交不能拥有父级。之后,所有正常提交都有一些现有提交作为其父提交,尽管可以创建新的提交。

结果是将字符串提交到一起,在时间和#34;中向后指向,而不管提交中存储的任何时间戳。我们可以像这样绘制一个小的三个提交存储库,其中单个大写字母代表实际的提交哈希值:

A <-B <-C

提交C是最新的(即使它的时间戳是在20世纪70年代制作的);提交BC的父级;并且root commit AB的父级。这些事实嵌入在每个提交中,它是只读的和不可破坏的。

Git 找到 C的方式是通过分支名称,例如masterC有一些哈希ID - C内容的加密校验和 - 而Git将该哈希ID放在键值存储中,键名为master 。因此,给定master,Git找到C,Git用它来查找B,依此类推。要将新提交放到master分支上,我们现在将编写一些新内容,运行git addgit commit,并使用新快照获取新提交D,其父级是提交C。无论D的哈希ID是什么,Git都会将其写入master,现在master将指向新提交D

历史和祖先是如此深深地交织在一起的。我们可以在任何时候给出所有起始点名称(分支,标签和任何其他名称),通过这些链接查找存储库中的所有提交。绘制链接会生成提交图。

git log命令不一定总是按照它们在此图中出现的顺序显示提交。例如,特别是如果git log有,则在其#34;承诺中显示&#34; queue,两个或更多提交,它必须先选择一个才能显示。它选择的是基于您在git log命令行上指定的排序条件。但是,如果只有一个提交要显示,则显示一个提交。显示一个提交,git log通常会将提交的父母(所有这些)添加到队列中,除非它们已经显示。

git log的默认设置是通过将(单个)提交放入队列来向您显示HEAD提交。现在只有一个提交在队列中,所以Git删除它并显示它。如果这是普通的单父提交,您将看到它,然后git log将其(单个)父级放入现在为空的队列中。然后Git会向您显示父级:无论是否提交任何提交时间戳,您都会按照图形顺序查看提交。

由于合并提交根据定义至少有两个父项,因此显示合并提交的行为通常会将两个尚未见过的提交丢弃到要显示的提交队列中。此时,时间戳可能(默认情况下,确实如此)进入图片。如果您没有指定任何特定的排序标准,默认情况下Git使用的是:首先显示提交时间戳更高的提交。所以现在年表 - 具有存储在提交中的时间戳的含义,这可能与实际提交时没有任何关系 - 具有一定的效果。

(请注意,您可以通过更改计算机的时钟来调整任何新提交中的一个或两个时间戳,或通过设置GIT_AUTHOR_DATE和/或更轻松,更可重复运行GIT_COMMITTER_DATE时的git commit个环境变量。某些命令(包括git commit本身)也会带有--author-date个标记等。)

关于问题的其余部分

有几个人告诉过你,你无法在Git中得到你想要的东西。

此外,这张图是荒谬的:

      A---B---C develop
     /         \
D---E---F---G---C master

因为它包含两个标记为C的不同提交。您无法更改现有提交,根据定义,您所做的任何新提交都将具有新的不同哈希ID。

这张图也是如此,出于同样的原因:

      A---B---C   develop
     /         \ 
D---E---F---G===C--- master

但是,如果我们用额外的C替换一个完全不同的提交 - 具有不同的哈希ID - 那么我们可以得到前一个图,现在看起来像: / p>

      A---B---C   <-- develop
     /         \
D---E---F---G---H    <-- master

即名称master选择提交H,其历史记录包括所有提交,而名称develop选择现有提交C,其历史记录可追溯到BA,然后返回E,依此类推。或者,如果您愿意,我们可以获得图表:

      A---B---C   <-- develop
     /
D---E---F---G---H    <-- master

与提交H相关联的快照可以是您想要的任何内容。 (要完全手动创建此类快照,您可以在工作树中工作,运行git add以通过当前索引中的副本复制更新的工作树文件,然后编写git write-tree以写出索引以形成H快照的树 - 但您似乎不需要这样。)

如果您希望提交H与提交C具有相同的快照,那么git diff develop master根本不产生任何输出,这非常简单:我们可以使用Git& #39;所谓的 plumbing 命令,用于从树中为提交H创建提交C。最后有三个步骤:

vim /tmp/msg
# write appropriate log message to file /tmp/msg

接下来,创建新的提交H并将其哈希ID保存在某处。 (我在这里使用一个变量。例如,如果你愿意的话,你可以用git log --graph $h检查它,然后再实际提交在任何地方使用它。)

h=$(git commit-tree -p master -p develop -F /tmp/msg develop^{tree})

这将按顺序创建两个父项masterdevelop(即GC)的提交。如果您只想要一个父母,请留出一个-p和参数。

最后,如果一切看起来都不错,并且您已经master并且希望H成为提示提交,请重置或快进合并到H:< / p>

git checkout master      # if necessary
git merge --ff-only $h

这会更新当前分支名称 - 即master-to指向commit $h(即新提交H),并相应地更新索引和工作树,以便commit H现在占用了通常的分段和工作空间。

(顺便说一句,如果您确实使用两个父项进行最终提交H,并使第一个父项提交G,第二个提交C但使用提交C的内容,相当于git merge -s theirs,如果它存在的话。它不会,但你可以通过多种方式合成它,包括上面的那些。另见VonC&#39 ;回答here和一些相关链接。)

答案 2 :(得分:0)

  

我不想要合并:我想要一个“粘贴”。但是,我还需要能够查看master上的git log并查看commit C,然后提交G,然后提交F等,即使C和G之间没有父系关系。

这是自相矛盾的。你已经切断了祖先的链接。你完全抛弃了历史。

Git checkout -B master develop

是您放弃现有master历史记录的方式,现在master会引用当前的develop历史记录。

如果你想在master的新历史中看到旧的遗弃历史,那么你可以

git merge -s ours master@{1}

记录祖先而不采取任何内容,但你要么记录了祖先,要么你没有记录。你不能切断你的祖先链接并拥有它们。