我一直在尝试使用Python中的GitPython软件包在其历史记录中的每次提交时复制GitHub存储库,并且当它在我的代码中途出现时会遇到此错误。
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
cmdline: git reset --mixed HEAD~1 --
stderr: 'fatal: Failed to resolve 'HEAD~1' as a valid revision.'
这是我一直在运行的代码:
from git import *
import os, shutil
repo = Repo(repo_path)
commits = list(repo.iter_commits('master'))
for c in commits:
# reset to previous commit
repo.head.reset('HEAD~1', index = True, working_tree = True)
# unique SHA key
sha = c.name_rev.split()[0]
shutil.copytree(repo_path, destination_path)
可能由于合并导致此错误吗?如果是这样,如何解决它,以便可以在回购的master分支中获得所有提交?
最佳答案
在开始回答之前,我会说:我不清楚您为什么要这么做。例如,您可以使用git archive
创建任何给定提交的tar或zip文件。例如:
git archive -o foo.tar v2.3.1
从标记为
foo.tar
的修订版本中制作一个v2.3.1
文件。要从master
可以访问的所有修订中制作许多tar或zip文件,您可以编写:git rev-list master | while read hash; do
git archive -o /path/to/$hash.zip $hash
done
并完成它。
可能由于合并导致此错误吗?
是的,可能会。
如果是这样,如何解决它,以便可以在回购的master分支中获得所有提交?
当心:
master
中的提交可能包括许多其他分支中的提交。执行此操作时:
commits = list(repo.iter_commits('master'))
您可以从名称
master
中获得所有提交的完整列表,从最新的列表开始。例如,假设master
指向要提交的图形。代替每个实际的提交哈希ID,我将使用一个大写字母表示提交:A--B--C------G <-- master
\ /
D--E--F <--- develop
该存储库有七次提交(计数!)。所有七个提交均处于启用状态,即可以从分支
master
进行访问。七个提交中的六个在分支develop
上。名称master
标识提交G
,它是一个合并提交。名称develop
标识提交F
,不是。执行此操作时:
repo.head.reset('HEAD~1', index = True, working_tree = True)
您让Python告诉Git将当前提交(这是其中的七个)解析到它的第一个父提交,然后将存储库的“当前提交”的概念更改为刚刚找到的提交。假设您从
HEAD
(当前提交)开始为提交G
。然后HEAD~1
是提交C
。在这里,事情变得有些复杂。
repo.head
对象代表Git自己的HEAD
,它始终是两个不同项之一。但是,在这种情况下,它很明显是一个符号引用,指向master
。我尚未对此进行测试,但是几乎可以肯定的是,GitPython在这里忠实地重现了Git自己的行为,并且根据您的参数和您的参数,使用git reset
,--soft
或--mixed
之一等效于--hard
是--hard
的代码(奇怪的是,此处显示失败的命令使用--mixed
;您的代码与您的发布不匹配,或者更有可能的是,GitPython使用了额外的步骤)。因此,这最终使名称master
指向新选择的提交C
:A--B--C <-- master
\
D--E--F <-- develop
提交
G
到哪里去了?好吧,虽然没有真正的地方,但是现在已经“丢失”了:很难找到它,并且在到期后,它实际上会被完全删除。因此,提交G
实际上已经消失了。 (如果我们知道它的哈希,可以将其复活:我们可以强制master
用另一个git reset
或同等名称再次指向它。您在变量commits
中的提交列表仍然列出了其哈希,因此这是众多哈希之一我们可以找到并恢复它的方式。)现在,您可以使用commit
C
来编写主循环主体代码:sha = c.name_rev.split()[0]
shutil.copytree(repo_path, destination_path)
您已经遍历了列表中的七个提交之一,在认为它是提交
C
的同时复制了提交G
(在repo.iter_commits('master')
中的第一个提交是提交G
,因为这是一个master
指着)。现在您可以循环使用第二个了。但是,存储库现在只有六个提交,并且
master
指向提交C
。现在,您再执行一次git reset --hard
,从图片中删除提交C
,剩下的就是:A--B <-- master
\
D--E--F <-- develop
现在,您可以使用commit
B
进行一些操作(而c
中的for c in commits
在七次提交中的第二次提交中,以一定顺序列出)–目前尚不清楚repo.iter_commits
使用的顺序,但它可能会运行git rev-list
并因此获得默认顺序;如果是这样,请参见git rev-list
文档)。现在,您执行另一个
git reset --hard
。这次,不会忘记提交B
:提交D
会记住它。但是master
最后指向提交A
:A <-- master
\
B--D--E--F <-- develop
您可以使用提交
A
来完成您的任务,而for c in commits
则是执行七个的第三次提交。现在,您要求Git查找
A
的第一个父提交...但是A
没有第一个父提交,也没有任何父提交。提交A
是有史以来的第一次提交;这是一个根提交。此时,git reset
完全失败。您已经通过仅跟随第一个父级链接来遍历从master
可以到达的四个提交。从master
可以访问的其他三个提交在某一时刻要求紧随第二个父提交。您还删除了您访问的四个提交中的两个;剩下两个只是因为它们可以通过另一个名称访问。请注意,您可能具有相同的图形,但不再有名称
develop
:A--B--C------G <-- master
\ /
D--E--F
在这种情况下,第一个擦除
git reset
的G
也会擦除对D-E-F
链的访问,因为G
是该访问的关键:现在是G^2
,它是commit的第二个父级G
,即找到F
。是F
找到E
,而E
找到D
;因此,失去G
会失去所有这些,而最终剩下的只是:A--B--C <-- master
可见。 (和以前一样,所有“已擦除”的提交都会在宽限期内停留,只要您可以再次找到它们,就可以将其复活。)
...我该如何解决
使用完全不同的算法,和/或明智地选择提交。仅仅因为从某个分支名称可以到达七个(或其他数目)提交,并不意味着所有七个(或任何其他数目)都作为第一父级链接在一起。
请注意,即使在完全线性的设置中,例如:
A--B <-- master
您将具有两个提交的列表(以
B
然后A
的顺序),但是您只能运行一次git reset HEAD~1
,才能从B
退回到A
。一旦进入A
,就无法再后退。在这种情况下,与执行提交相比,您必须退回的步数要少一些。无论做什么,您都应该首先执行提交。对于我来说,GitPython如何处理“分离的HEAD”并不是立即显而易见的,尽管如果您想直接从Python代码访问文件,那么使用分离的HEAD并没有多大意义。但是,如果您要运行
shutils.copytree
,则最好只用shell脚本编写整个过程,这要简单得多:Git充满了shell脚本,并且旨在与它们很好地协同工作,并且需要shell解释器为了使Git完全起作用而存在,因此,如果您具有Git,则具有外壳解释器。
关于python - 如何使用Python中的GitPython包在每次提交时制作GitHub存储库的副本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45472397/