python - 如何将 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
解决方案
TL;博士
如果您愿意,可以进行两次提交。没有太多的价值,但有一点。它的一些价值是积极的,一些是消极的。这是你的选择。
长
Git 没有文件历史记录。Git 有提交;提交是历史。
提交本身相对简单:每个文件都有每个文件的完整快照,以及一些包含提交作者姓名和电子邮件地址等内容的元数据。任何一次提交的元数据都包括任何早期提交的原始哈希 ID。大多数提交,称为普通提交,都有一个更早的提交,而那个更早的提交也有一个快照和元数据,它指向一个更早的提交,依此类推。这就是快照和元数据是历史的方式。
考虑到这一点,请注意git log -p
或通过以下方式git show
显示普通提交:
- 显示其元数据(有趣的部分),并带有格式;然后
- 显示该提交中的更改。
为了实现第 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__.py
etc.: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
其他管道命令将需要新选项。
推荐阅读
- r - 有没有办法获得所有交互项的残差图?
- java - 如何将标头从控制器发送到Java中的另一个类
- node.js - 如何在节点中使用 crypto.createDecipheriv 解密流?
- ios - Xcode:'没有这样的模块' - 从 Git 中提取项目后
- python - 人类可读或工程风格漂浮在 Pandas 中?
- mysql - 具有特定条件的最长连续 SQL 查询
- shell - 詹金斯管道 - 错误的替换错误
- javascript - 反应添加新的数组方法以通过它的键返回数组的对象项
- android - 我在android studio中遇到过这些错误?
- c - float cf = *(float *)&ci; 是什么意思?在C里做什么?