【问题标题】:How can I call 'git pull' from within Python?如何从 Python 中调用“git pull”?
【发布时间】:2013-02-25 06:30:30
【问题描述】:

使用 github webhooks,我希望能够将任何更改拉到远程开发服务器。目前,当在适当的目录中时,git pull 会获得需要进行的任何更改。但是,我不知道如何从 Python 中调用该函数。我尝试了以下方法:

import subprocess
process = subprocess.Popen("git pull", stdout=subprocess.PIPE)
output = process.communicate()[0]

但这会导致以下错误

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 679, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1249, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

有没有一种方法可以让我在 Python 中调用这个 bash 命令?

【问题讨论】:

  • @Brandon 这不是真的,还有很多其他的解决方案,最好的。
  • git 是否在 PATH 中可执行?
  • @jleahy 也许,据我了解,celecnius 正在有效地询问“我如何运行 bash 命令”。这个问题已经被问过很多次了。
  • 这是一个老问题,但我认为subprocess.Popen 现在有一个cwd 关键字参数,它将在指定目录中执行命令。例如:subprocess.Popen(['git', 'pull', '-v', 'origin', 'master'], cwd='/path/to/git/repo', ...)

标签: python git bash github


【解决方案1】:

subprocess.Popen 需要程序名称和参数的列表。你传递给它一个字符串,它(默认shell=False)相当于:

['git pull']

这意味着子进程试图找到一个字面上命名为 git pull 的程序,但没有这样做:在 Python 3.3 中,您的代码会引发异常 FileNotFoundError: [Errno 2] No such file or directory: 'git pull'。相反,传入一个列表,如下所示:

import subprocess
process = subprocess.Popen(["git", "pull"], stdout=subprocess.PIPE)
output = process.communicate()[0]

顺便说一句,在 Python 2.7+ 中,您可以使用 check_output 便利函数简化此代码:

import subprocess
output = subprocess.check_output(["git", "pull"])

此外,要使用 git 功能,绝对没有必要(尽管简单且可移植)调用 git 二进制文件。考虑使用git-pythonDulwich

【讨论】:

  • 每个文档:“args 应该是程序参数的序列或者单个字符串。”(强调我的)。你确定这一点,因为它对我有用?
  • @poke 是的,文档有点不清楚。如果要传入字符串,必须传入shell=True
  • 好吧,正如我所说,它对我来说只适用于 subprocess.Popen("git pull", stdout=subprocess.PIPE).communicate()(Windows 上的 3.3 和 2.7)。 - 好吧,进一步阅读,文档解释说,这只会在不传递任何参数的情况下在 Unix 上工作。那没关系^^
  • @poke 你对单字符串的情况是对的,但我最初的解释是错误的。事实证明,你可以传入一个字符串,但是 (quoting): If args is a string, the interpretation is platform-dependent。添加了更多细节。在 Python 3.3 中,错误消息要好得多。
  • 当需要 autocrlf 时,这在 windows 上不起作用。从 cmd 调用 git 和从 sh.exe 调用 git 是不一样的。没有考虑自动 crfl 替换,这就是 git status 不起作用的原因
【解决方案2】:

你考虑过使用 GitPython 吗?它旨在为您处理所有这些废话。

import git 

g = git.cmd.Git(git_dir)
g.pull()

https://github.com/gitpython-developers/GitPython

【讨论】:

  • git_dir 是您拥有的任何源代码控制目录(其中包含 .git 文件夹)
  • 它正在提示输入密码..我怎样才能传递密码??
  • 可以使用ssh建立密钥,无需密码验证
  • msg = g.pull() 告诉你发生了什么。 msg'''Updating ... Fast-forward README.md | 1 + 1 file changed, 1 insertion(+)' 如果您成功拉动,'Already up-to-date.' 如果您...已经是最新的。
  • 这是使用 GitPython 的一种非常糟糕的方式——它几乎不处理任何垃圾,只转换为 subprocess.check_output(['git', 'pull'], cwd=git_dir),让您自己解析“git瓷器”命令的输出,你不应该这样做
【解决方案3】:

试试:

subprocess.Popen("git pull", stdout=subprocess.PIPE, shell=True)

【讨论】:

  • 这会不必要地产生一个外壳,不是吗?
【解决方案4】:

这是一个示例配方,我一直在我的一个项目中使用。同意有多种方法可以做到这一点。 :)

>>> import subprocess, shlex
>>> git_cmd = 'git status'
>>> kwargs = {}
>>> kwargs['stdout'] = subprocess.PIPE
>>> kwargs['stderr'] = subprocess.PIPE
>>> proc = subprocess.Popen(shlex.split(git_cmd), **kwargs)
>>> (stdout_str, stderr_str) = proc.communicate()
>>> return_code = proc.wait()

>>> print return_code
0

>>> print stdout_str
# On branch dev
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   file1
#   file2
nothing added to commit but untracked files present (use "git add" to track)

>>> print stderr_str

您的代码的问题是,您没有为subprocess.Popen() 传递一个数组,因此试图运行一个名为git pull 的二进制文件。相反,它需要执行二进制文件git,第一个参数是pull,依此类推。

【讨论】:

  • 并不是说split 真的应该是shlex.split,而且那个代码是不必要的复杂——在communicate 之后你不需要wait,对于大多数用例来说,check_output 需要已经是正确的字符串了。
  • 我使用 wait() 来收集进程的返回码,以满足我的需要。如果 OP 对返回码检查不感兴趣,它是可选的。不过同意使用shlex.split。是的,如果您对获取 stdout 或 stderr 不感兴趣,您可以跳过这些。提供替代解决方案没有错,它可以让您做更多事情而不是标准的 python 文档示例;)
  • 正如你所提到的,check_output 是从 2.7 开始添加的,我也需要在 Python 版本更早的环境中运行我的脚本。
  • 虽然wait 有效,但proc.pid 更容易理解。由于 check_output 是纯 Python,因此可以简单地 copy it 使其在较旧的 Python 版本上也可用。
  • 天哪!是的check_output 似乎也更健壮:)
【解决方案5】:

使用GitPython 的公认答案比直接使用subprocess 好一点。

这种方法的问题是,如果你想解析输出,你最终会看到“瓷器”命令的结果,which is a bad idea

以这种方式使用 GitPython 就像得到一个闪亮的新工具箱,然后将它用于将它固定在一起的一堆螺丝,而不是里面的工具。以下是 API 的设计用途:

import git
repo = git.Repo('Path/to/repo')
repo.remotes.origin.pull()

如果要检查是否有更改,可以使用

current = repo.head.commit
repo.remotes.origin.pull()
if current != repo.head.commit:
    print("It changed")

【讨论】:

    【解决方案6】:

    如果您使用的是 Python 3.5+,则更喜欢 subprocess.run 而不是 subprocess.Popen 来处理它可以处理的场景。例如:

    import subprocess
    subprocess.run(["git", "pull"], check=True, stdout=subprocess.PIPE).stdout
    

    【讨论】:

      猜你喜欢
      • 2013-10-21
      • 2015-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-25
      • 2021-01-30
      • 2014-08-31
      • 2021-11-17
      相关资源
      最近更新 更多