Python调用git mktree时,如何解决内存文件对象无文件描述符问题?
用Python 3.11.2的
subprocess.Popen实现git mktree < foo.txt并避免创建物理文件 环境搭建脚本
执行以下bash脚本可复现场景:
#!/bin/bash export GIT_AUTHOR_NAME=foo export GIT_AUTHOR_EMAIL=foo@bar.com export GIT_AUTHOR_DATE="Tue Jun 4 21:40:15 2024 +0800" export GIT_COMMITTER_NAME=foo export GIT_COMMITTER_EMAIL=foo@bar.com export GIT_COMMITTER_DATE="Tue Jun 4 21:40:15 2024 +0800" rm -rf foo git init foo mkdir -p foo/hello echo hello > foo/hello/hello.txt echo hello > foo/hello.txt echo world > foo/world.txt git -C foo add . git -C foo commit -m 'hello world' git -C foo log --no-decorate git -C foo ls-tree HEAD hello.txt git -C foo ls-tree HEAD world.txt
预期输出
执行上述脚本后,会输出提交记录及两个blob条目:
commit d2b25fd15c1435f515dd6379eca8d691dde6abeb Author: foo <foo@bar.com> Date: Tue Jun 4 21:40:15 2024 +0800 hello world 100644 blob ce013625030ba8dba906f756967f9e9ca394464a hello.txt 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 world.txt
初始实现代码
目标是创建仅包含hello.txt和world.txt的树对象,初始代码通过物理文件foo.txt实现:
import subprocess # 获取hello.txt的blob条目 o, e = subprocess.Popen( ['git', 'ls-tree', 'HEAD', 'hello.txt'], stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() line1 = o.decode() # 获取world.txt的blob条目 o, e = subprocess.Popen( ['git', 'ls-tree', 'HEAD', 'world.txt'], stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() line2 = o.decode() # 将两行内容写入foo.txt with open('foo.txt', 'w') as f: f.write(line1) f.write(line2) # 从foo.txt创建新的树对象 with open('foo.txt') as f: o, e = subprocess.Popen( ['git', 'mktree'], stdin=f, stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() tree = o.decode() print(f'created tree object {tree}')
尝试用内存文件替代物理文件时的报错
为避免创建和删除物理文件,尝试使用io.StringIO作为输入,但执行以下代码时出现异常:
import subprocess import io line1 = '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\thello.txt\n' line2 = '100644 blob cc628ccd10742baea8241c5924df992b5c019f71\tworld.txt\n' with io.StringIO(line1 + line2) as f: o, e = subprocess.Popen( ['git', 'mktree'], stdin=f, stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() tree = o.decode() print(f'created tree object {tree}')
异常信息:
Traceback (most recent call last): File "<stdin>", line 2, in <module> File "C:\Python311\Lib\subprocess.py", line 892, in __init__ errread, errwrite) = self._get_handles(stdin, stdout, stderr) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Python311\Lib\subprocess.py", line 1339, in _get_handles p2cread = msvcrt.get_osfhandle(stdin.fileno()) ^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno
原因是io.StringIO没有文件描述符,无法被subprocess.Popen的stdin参数直接使用。
解决方案
通过subprocess.Popen的communicate(input=...)方法直接传入字节数据,无需使用文件对象。注意必须传入字节类型数据,若传入字符串会生成内容错误的树对象:
import subprocess # 获取hello.txt的blob条目(直接保留字节类型) line1, _ = subprocess.Popen( ['git', 'ls-tree', 'HEAD', 'hello.txt'], stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() # 获取world.txt的blob条目(直接保留字节类型) line2, _ = subprocess.Popen( ['git', 'ls-tree', 'HEAD', 'world.txt'], stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate() # 直接传入字节数据创建树对象 o, e = subprocess.Popen( ['git', 'mktree'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, env={'GIT_DIR': 'foo/.git'}, ).communicate(input=line1+line2) tree = o.decode().strip() print(f'created tree object {tree}')
内容的提问来源于stack exchange,提问作者ElpieKay
相关产品推荐
相关产品推荐

