Skip to content

Commit 7c4070e

Browse files
committed
Avoid waiting for user input in git & svn commands
Fixes #570
1 parent 43991ea commit 7c4070e

File tree

5 files changed

+68
-40
lines changed

5 files changed

+68
-40
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Release 0.11.0 (unreleased)
2424
* Add more tests and documentation for patching (#888)
2525
* Restrict ``src`` to string only in schema (#888)
2626
* Don't consider ignored files for determining local changes (#350)
27+
* Avoid waiting for user input in ``git`` & ``svn`` commands (#570)
2728

2829
Release 0.10.0 (released 2025-03-12)
2930
====================================

dfetch/project/svn.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def externals() -> list[External]:
5252
logger,
5353
[
5454
"svn",
55+
"--non-interactive",
5556
"propget",
5657
"svn:externals",
5758
"-R",
@@ -130,7 +131,7 @@ def _split_url(url: str, repo_root: str) -> tuple[str, str, str, str]:
130131
def check(self) -> bool:
131132
"""Check if is SVN."""
132133
try:
133-
run_on_cmdline(logger, f"svn info {self.remote} --non-interactive")
134+
run_on_cmdline(logger, ["svn", "info", self.remote, "--non-interactive"])
134135
return True
135136
except SubprocessCommandError as exc:
136137
if exc.stdout.startswith("svn: E170013"):
@@ -147,7 +148,7 @@ def check_path(path: str = ".") -> bool:
147148
"""Check if is SVN."""
148149
try:
149150
with in_directory(path):
150-
run_on_cmdline(logger, "svn info --non-interactive")
151+
run_on_cmdline(logger, ["svn", "info", "--non-interactive"])
151152
return True
152153
except (SubprocessCommandError, RuntimeError):
153154
return False
@@ -171,7 +172,9 @@ def _does_revision_exist(self, revision: str) -> bool:
171172

172173
def _list_of_tags(self) -> list[str]:
173174
"""Get list of all available tags."""
174-
result = run_on_cmdline(logger, f"svn ls --non-interactive {self.remote}/tags")
175+
result = run_on_cmdline(
176+
logger, ["svn", "ls", "--non-interactive", f"{self.remote}/tags"]
177+
)
175178
return [
176179
str(tag).strip("/\r") for tag in result.stdout.decode().split("\n") if tag
177180
]
@@ -180,7 +183,7 @@ def _list_of_tags(self) -> list[str]:
180183
def list_tool_info() -> None:
181184
"""Print out version information."""
182185
try:
183-
result = run_on_cmdline(logger, "svn --version")
186+
result = run_on_cmdline(logger, ["svn", "--version", "--non-interactive"])
184187
except RuntimeError as exc:
185188
logger.debug(
186189
f"Something went wrong trying to get the version of svn: {exc}"
@@ -304,7 +307,9 @@ def _export(url: str, rev: str = "", dst: str = ".") -> None:
304307
def _files_in_path(url_path: str) -> list[str]:
305308
return [
306309
str(line)
307-
for line in run_on_cmdline(logger, f"svn list --non-interactive {url_path}")
310+
for line in run_on_cmdline(
311+
logger, ["svn", "list", "--non-interactive", url_path]
312+
)
308313
.stdout.decode()
309314
.splitlines()
310315
]
@@ -322,7 +327,7 @@ def _license_files(url_path: str) -> list[str]:
322327
def _get_info_from_target(target: str = "") -> dict[str, str]:
323328
try:
324329
result = run_on_cmdline(
325-
logger, f"svn info --non-interactive {target.strip()}"
330+
logger, ["svn", "info", "--non-interactive", target.strip()]
326331
).stdout.decode()
327332
except SubprocessCommandError as exc:
328333
if exc.stdout.startswith("svn: E170013"):
@@ -347,7 +352,7 @@ def _get_last_changed_revision(target: str) -> str:
347352
if os.path.isdir(target):
348353
last_digits = re.compile(r"(?P<digits>\d+)(?!.*\d)")
349354
version = run_on_cmdline(
350-
logger, f"svnversion {target.strip()}"
355+
logger, ["svnversion", target.strip()]
351356
).stdout.decode()
352357

353358
parsed_version = last_digits.search(version)
@@ -358,7 +363,14 @@ def _get_last_changed_revision(target: str) -> str:
358363
return str(
359364
run_on_cmdline(
360365
logger,
361-
f"svn info --non-interactive --show-item last-changed-revision {target.strip()}",
366+
[
367+
"svn",
368+
"info",
369+
"--non-interactive",
370+
"--show-item",
371+
"last-changed-revision",
372+
target.strip(),
373+
],
362374
)
363375
.stdout.decode()
364376
.strip()
@@ -415,7 +427,7 @@ def _untracked_files(path: str, ignore: Sequence[str]) -> list[str]:
415427
result = (
416428
run_on_cmdline(
417429
logger,
418-
["svn", "status", path],
430+
["svn", "status", "--non-interactive", path],
419431
)
420432
.stdout.decode()
421433
.splitlines()
@@ -441,7 +453,7 @@ def ignored_files(path: str) -> Sequence[str]:
441453
result = (
442454
run_on_cmdline(
443455
logger,
444-
["svn", "status", "--no-ignore", "."],
456+
["svn", "status", "--non-interactive", "--no-ignore", "."],
445457
)
446458
.stdout.decode()
447459
.splitlines()

dfetch/util/cmdline.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import logging
44
import os
55
import subprocess # nosec
6-
from typing import Any, Optional, Union # pylint: disable=unused-import
6+
from collections.abc import Mapping
7+
from typing import Any, Optional
78

89

910
class SubprocessCommandError(Exception):
@@ -36,16 +37,15 @@ def message(self) -> str:
3637

3738

3839
def run_on_cmdline(
39-
logger: logging.Logger, cmd: Union[str, list[str]]
40+
logger: logging.Logger,
41+
cmd: list[str],
42+
env: Optional[Mapping[str, str]] = None,
4043
) -> "subprocess.CompletedProcess[Any]":
4144
"""Run a command and log the output, and raise if something goes wrong."""
4245
logger.debug(f"Running {cmd}")
4346

44-
if not isinstance(cmd, list):
45-
cmd = cmd.split(" ")
46-
4747
try:
48-
proc = subprocess.run(cmd, capture_output=True, check=True) # nosec
48+
proc = subprocess.run(cmd, env=env, capture_output=True, check=True) # nosec
4949
except subprocess.CalledProcessError as exc:
5050
raise SubprocessCommandError(
5151
exc.cmd,
@@ -54,8 +54,7 @@ def run_on_cmdline(
5454
exc.returncode,
5555
) from exc
5656
except FileNotFoundError as exc:
57-
cmd = cmd[0]
58-
raise RuntimeError(f"{cmd} not available on system, please install") from exc
57+
raise RuntimeError(f"{cmd[0]} not available on system, please install") from exc
5958

6059
stdout, stderr = proc.stdout, proc.stderr
6160

dfetch/vcs/git.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Submodule(NamedTuple):
3030

3131
def get_git_version() -> tuple[str, str]:
3232
"""Get the name and version of git."""
33-
result = run_on_cmdline(logger, "git --version")
33+
result = run_on_cmdline(logger, ["git", "--version"])
3434
tool, version = result.stdout.decode().strip().split("version", maxsplit=1)
3535
return (str(tool), str(version))
3636

@@ -48,7 +48,11 @@ def is_git(self) -> bool:
4848
return True
4949

5050
try:
51-
run_on_cmdline(logger, f"git ls-remote --heads {self._remote}")
51+
run_on_cmdline(
52+
logger,
53+
cmd=["git", "ls-remote", "--heads", self._remote],
54+
env={"GIT_TERMINAL_PROMPT": "0"},
55+
)
5256
return True
5357
except SubprocessCommandError as exc:
5458
if exc.returncode == 128 and "Could not resolve host" in exc.stdout:
@@ -82,7 +86,9 @@ def get_default_branch(self) -> str:
8286
"""Try to get the default branch or fallback to master."""
8387
try:
8488
result = run_on_cmdline(
85-
logger, f"git ls-remote --symref {self._remote} HEAD"
89+
logger,
90+
cmd=["git", "ls-remote", "--symref", self._remote, "HEAD"],
91+
env={"GIT_TERMINAL_PROMPT": "0"},
8692
).stdout.decode()
8793
except SubprocessCommandError:
8894
logger.debug(
@@ -101,7 +107,9 @@ def get_default_branch(self) -> str:
101107
@staticmethod
102108
def _ls_remote(remote: str) -> dict[str, str]:
103109
result = run_on_cmdline(
104-
logger, f"git ls-remote --heads --tags {remote}"
110+
logger,
111+
cmd=["git", "ls-remote", "--heads", "--tags", remote],
112+
env={"GIT_TERMINAL_PROMPT": "0"},
105113
).stdout.decode()
106114

107115
info: dict[str, str] = {}
@@ -156,12 +164,14 @@ def check_version_exists(
156164
temp_dir = tempfile.mkdtemp()
157165
exists = False
158166
with in_directory(temp_dir):
159-
run_on_cmdline(logger, "git init")
160-
run_on_cmdline(logger, f"git remote add origin {self._remote}")
161-
run_on_cmdline(logger, "git checkout -b dfetch-local-branch")
167+
run_on_cmdline(logger, ["git", "init"])
168+
run_on_cmdline(logger, ["git", "remote", "add", "origin", self._remote])
169+
run_on_cmdline(logger, ["git", "checkout", "-b", "dfetch-local-branch"])
162170
try:
163171
run_on_cmdline(
164-
logger, f"git fetch --dry-run --depth 1 origin {version}"
172+
logger,
173+
["git", "fetch", "--dry-run", "--depth", "1", "origin", version],
174+
env={"GIT_TERMINAL_PROMPT": "0"},
165175
)
166176
exists = True
167177
except SubprocessCommandError as exc:
@@ -185,7 +195,11 @@ def is_git(self) -> bool:
185195
"""Check if is git."""
186196
try:
187197
with in_directory(self._path):
188-
run_on_cmdline(logger, "git status")
198+
run_on_cmdline(
199+
logger,
200+
["git", "status"],
201+
env={"GIT_TERMINAL_PROMPT": "0"},
202+
)
189203
return True
190204
except (SubprocessCommandError, RuntimeError):
191205
return False
@@ -209,12 +223,12 @@ def checkout_version( # pylint: disable=too-many-arguments
209223
ignore (Optional[Sequence[str]]): Optional sequence of glob patterns to ignore (relative to src)
210224
"""
211225
with in_directory(self._path):
212-
run_on_cmdline(logger, "git init")
213-
run_on_cmdline(logger, f"git remote add origin {remote}")
214-
run_on_cmdline(logger, "git checkout -b dfetch-local-branch")
226+
run_on_cmdline(logger, ["git", "init"])
227+
run_on_cmdline(logger, ["git", "remote", "add", "origin", remote])
228+
run_on_cmdline(logger, ["git", "checkout", "-b", "dfetch-local-branch"])
215229

216230
if src or ignore:
217-
run_on_cmdline(logger, "git config core.sparsecheckout true")
231+
run_on_cmdline(logger, ["git", "config", "core.sparsecheckout", "true"])
218232
with open(
219233
".git/info/sparse-checkout", "a", encoding="utf-8"
220234
) as sparse_checkout_file:
@@ -228,11 +242,17 @@ def checkout_version( # pylint: disable=too-many-arguments
228242
sparse_checkout_file.write("\n")
229243
sparse_checkout_file.write("\n".join(ignore_abs_paths))
230244

231-
run_on_cmdline(logger, f"git fetch --depth 1 origin {version}")
232-
run_on_cmdline(logger, "git reset --hard FETCH_HEAD")
245+
run_on_cmdline(
246+
logger,
247+
["git", "fetch", "--depth", "1", "origin", version],
248+
env={"GIT_TERMINAL_PROMPT": "0"},
249+
)
250+
run_on_cmdline(logger, ["git", "reset", "--hard", "FETCH_HEAD"])
233251

234252
current_sha = (
235-
run_on_cmdline(logger, "git rev-parse HEAD").stdout.decode().strip()
253+
run_on_cmdline(logger, ["git", "rev-parse", "HEAD"])
254+
.stdout.decode()
255+
.strip()
236256
)
237257

238258
if src:
@@ -305,10 +325,7 @@ def get_current_hash(self) -> str:
305325
def get_remote_url() -> str:
306326
"""Get the url of the remote origin."""
307327
try:
308-
result = run_on_cmdline(
309-
logger,
310-
["git", "remote", "get-url", "origin"],
311-
)
328+
result = run_on_cmdline(logger, ["git", "remote", "get-url", "origin"])
312329
decoded_result = str(result.stdout.decode())
313330
except SubprocessCommandError:
314331
decoded_result = ""

features/check-git-repo.feature

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,5 @@ Feature: Checking dependencies from a git repository
221221
"""
222222
Dfetch (0.10.0)
223223
>>>git ls-remote --heads --tags https://github.com/dfetch-org/test-repo-private.git<<< returned 128:
224-
remote: Write access to repository not granted.
225-
fatal: unable to access 'https://github.com/dfetch-org/test-repo-private.git/': The requested URL returned error: 403
224+
fatal: could not read Username for 'https://github.com': terminal prompts disabled
226225
"""

0 commit comments

Comments
 (0)