Git哲学问题:“祖先”和“历史”(a.k.a.年代学)是不同的东西?...
看,我有以下git存储库:
A---B---C develop
/
D---E---F---G master
而且我希望主人的脑袋能够准确地包含发展的主要内容:
A---B---C develop
/ \
D---E---F---G---C master
除此之外,我不想合并。我希望它是这样的:
A---B---C develop
/ \
D---E---F---G===C--- master
在主人的头上没有多个祖先的地方,它将成为开发主管的所在。我的图中的===
意味着按时间顺序连接,但不是祖先。
为了澄清,我不想使用git merge -X theirs
或git merge -r ours
,因为那些仍然是真正的合并...我只是不想合并:我想要一个“粘贴”。但是,我还需要能够查看master上的git log并查看commit C,然后提交G,然后提交F等,即使C和G之间没有父系关系。另外,我的约束禁止我从擦除大师或发展,或搞乱他们的历史。有一个简单的方法吗?
你可以实现这样的目标,但这不是Git的工作方式,你不应该这样做。
您可以使用git reset
移动master
并拖动当前的develop
状态。你需要将master
分支从其当前提交G
移动到所需的提交C
,然后软重置master
回到其原始提交G
,用它拖动当前状态C
。然后,您可以提交这些更改,使用C
上存在的更改创建新的提交(NOT 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'
和C
包含相同的内容,但有不同的历史。
您可以先进行合并,然后完全按照上面的步骤执行步骤,并创建一个新的提交,其中包含master
和develop
作为祖先,但内容为C
:
git checkout master git merge develop git reset --hard develop git reset HEAD @ {1} git commit -m'是C'的提交消息。
这将为您提供以下内容,其中M
是合并提交:
A---B---C develop
/ \
D---E---F---G---M---C' master
但同样,这并不是Git的工作原理,如果您打算放弃一个分支或另一个分支的所有更改,则没有理由这样做。
编辑:让我从哲学问题开始(我之前错过了)。
Git哲学问题:“祖先”和“历史”(a.k.a.年代学)是不同的东西?...
并不是的。好吧,也许:“祖先”和“历史”是相同的,或者至少是深刻的联系。年表...通常无关紧要。 Git存储提交,提交是或至少形成历史。
每个提交都有一些父级:通常只有一个父级,但是合并提交有两个或更多,并且存储库中至少有一个提交 - 称为root commit - 没有父级。通常,您通过选择现有提交,选择或创建树(快照)以进入新提交,并使用以下内容写出新提交来进行新提交:
一旦完成,提交将永久冻结:它实际上无法更改,因为它的哈希ID - 就Git在数据库中如何找到它而言的“真实名称” - 是其内容的加密校验和。以任何方式更改内容,甚至只更改一位,并获得不同的校验和,这意味着新的和不同的提交(同时旧的提交保留在存储库中)。
当您在不启动当前提交的情况下进行提交时,会发生根提交的特殊情况。这显然必须是新的空存储库中第一次提交的情况:没有先前的提交,因此第一次提交不能拥有父级。之后,所有正常提交都有一些现有提交作为其父提交,但您可以创建新的提交。
结果是将字符串提交到一起,指向“及时向后”,而不管提交中存储的任何时间戳。我们可以像这样绘制一个小的三提交存储库,其中单个大写字母代表实际的提交哈希:
A <-B <-C
承诺C
是最新的(即使它的时间戳表明它是在20世纪70年代制造的);提交B
是C
的父母;和root commit A
是B
的父母。这些事实嵌入在每个提交中,这是只读和不可破坏的。
Git找到C
的方式是通过分支名称,如master
。 C
有一些哈希ID - C
内容的加密校验和--Git将哈希ID放在键值存储中,键名为master
。所以给定master
,Git找到C
,Git用它来找到B
,依此类推。要在master
分支上放置一个新的提交,我们现在会编写一些新东西,运行git add
和git commit
,并获得一个新的提交D
,其中包含一个新的快照,其父级是提交C
。无论D
的哈希ID是什么,Git都会将其写入master
,现在master
将指向新的提交D
。
这就是历史和祖先如此深深地交织在一起的方式。我们可以在任何时候给出所有起始点名称(分支,标签和任何其他名称),通过这些链接查找存储库中的所有提交。绘制链接会生成提交图。
git log
命令不一定总是按照它们在此图中出现的顺序显示提交。例如,特别是,如果git log
在其“提交显示”队列中有两个或多个提交,则必须选择一个首先显示。它选择的是基于您在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
但是,如果我们使用具有不同散列ID的完全不同的commit-one替换额外的C
,那么我们可以获得前一个图形,现在看起来像:
A---B---C <-- develop
/ \
D---E---F---G---H <-- master
也就是说,名称master
选择提交H
,其历史包括所有提交,而名称develop
选择现有提交C
,其历史通过B
返回A
,然后返回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所谓的管道命令从树中为commit 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})
这将按顺序创建两个父项master
和develop
(即G
和C
)的提交。如果你只想要一个父母,请留下一个-p
和参数。
最后,如果所有看起来都很好并且你在master
并希望使H
成为提示提交,重置或快进合并到H
:
git checkout master # if necessary
git merge --ff-only $h
这会更新当前分支名称 - 即master-to to to commit $h
(即新提交H
),并相应地更新索引和工作树,以便commit H
的内容现在占用通常的staging和工作空间。
(顺便说一句,如果你确实与两个父母进行最终提交H
,并使第一个父提交G
和第二个提交C
但使用提交C
的内容,那将相当于git merge -s theirs
,如果它存在。它不会,但是你可以通过多种方式合成它,包括上面的方法。另见VonC的回答here和一些相关的链接。)
我不想要合并:我想要一个“粘贴”。但是,我还需要能够查看master上的git log并查看commit C,然后提交G,然后提交F等,即使C和G之间没有父系关系。
这是自相矛盾的。你已经切断了祖先的链接。你完全抛弃了历史。
Git checkout -B master develop
是你如何放弃现有的master
历史,使master
现在参考当前的develop
历史。
如果你想在master
的新历史中看到旧的废弃历史,那么你可以
git merge -s ours master@{1}
记录祖先而不采取任何内容,但你要么记录了祖先,要么你没有记录。你不能切断你的祖先链接并拥有它们。