You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 23:12:34