在将文件移动到正在拆分的目录之前保留文件的历史记录
Preserve history of file from before it was moved into the directory being split
假设我有一个 git 存储库,在目录 dir_a
中包含一个文件 text.txt
。后来,我决定将 text.txt
移动到一个名为 dir_b
.
的新目录
一段时间后,我决定使用 git subtree split
将 dir_b
拆分为独立的 git 存储库。默认情况下,dir_b
存储库中最早的提交是我将 text.txt
从 dir_a
移动到 dir_b
的提交,这很不幸,因为例如责备不会按预期工作。
有没有办法在新的 git 存储库中保留 text.txt
还在 dir_a
时所做的更改?
为了清楚起见,在原始存储库中,我将 text.txt
从 dir_a
移动到 dir_b
的提交成功地将移动操作注册为重命名,例如git diff
在那里正常工作。我的问题是,在 新存储库 中,移动之前所做的提交不会转移到新存储库。
编辑:我完全错过了 git subtree split -P <em>prefix</em>
部分。原来的答案仍然适用,但有一个 possibly-fatal 扭曲。
当你 运行 git 子树拆分 -P <em>prefix</em> [ <em>options</em> ] [ <em>commit-range</em> ]
,你告诉 Git 复制 一些提交到新的。您已 Git 复制包含给定 prefix
中任何文件的任何提交,但进行了以下更改:
- 丢弃所有不以给定
prefix
. 开头的文件
- 重命名剩余的文件以去除
prefix
(和斜杠)。
- 如果提交与 previously-copied 提交匹配,则将其完全删除。
(您也可以使用 git filter-branch
执行此操作,尽管它会比 git subtree split
慢,并且需要您先创建一个新分支进行过滤。)
结果是一个新的、不相交的提交图(或子图,因为它现在已添加到您的主提交图中),植根于 first-copied 提交并终止于 last-copied 提交。 (复制过程必须枚举提交,以 Git 通常的向后方式,从单个提示提交,而不是从多个提示提交。一旦所有提交都以这种方式找到,复制从 root / last-enumerated,给小费,这是必须的。)然后你可以使用 git subtree
的 -b <em>branch[=169 给这个新的 sub-graph 一个分支名称=]
选项。如果你不给它一个名字,你有一个很短的时间(默认为 14 天),在此期间你可以使用 git subtree split
打印的提示提交哈希 ID 做一些事情,之后副本有资格自动garbage-collection.
作为简要说明,请考虑下图:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
假设提交 A
在 README
中(没有别的),B
添加项目的第一部分,C-D-E
是项目的更多部分, F
和 G
来自功能分支并添加名为 subbie
的子树包含各种文件,H
合并子树,在 I
中它被重命名为 feature
,在 J
中没有任何反应,在 K
中添加了 feature/README_TOO
。
如果你现在 split feature
作为一个子树,这使得 Git 复制提交:
I
:feature
首先作为名称出现,包含例如feature/__init.py
和feature/impl.py
,例如
K
: feature/README_TOO
出现。
作为一个新的、独立的 sub-graph 提交,它看起来像这样:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
I'--K' <-- dash-b-argument
请注意,我们没有复制 F
、G
和H
:它们没有以[开头的文件=42=]。提交 J
确实有这样的文件,但它们与提交 I
中的相同,所以我们跳过它。同时,提交 I'
和 K'
中文件的 names 不是 feature/__init__.py
等等,而是简单的 __init__.py
和等等。
正如我在原始答案中指出的那样,存储库中的历史 是 提交。我们通过从分支提示提交开始并向后工作来查看历史记录。如果我们从 K'
开始并向后工作到 I'
,历史就是这两个提交。要发现重命名,我们必须 也 复制提交 F
和 G
至少,也许 H
(没有什么 H
这次合并,因为我们会跳过 A-B-C-D-E
,所以我们可能会完全放弃 H
)。但要做到这一点,我们必须知道保留 subbie/*
.
您可以修改 git subtree
代码以允许额外的 preserved-as-as 前缀参数。但是,没有明确的方法来扭转这个 after-the-fact。基本的 git subtree
代码依赖于一个唯一的前缀:它 是 总是被剥离,所以为了反转转换,我们总是把它加回去。两个明显的选择是:永远不要去掉任何前缀(所以永远不要添加任何东西),或者要求额外的 non-stripped 前缀从不 "collide with" prefix-stripped 名称。也就是说,给定任何任意复制的提交,如果它的快照有一个名为 pa/th/to/file.ext
的文件,要么 pa/th/to
是 而不是 一个 "preserved in place" 前缀(所以它获取 -P
前缀添加回来),否则 pa/th/to
是 这样的前缀(因此它没有添加任何内容)。
原回答
在 Git 中,文件没有历史记录。没有什么可保留的!
在 Git 中,只有 提交 有——或者更确切地说,是——历史。每个提交都是源代码树的完整快照,加上一些 meta-data:姓名和电子邮件以及时间戳(作为提交的作者),另一个 name/email/timestamp 三元组(对于提交者);提交日志消息;并且——对于形成历史至关重要——parent commit.
的 ID
(一些提交,我们称之为 合并提交 ,有两个或更多 parent。至少有一个提交——即第一次提交——有 no parents;我们称之为 root 提交。但大多数提交只有一个 parent,通常是提交那是一些树枝的尖端,jus在提交者做出 new 提交之前 成为 该分支的尖端。)
通过将提交与其 parent 进行比较,我们可以了解随着时间的推移发生了什么。如果之前的 (parent) 提交有 10 个文件,而随后的 (child) 提交有 11 个文件,那么肯定有人添加了一个文件。如果 child 提交在 README.txt
中有新的第 20 行,他们必须添加该行。但是我们只是动态地发现这些,通过比较parent和child。这就是由提交形成的历史。
git blame
代码将从 child 返回到 parent(然后将 parent 视为 child 的另一个 child 122=]another parent), 搜索取自其他文件的行,或搜索从一个位置重命名为另一个位置的整个文件。 以及 搜索如何工作是另一回事——但作为一般规则,如果某些文件 p/a/t/h.ext
存在于 parent 而不是 child , n/e/w.name
存在于 child 而 parent 中不存在,Git 会将这两个文件放入 "candidates for rename detection" 列表中。
如果两个 differently-named 文件绝对 100%,bit-for-bit 相同,Git 几乎总是 1 将它们配对。他们成为less-identical,less-likelyGit将他们配对。此配对有控制旋钮:在 git diff
和朋友中,它们是 --find-renames
值。还有一个 --find-copies
和一个 --find-copies-harder
。在 git blame
中,-C
参数以一种稍微不同的方式控制事物。我还没有对此进行足够的实验来确定它是如何工作的,但是一个或两个 -C
参数肯定应该检测到 whole-file 重命名,基于 the documentation.
1对于git diff
,rename-finding在Git中默认完全禁用 2.9 之前的版本,但 en 在 Git 2.9 及更高版本中默认启用。在旧版本的 Git.
中,您可以将 diff.renames
设置为 true
以启用它,而无需配置特定的 -M
/ --find-renames
阈值
还有最大 pairing-queue 大小,可配置为 diff.renameLimit
。达到这个限制是很少见的,尽管重命名目录中的每个文件——如何 Git 处理重命名目录——更有可能 able 击中它。默认限制多年来一直在增长;以前是100个,后来是200个,现在是400个文件
假设我有一个 git 存储库,在目录 dir_a
中包含一个文件 text.txt
。后来,我决定将 text.txt
移动到一个名为 dir_b
.
一段时间后,我决定使用 git subtree split
将 dir_b
拆分为独立的 git 存储库。默认情况下,dir_b
存储库中最早的提交是我将 text.txt
从 dir_a
移动到 dir_b
的提交,这很不幸,因为例如责备不会按预期工作。
有没有办法在新的 git 存储库中保留 text.txt
还在 dir_a
时所做的更改?
为了清楚起见,在原始存储库中,我将 text.txt
从 dir_a
移动到 dir_b
的提交成功地将移动操作注册为重命名,例如git diff
在那里正常工作。我的问题是,在 新存储库 中,移动之前所做的提交不会转移到新存储库。
编辑:我完全错过了 git subtree split -P <em>prefix</em>
部分。原来的答案仍然适用,但有一个 possibly-fatal 扭曲。
当你 运行 git 子树拆分 -P <em>prefix</em> [ <em>options</em> ] [ <em>commit-range</em> ]
,你告诉 Git 复制 一些提交到新的。您已 Git 复制包含给定 prefix
中任何文件的任何提交,但进行了以下更改:
- 丢弃所有不以给定
prefix
. 开头的文件
- 重命名剩余的文件以去除
prefix
(和斜杠)。 - 如果提交与 previously-copied 提交匹配,则将其完全删除。
(您也可以使用 git filter-branch
执行此操作,尽管它会比 git subtree split
慢,并且需要您先创建一个新分支进行过滤。)
结果是一个新的、不相交的提交图(或子图,因为它现在已添加到您的主提交图中),植根于 first-copied 提交并终止于 last-copied 提交。 (复制过程必须枚举提交,以 Git 通常的向后方式,从单个提示提交,而不是从多个提示提交。一旦所有提交都以这种方式找到,复制从 root / last-enumerated,给小费,这是必须的。)然后你可以使用 git subtree
的 -b <em>branch[=169 给这个新的 sub-graph 一个分支名称=]
选项。如果你不给它一个名字,你有一个很短的时间(默认为 14 天),在此期间你可以使用 git subtree split
打印的提示提交哈希 ID 做一些事情,之后副本有资格自动garbage-collection.
作为简要说明,请考虑下图:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
假设提交 A
在 README
中(没有别的),B
添加项目的第一部分,C-D-E
是项目的更多部分, F
和 G
来自功能分支并添加名为 subbie
的子树包含各种文件,H
合并子树,在 I
中它被重命名为 feature
,在 J
中没有任何反应,在 K
中添加了 feature/README_TOO
。
如果你现在 split feature
作为一个子树,这使得 Git 复制提交:
I
:feature
首先作为名称出现,包含例如feature/__init.py
和feature/impl.py
,例如K
:feature/README_TOO
出现。
作为一个新的、独立的 sub-graph 提交,它看起来像这样:
C--D--E
/ \
A--B H--I--J--K <-- master
\ /
F-----G
I'--K' <-- dash-b-argument
请注意,我们没有复制 F
、G
和H
:它们没有以[开头的文件=42=]。提交 J
确实有这样的文件,但它们与提交 I
中的相同,所以我们跳过它。同时,提交 I'
和 K'
中文件的 names 不是 feature/__init__.py
等等,而是简单的 __init__.py
和等等。
正如我在原始答案中指出的那样,存储库中的历史 是 提交。我们通过从分支提示提交开始并向后工作来查看历史记录。如果我们从 K'
开始并向后工作到 I'
,历史就是这两个提交。要发现重命名,我们必须 也 复制提交 F
和 G
至少,也许 H
(没有什么 H
这次合并,因为我们会跳过 A-B-C-D-E
,所以我们可能会完全放弃 H
)。但要做到这一点,我们必须知道保留 subbie/*
.
您可以修改 git subtree
代码以允许额外的 preserved-as-as 前缀参数。但是,没有明确的方法来扭转这个 after-the-fact。基本的 git subtree
代码依赖于一个唯一的前缀:它 是 总是被剥离,所以为了反转转换,我们总是把它加回去。两个明显的选择是:永远不要去掉任何前缀(所以永远不要添加任何东西),或者要求额外的 non-stripped 前缀从不 "collide with" prefix-stripped 名称。也就是说,给定任何任意复制的提交,如果它的快照有一个名为 pa/th/to/file.ext
的文件,要么 pa/th/to
是 而不是 一个 "preserved in place" 前缀(所以它获取 -P
前缀添加回来),否则 pa/th/to
是 这样的前缀(因此它没有添加任何内容)。
原回答
在 Git 中,文件没有历史记录。没有什么可保留的!
在 Git 中,只有 提交 有——或者更确切地说,是——历史。每个提交都是源代码树的完整快照,加上一些 meta-data:姓名和电子邮件以及时间戳(作为提交的作者),另一个 name/email/timestamp 三元组(对于提交者);提交日志消息;并且——对于形成历史至关重要——parent commit.
的 ID(一些提交,我们称之为 合并提交 ,有两个或更多 parent。至少有一个提交——即第一次提交——有 no parents;我们称之为 root 提交。但大多数提交只有一个 parent,通常是提交那是一些树枝的尖端,jus在提交者做出 new 提交之前 成为 该分支的尖端。)
通过将提交与其 parent 进行比较,我们可以了解随着时间的推移发生了什么。如果之前的 (parent) 提交有 10 个文件,而随后的 (child) 提交有 11 个文件,那么肯定有人添加了一个文件。如果 child 提交在 README.txt
中有新的第 20 行,他们必须添加该行。但是我们只是动态地发现这些,通过比较parent和child。这就是由提交形成的历史。
git blame
代码将从 child 返回到 parent(然后将 parent 视为 child 的另一个 child 122=]another parent), 搜索取自其他文件的行,或搜索从一个位置重命名为另一个位置的整个文件。 以及 搜索如何工作是另一回事——但作为一般规则,如果某些文件 p/a/t/h.ext
存在于 parent 而不是 child , n/e/w.name
存在于 child 而 parent 中不存在,Git 会将这两个文件放入 "candidates for rename detection" 列表中。
如果两个 differently-named 文件绝对 100%,bit-for-bit 相同,Git 几乎总是 1 将它们配对。他们成为less-identical,less-likelyGit将他们配对。此配对有控制旋钮:在 git diff
和朋友中,它们是 --find-renames
值。还有一个 --find-copies
和一个 --find-copies-harder
。在 git blame
中,-C
参数以一种稍微不同的方式控制事物。我还没有对此进行足够的实验来确定它是如何工作的,但是一个或两个 -C
参数肯定应该检测到 whole-file 重命名,基于 the documentation.
1对于git diff
,rename-finding在Git中默认完全禁用 2.9 之前的版本,但 en 在 Git 2.9 及更高版本中默认启用。在旧版本的 Git.
diff.renames
设置为 true
以启用它,而无需配置特定的 -M
/ --find-renames
阈值
还有最大 pairing-queue 大小,可配置为 diff.renameLimit
。达到这个限制是很少见的,尽管重命名目录中的每个文件——如何 Git 处理重命名目录——更有可能 able 击中它。默认限制多年来一直在增长;以前是100个,后来是200个,现在是400个文件