如何在保存历史记录的同时将目录从一个分支移动到另一个分支?
我想搬进同一个仓库。
答案 0 :(得分:1)
您不能(完全)得到想要的东西,但是,如果有时尝试,您可能会发现,您会得到所需的东西。
您要做的就是重命名目录并提交。如果当前分支顶部的提交中尚未包含该目录,则可能首先需要从其他提交中提取目录。也就是说,您可能需要一个缩写:
git checkout otherbranch -- path/to/directory
git commit # optional, but see below
,然后在任何情况下运行:
git mv path/to/directory new/path/to/dir
,然后git commit
结果。那并不会满足您的需求,但可能会满足您的需求,特别是如果您进行了第一次不需要的东西时,重命名,以便您有两个相邻的提交,一个带有旧名称,另一个带有新名称。
相反,您可能想合并分支,提交合并,然后才重命名并再次提交。是否需要以及为什么需要详细说明。
在这里了解两件事很重要:
人们经常反对Git存储快照的想法,因为git log -p
显示了补丁,即更改。例如,您查看了提交0a36ca1
,并且看到对README.md
的更改。然后git log
继续提交0a36ca1
的父提交922bf37
,例如,您看到README.md
和/或其他文件的另一处更改,依此类推。这是否意味着0a36ca1
仅将更改存储到README.md
?答案是:否,0a36ca1
存储README.md
和所有其他文件的完整副本。 Git通过检查两者 922bf37
(0a36ca1
的父元素,即0a36ca1
之前的提交)来显示的变化— < em>和 0a36ca1
。两次提交都有每个文件的副本。 Git比较了两个提交的文件。这两个提交中的所有文件都与README.md
的 except 匹配。然后,Git比较了两个README.md
版本以查看发生了什么变化,并向您显示了该文件中发生了什么更改。
git show
命令与之类似,不同之处在于您通常为它提供一次提交的哈希ID,而git show
打印该提交的元数据(创建记录的元数据,创建者,时间和原因-日志消息)。然后将父级中的快照与该提交中的快照进行比较。 不同是您看到的文件。
当您通过运行git log
或git log
来询问git log master
的历史记录时,Git:
git log
或master
中的 last 提交开始,并向您显示提交git show
的消息。 2 重复此过程,直到Git的父母用完为止,或者您厌倦了通过git log
输出进行分页。给定一个不错的简单线性提交链,例如:
A <-B <-C <-D <-E <-F <-G <-- master
(单个大写字母代表Git实际使用的丑陋的哈希ID),Git首先显示G
(名称为master
),然后移至{ {1}}并向您显示F
,然后移至F
并显示E
,依此类推。提交E
是存储库中的第一个提交-它没有父级; A
中没有向后的箭头让Git向左移动-因此A
表示它是从头开始创建的每个文件。这意味着git show
以相同的方式显示它。当然,没有父母,就没有向后跟随的箭头。
1 从技术上讲,目录会变成内部的 tree 对象,但是Git不会存储 empty 目录,原因很简单,因为您可以不能将目录添加到Git的索引中,并且Git不会从工作树中构建提交,而是从索引中构建。将Git视为仅存储文件会更容易,因为这是最终的结果。
2 当然,这假设您正在使用git log -p
。 git log -p
和git log
之间有几个重要的区别:首先,git show
向后走;第二,git log
默认为 not 显示补丁;第三,git log
以不同的默认方式显示合并提交:git show
默认对自己说: ugh,一个合并提交,太难了:我只打印日志消息,然后继续前进,根本不显示任何差异。 git log -p
的默认设置是显示组合差异,这是对多个父母的差异化形式。
git show
可以显示历史记录的子集 您可以运行以下命令,而不是仅运行git log
或git log
:
git log master
,然后查看git log master -- path/to/file.ext
的历史记录。 path/to/file.ext
在这里的操作与往常一样是 commit 历史,但随后不显示一些提交。也就是说,鉴于上面的简单线性链,git log
从提交git log
开始。它比较G
和F
(的快照)以查看更改了哪些文件。如果这些文件 do 包含G
,则path/to/file.ext
显示提交git log
。然后,即使它什么都没显示,它又返回到提交G
。
换句话说,F
不仅可以向您显示遍历的所有所有提交,而且可以向您显示遍历的所选提交。结果是,Git似乎具有文件历史记录,但事实并非如此:它只是合成真实历史记录中的一个子集历史记录,并且可以正常工作。
这很重要,因为当Git进行此合成文件历史记录创建时,git log
正在修改提交步。 The git log
documentation calls this History Simplification,并且很复杂。有大约六个git log
选项可以控制如何简化历史记录。这意味着您在git log
中看到的“文件历史记录”取决于您传递给git log
的选项,以及实际的提交历史记录。
(至少有一天要阅读和研究“历史简化”部分,因为其中有很多内容。我使用Git已有很长时间了,我想认为我对此有很多了解,但是即使到那时我必须重新参考文档。尤其是“ TREESAME”的概念(在每次提交中减去不需要的树组件之后才应用),并且合并时遵循哪些提交是特别棘手的。)
git log
,--follow
将尝试检测重命名当Git执行此逐提交,向后遍历一连串提交的过程时,父级到子级之间的差异可能表明某些文件已被重命名。例如,名为git log
的文件可能已重命名为名为README
的文件。一个简单的:
README.md
将向您展示git log master -- README.md
随时间的变化(向后),但是在README.md
被命名为README.md
时停止,因为它正在寻找README
并从这里开始提交没有 README.md
。
当您将README.md
添加到--follow
时,将在整个重命名之后,只需更改一个哪个文件,该文件就只能使用一个文件!寻找。在检测到例如git log
到D
的边界后,现在E
的文件在落实README.md
中被称为README
, D
停止寻找git log
,并从README.md
开始寻找对名为D
的文件的更改。真的就是这么简单。
README
对于您的用例来说太简单了这里的问题是--follow
很简单,这太简单了。因此,它不会执行您想要的操作,原因有两个:
首先,您要谈论的是跨相当大的距离复制文件:
--follow
如果您的完整文件目录位于...--F--G--H <-- master
\
N--O--P <-- branch
上的提交H
中,而您刚刚将其复制到新的提交中,则将在master
上进行是在提交branch
之后发出的,嗯,没有从从 P
到P
的反向链接。这就是为什么我建议您提交文件而不重命名它们,然后重命名它们并再次提交。结果将是:
H
其中提交...--F--G--H <-- master
\
N--O--P--Q--R <-- branch
的文件已重命名,R
的文件未重命名,只是从Q
复制而来。在针对H
的提交 log消息中,您可以声明指向指向Q
时,已经从分支master
复制了整个目录(请在此处使用H
的真实哈希ID-运行H
以查看git rev-parse master
立即指定的哈希ID)。然后,每当Git从提交master
返回到提交R
时,您便重新命名目录并再次提交以使它们显示为重命名。
Q
选项仅适用于一个文件。也就是说,给定的提交是git log --follow
或从其派生的,因此具有新的目录名称,则必须运行:
R
最终将以提交git log --follow [<commit-hash>] [--] new/path/to/dir/file.ext
的方式工作,显示R
(因为与提交new/path/to/dir/file.ext
相比,它在提交R
中已重命名),然后移回提交Q
并开始寻找Q
。
从检测到的单个重命名,加上path/to/directory/file.ext
和Q
中的日志消息,您(一个聪明的人,而不是一个只遵循非常简单的规则的愚蠢的Git程序)可以得出结论,这些文件的全部来自提交R
。
这是您可能想要真正合并的地方。您不仅可以从H
复制文件,还可以将提交H
作为 merge commit ,将历史记录从提交Q
重新连接到 都提交:Q
和 P
。也就是说,假设您最终得到:
H
现在,当Git遍历提交历史时,它会变为:...--F--G--H <-- master
\ \
N--O--P----Q--R <-- branch
,R
,Q
和and H
,P
和and {{ 1}},G
和-O
,依此类推。也就是说,F
遍历了实际的历史记录,一次完成了一次提交,并通过一种复杂的方法来跟踪历史记录中的分叉,其中提交N
和git log
合并形成提交H
。
进行合并的缺点很明显:这是合并。默认情况下,它将引入某些共同祖先以来的 all 更改-因为在P
和Q
之前N
和F
之前的任何提交最终导致返回 shared 提交:这是两个分支上的提交。您不一定必须 commit 进行这些更改,甚至不必进行任何更改:您可以使提交branch
的快照与提交master
的快照匹配,当然除了您想要的新目录。
(进行这种合并的方法有多种。如何完全实现合并是另一个StackOverflow问题,已经很好地回答了这个问题。请参见(Git Merging) When to use 'ours' strategy, 'ours' option and 'theirs' option?和VonC's answer to a different question here。这里有很多选择,但是如果您完全想使用Q
,则可能要从P
开始,然后使用git merge -s ours --no-commit
提取文件,然后再将-s ours
作为合并。)
合并的优点是将历史联系在一起,这样git checkout <commit> -- <path>
可以从合并Q
到提交git log
, (重新命名)文件的实际历史记录的来源。缺点是它将历史联系在一起,因此从那时起,Git认为将Q
与H
合并的正确结果是H
,甚至如果您以后改变主意。
如果合并不是您想要的,则可能需要提交加上日志消息。