首页 > 解决方案 > 如何将 git 中 __init__.py 的内容(并维护历史记录)传输到另一个文件,同时仍然保持空的 __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

标签: pythongittouchmv

解决方案


TL;博士

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

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

提交本身相对简单:每个文件都有每个文件的完整快照,以及一些包含提交作者姓名和电子邮件地址等内容的元数据。任何一次提交的元数据都包括任何早期提交的原始哈希 ID。大多数提交,称为普通提交,都有一个更早的提交,而那个更早的提交也有一个快照和元数据,它指向一个更早的提交,依此类推。这就是快照和元数据历史的方式。

考虑到这一点,请注意git log -p或通过以下方式git 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 运行这种 diff 时,它可以(现在默认情况下)会查找重命名的文件。但是,它找到这些重命名的方法并不完美。它的作用是这样的:

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

当你跑的时候:

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

该设置非常适合 Git:所有其他文件都 100% 左右匹配,剩下的列表是左侧有__init__.py,右侧有utils.py,内容匹配 100%。所以一定要改名!(在某种程度上,这些文件被命名为package/__init__.pyetc.:Git 将整个内容(包括斜杠)视为一个文件名。但我省略 ' 会更短package/,您可能会将这些视为 files-in-a -folder 或 files-in-a-directory 自己。)

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

如果你现在做一个新的提交,在这种情况下,git diff将继续发现以这种方式设置的东西,至少直到某个神话般的未来 Git 足够聪明地注意到,即使两个文件具有相同的名称,一个差异表明“重命名然后重新创建”在某种程度上是优越的。3

但是,如果您进行了仅包含重命名步骤的提交,然后创建一个新__init__.py文件以使包正常工作并将其作为第二次提交提交,git log -p并且git show将继续检测重命名。这样做的好处是,当它检测到重命名时git log --follow,它会一步一步地通过更改它正在寻找的名称来工作。这样做的缺点是你会有一个被故意破坏的提交。您可能应该在其提交消息中注意到这一点。如果您必须经常执行此类操作,并且提交消息始终标记此类提交,则您可以在期间自动跳过此类提交git bisect通过编写 bisect 脚本测试器来检查这些标记。


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

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

3如果某个未来git diff 如此聪明,未来的 Git 作者将不得不考虑这是否会破坏某些脚本。幸运git diff的是瓷器命令,而不是管道命令,但是git diff-tree其他管道命令将需要新选项。


推荐阅读