假设我在目录text.txt
中有一个包含文件dir_a
的git repo。稍后,我决定将text.txt
移动到名为dir_b
的新目录。
过了一段时间,我决定使用dir_b
将git subtree split
拆分到自己的独立git存储库中。默认情况下,dir_b
存储库中最早的提交是我将text.txt
从dir_a
移动到dir_b
的提交,这是不幸的,因为例如责备不会按预期发挥作用。
有没有办法在新的git仓库中保留text.txt
时dir_a
所做的更改?
为了清楚起见,在原始存储库中,我将text.txt
从dir_a
移动到dir_b
的提交成功将移动操作注册为重命名,例如, git diff
在那里正常运作。我的问题是,在新存储库中,移动前提交的提交不会转移到新存储库。
答案 0 :(得分:4)
编辑:我完全错过了git subtree split -P prefix
部分内容。原来的答案仍然适用,但可能有致命的转折。
当你运行git subtree split -P prefix [ options ] [ commit-range ]
时,你告诉Git 复制一些新的提交。你有Git复制任何提交包含给定 prefix
中的任何文件,但有这些更改:
prefix
开头的所有文件。prefix
(和斜杠)。(您也可以使用git filter-branch
执行此操作,但速度会比git subtree split
慢,并且需要您首先创建一个新分支进行过滤。)
结果是一个新的,不相交的提交图(或子图,因为它现在被添加到您的主提交图中),以第一次复制的提交为根,并在最后复制的提交时终止。 (复制过程必须以Git通常的向后方式枚举提交,来自单个提示,而不是来自多个提示。一旦所有提交都以这种方式发现,复制将从root / last-enumerated,必须提示。)然后,您可以使用git subtree
-b branch
选项为此新子图提供分支名称。如果您没有给它命名,那么您有一个短期(默认为14天),在此期间您可以使用git subtree split
打印的提示提交哈希ID执行某些操作,之后副本符合条件用于自动垃圾收集。
作为简要说明,请考虑以下图表:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
让我们说提交A
在README
(没有别的),B
添加项目的第一部分,C-D-E
更多的是项目F
和G
来自功能分支,并添加一个名为subbie
的子树,其中包含各种文件,H
合并子树,I
它&# 39; s重命名为feature
,在J
中没有任何反应,并且K
feature/README_TOO
已添加。
如果您现在拆分 feature
作为子树,这会使Git复制提交:
I
:feature
首次显示为名称,例如包含feature/__init.py
和feature/impl.py
。K
:feature/README_TOO
出现。作为一个新的,独立的提交子图,它看起来像这样:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
I'--K' <-- dash-b-argument
请注意,我们未复制 F
,G
和H
:他们没有名称以feature/
开头的文件。提交J
确实有这样的文件,但它们与提交I
中的文件相同,所以我们跳过它。同时,提交I'
和K'
中的文件的名称不是feature/__init__.py
,依此类推,而只是__init__.py
等等。
正如我在原始答案中所述,存储库中的历史记录是提交。我们从分支提示开始并向后工作来查看历史记录。如果我们从K'
开始并向后工作到I'
,那么历史就是那两个提交。要发现重命名,我们必须还至少复制提交F
和G
,也许H
也是如此(这里没有任何内容) H
要合并此次,因为我们会跳过A-B-C-D-E
,因此我们可能只是完全放弃H
。但要做到这一点,我们必须知道保留subbie/*
。
您可以修改git subtree
代码以允许其他保留为前缀的参数。但事后并没有明确的方法来扭转这种情况。基本git subtree
代码依赖于唯一的前缀: 总是被剥离,所以为了反转转换,我们总是将其添加回来。两个明显的选项是:永远不会删除任何前缀(所以永远不要添加任何东西),或者要求额外的,未剥离的前缀永远不会与#34;冲突。前缀剥离的名称。也就是说,给定任意复制的提交,如果其快照具有名为pa/th/to/file.ext
的文件,则pa/th/to
不 a&#34;保留在原位&#34;前缀(因此它会添加-P
前缀),或者pa/th/to
是这样的前缀(因此不会添加任何内容)。
在Git中,文件没有历史记录。没有什么可以保留的!
在Git中,只有提交有 - 或者更确切地说,是 - 历史。每次提交都是源树的完整快照,加上一些元数据:名称和电子邮件以及时间戳(作为提交的作者),另一个名称/电子邮件/时间戳三元组(用于提交者);提交日志消息;并且 - 对于形成历史至关重要 - 父提交的ID 。
(有些提交,我们称之为 merge commits ,有两个或更多个父母。至少有一个提交 - 即第一个提交 - 有没有父母;我们打电话这是一个 root commit 。但是大多数提交只有一个父,这通常是某个分支的提示,就在提交者做出 new 提交之前< em>成为该分支的一角。)
通过将提交与其父级进行比较,我们发现了一段时间内发生的事情。如果前一个(父)提交有10个文件,后续(子)提交有11个文件,那么有人必须添加一个文件。如果子提交在README.txt
中有一个新行20,则必须添加该行。但我们只是通过比较父和子来动态地发现这些。这是由承诺形成的历史。
git blame
代码将从子项回到父项(然后将该父项视为另一个父项的另一个子项),搜索从其他文件中获取的行,或者将整个文件从一个位置重命名为另一个位置。 井搜索的工作原理是一个单独的问题 - 但作为一般规则,如果父文件中存在某些文件p/a/t/h.ext
但子文件中不存在,则存在其他文件n/e/w.name
在孩子而不是父母的情况下,Git会将这两个文件放入&#34;候选人进行重命名检测&#34;列表。
如果两个不同命名的文件是绝对的,100%,逐位相同,Git几乎总是 1 配对它们。它们变得越不相同,Git就不太可能将它们配对。此配对具有控制旋钮:在git diff
和朋友中,它们是--find-renames
值。还有一个--find-copies
和一个--find-copies-harder
。在git blame
中,-C
参数以稍微不同的方式控制事物。我没有对此进行过多的实验来确定它是如何工作的,但是基于the documentation,一个或两个-C
参数肯定应该检测整个文件的重命名。
1 对于git diff
,重命名发现在2.9之前的Git版本中默认完全 dis ,但 en 能够默认情况下,在Git 2.9及更高版本中。您可以将diff.renames
设置为true
以启用它,而无需在较旧版本的Git中配置特定的-M
/ --find-renames
阈值。
还有一个最大配对队列大小,可配置为diff.renameLimit
。虽然重命名目录中的每个文件 - 这是 Git如何处理重命名目录 - 更有可能能够命中它,但很少达到这个限制。多年来,默认限制一直在增长;它曾经是100,然后是200,现在是400个文件。