如何将 __init__.py 中 git 中的内容(并保留历史记录)传输到另一个文件,同时仍保持空 __init__.py

How to transfer contents from __init__.py in git (and maintain history) to another file whilst still keeping empty __init__.py

我创建了一个从 __init__.py 导入的导入方案,而不是 __init__.py 从它的模块导入。

为了解决这个问题,我 运行:

$ git mv package/__init__.py package/utils.py

这看起来是正确的:

Changes to be committed:
(use "git restore --staged <file>..." to unstage)
    renamed:    package/__init__.py -> package/utils.py

但是,如果我 运行 以下内容:

$ touch package/__init__.py

这是我看到的:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   package/__init__.py
    new file:   package/utils.py

我怎样才能 git 执行以下操作?

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   package/utils.py
    new file:   package/__init__.py

TL;DR

如果您愿意,可以进行两次提交。这没有太多价值,但也有一点。它的一些值是正的,一些是负的。这是你的选择。

Git 没有文件历史记录。 Git 已提交;提交 历史。

提交本身相对简单:每个提交都有每个文件的完整快照,加上一些元数据,其中包含提交作者的姓名和电子邮件地址等内容。任何一次提交的元数据包括任何早期提交的原始哈希 ID。大多数提交,称为 ordinary 提交,有一个较早的提交,并且那个较早的提交也有一个快照和元数据,它指向另一个更早的提交,等等。这就是快照和元数据历史的方式。

考虑到这一点,请注意 git log -pgit show 显示 普通提交:

  1. 显示其元数据(有趣的部分),带格式;然后
  2. 显示该提交中更改的内容。

为了实现第2项,Git实际上是将提交及其父都提取到一个临时区域(内存中),然后比较两组快照文件的数量。1 此比较采用 diff (git diff) 的形式,即两个快照之间的差异。

git status 命令也运行 git diff。事实上,它运行 git diff 两次 ,一次是为了比较当前(又名 HEAD)提交与 Git 的索引——你提议的下一次提交,由任何 git add 更新产生——并再次将 Git 的索引与您的工作树进行比较,以防您忘记 git add。 (这种 diff 形式至少使用了一个未保存在提交中的快照,并且两种形式之一使用真实文件,这比使用 Git 的快捷哈希 ID 技巧需要更多的工作。但最终结果是一样。)

当 Git 运行这种差异时,它可以——现在,默认情况下——查找 重命名的 文件。但是,它查找这些重命名的方法并不完善。它的作用是这样的:

  • 列出左侧的所有文件(“之前”或“旧版本”)。
  • 列出右侧的所有文件(“之后”或“新版本”)。
  • 如果左右有一对文件具有相同的名称,将它们配对:它们必须相同文件.
  • 取所有剩下的、未配对的名字。其中一些可能是 重命名 。对照所有右侧文件检查所有左侧文件。2 如果左侧文件与右侧文件“足够相似”,则将最佳匹配配对。 (在大多数情况下,100% 相同的匹配在这里进行得更快,并减少剩余的未配对名称堆,所以 Git 总是先这样做。)

当你 运行:

git mv package/__init__.py package/utils.py

设置非常适合 Git:每个 other 文件左右匹配 100%,剩下的列表是左侧有 __init__.py 右侧有 utils.py 并且内容匹配 100%。所以那一定是重命名! (在某种程度上,这些文件被命名为 package/__init__.py 等:Git 将包括斜杠在内的整个文件视为一个文件名。但对我来说,省去 package/,您可能自己将这些视为文件夹中的文件或目录中的文件。)

但是,一旦您创建了一个名为 __init__.py 的新文件,Git 现在就同时拥有了名为 __init__.py 的左侧和右侧文件,加上这个剩余的右侧文件命名为 utils.py。因此 Git 将具有 相同 名称的文件配对,并留下一个无法配对的右侧文件。

如果您现在进行新的提交,在这种情况下,git diff 将继续查找以这种方式设置的内容,至少直到某个神秘的未来 Git 足够聪明地注意到,即使这两个文件具有相同的 名称,一个表示“重命名然后重新创建”的差异在某种程度上更优越。3

但是,如果您进行的提交仅包含 重命名步骤 ,然后创建一个新的 __init__.py 文件以便包正常工作并提交作为 second 提交,git log -pgit show 将恢复检测重命名。这样做的好处是 git log --follow 会在检测到重命名时通过 更改它正在寻找的名称 逐步进行工作。这样做的缺点是您将有一个提交被故意破坏。您可能应该在其提交消息中注意到这一点。如果您必须经常做这种事情,并且提交消息始终标记此类提交,您可以在 git bisect 期间自动跳过此类提交,方法是编写 bisect 脚本测试程序来检查这些标记。


1从技术上讲,Git 只比较树和 blob 的哈希 ID,这使得在大多数情况下速度非常快。

2这个检查在计算上非常昂贵,所以 Git 有很多捷径,还有一个它只是放弃的截止限制。您可以调整其中一些设置。

3要是以后的git diff就是这么聪明,以后的Git作者就得考虑了这是否会破坏一些脚本。幸运的是 git diff 是瓷器命令,而不是管道命令,但是 git diff-tree 和其他管道命令将需要新选项。