Я планирую использовать subprocess.Popen (в Python 3.11.2) для реализации git mktree < foo.txt. Теперь передо мной стоит вопрос.
Чтобы воспроизвести мою ситуацию, вот скрипт, создающий среду.
#!/bin/bash
export GIT_AUTHOR_NAME=foo
export [email protected]
export GIT_AUTHOR_DATE = "Tue Jun 4 21:40:15 2024 +0800"
export GIT_COMMITTER_NAME=foo
export [email protected]
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 <[email protected]>
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. Итак, сначала мне нужно создать новый объект дерева. (Обновление: получив правильное решение, я обнаружил, что в коде есть фатальная ошибка. Он создает объект дерева с неправильным содержимым из-за str и байтов.)
import subprocess
# get the blob entry of hello.txt
o, e = subprocess.Popen(
['git', 'ls-tree', 'HEAD', 'hello.txt'],
stdout=subprocess.PIPE,
env = {'GIT_DIR': 'foo/.git'},
).communicate()
line1 = o.decode()
# get the blob entry of world.txt
o, e = subprocess.Popen(
['git', 'ls-tree', 'HEAD', 'world.txt'],
stdout=subprocess.PIPE,
env = {'GIT_DIR': 'foo/.git'},
).communicate()
line2 = o.decode()
# write the 2 lines to foo.txt
with open('foo.txt', 'w') as f:
f.write(line1)
f.write(line2)
# create a new tree object from 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}')
Интересно, могу ли я использовать файловый объект в памяти, чтобы мне не приходилось создавать и удалять foo.txt. Поскольку 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, похоже, io.StringIO не имеет дескриптора файла.
файлно()
Возвращает базовый дескриптор файла (целое число) поток, если он существует. OSError возникает, если объект ввода-вывода не используйте файловый дескриптор.
Есть ли в памяти файловый объект, имеющий файловый дескриптор? Или есть какой-нибудь способ обойти исключение с помощью io.StringIO в subprocess.Popen?
Обновлять:
С помощью ответа @Ture Pålsson я получаю ожидаемое решение.
import subprocess
# get the blob entry of hello.txt
line1, _ = subprocess.Popen(
['git', 'ls-tree', 'HEAD', 'hello.txt'],
stdout=subprocess.PIPE,
env = {'GIT_DIR': 'foo/.git'},
).communicate()
# get the blob entry of world.txt
line2, _ = subprocess.Popen(
['git', 'ls-tree', 'HEAD', 'world.txt'],
stdout=subprocess.PIPE,
env = {'GIT_DIR': 'foo/.git'},
).communicate()
# create a tree object from the lines
# although Popen's text=True allows communicate's input to be str,
# here it should be bytes.
# str input creates a tree object with wrong content.
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}')
@OldBoy Я попробовал добавить круглые скобки, и это вызвало то же исключение. Не могли бы вы объяснить разницу между скобками и без них? Спасибо.
Если вы планируете делать что-то достаточно тяжелое, я бы рекомендовал libgit2 (например, через pygit2). Это будет проще писать в сценарии и, вероятно, значительно быстрее.
Наверное, я вчера что-то неправильно понял, вроде работает и без. Это наводит меня на мысль, что ошибка не имеет ничего общего с StreamIO.
subprocess.run поставляется из коробки практически со всем, что вы здесь делаете вручную. В настоящее время нет необходимости использовать subprocess.Popen, если только вы не хотите, чтобы процесс выполнялся одновременно.
@nneonneo Спасибо за совет. Однажды я узнал о pygit2 и обнаружил, что это похоже на молоток по комару. Мои задачи просты, и я знаком с командами git, поэтому обычно использую GitPython. Его способность напрямую переводить и вызывать команды git мне подходит. Однако эта возможность не применима в случае использования стандартного ввода.
@MisterMiyagi Спасибо. Я узнаю о subprocess.run.






Если я чего-то не упустил, вы можете установить stdin=PIPE при создании объекта Popen и передать входные данные в качестве аргумента communicate.
Вот что я использовал в качестве «теста на дым»:
import subprocess
with subprocess.Popen(['cat'],
encoding='utf-8',
stdin=subprocess.PIPE,
stdout=subprocess.PIPE) as p:
o, e = p.communicate('foo')
print(o, e)
Спасибо за ответ. Не могли бы вы дать демо?
Я искал и нашел этот ответ. Теперь я понимаю, что означает ваш ответ. Еще раз спасибо!
Попробуйте использовать круглые скобки вокруг вызова
StringIO, например:with (io.StringIO(line1 + line2)) as f: