File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -146,6 +146,7 @@ def run(
146146 config_env : str | None = None ,
147147 # Pass-through to run()
148148 log_in_real_time : bool = False ,
149+ timeout : float | None = None ,
149150 ** kwargs : t .Any ,
150151 ) -> str :
151152 """Run a command for this git repository.
@@ -205,6 +206,13 @@ def run(
205206 ``--config=<name>=<value>``
206207 config_env :
207208 ``--config-env=<name>=<envvar>``
209+ timeout : float, optional
210+ Wall-clock seconds to wait before terminating the subprocess.
211+ ``None`` (default) preserves the legacy behaviour of blocking
212+ until the process exits. When the deadline is exceeded the
213+ process is sent ``SIGTERM`` (then ``SIGKILL`` after a grace
214+ period) and :class:`libvcs.exc.CommandTimeoutError` is raised
215+ with any output collected so far.
208216
209217 Examples
210218 --------
@@ -281,7 +289,7 @@ def stringify(v: t.Any) -> str:
281289 if self .progress_callback is not None :
282290 kwargs ["callback" ] = self .progress_callback
283291
284- return run (args = cli_args , ** kwargs )
292+ return run (args = cli_args , timeout = timeout , ** kwargs )
285293
286294 def clone (
287295 self ,
Original file line number Diff line number Diff line change @@ -98,6 +98,7 @@ def run(
9898 pager : HgPagerType | None = None ,
9999 color : HgColorType | None = None ,
100100 check_returncode : bool | None = None ,
101+ timeout : float | None = None ,
101102 ** kwargs : t .Any ,
102103 ) -> str :
103104 """Run a command for this Mercurial repository.
@@ -149,6 +150,13 @@ def run(
149150 ``--config CONFIG [+]``, ``section.name=value``
150151 check_returncode : bool, default: ``True``
151152 Passthrough to :func:`libvcs._internal.run.run()`
153+ timeout : float, optional
154+ Wall-clock seconds to wait before terminating the subprocess.
155+ ``None`` (default) preserves the legacy behaviour of blocking
156+ until the process exits. When the deadline is exceeded the
157+ process is sent ``SIGTERM`` (then ``SIGKILL`` after a grace
158+ period) and :class:`libvcs.exc.CommandTimeoutError` is raised
159+ with any output collected so far.
152160
153161 Examples
154162 --------
@@ -194,6 +202,7 @@ def run(
194202 return run (
195203 args = cli_args ,
196204 check_returncode = True if check_returncode is None else check_returncode ,
205+ timeout = timeout ,
197206 ** kwargs ,
198207 )
199208
Original file line number Diff line number Diff line change @@ -83,6 +83,7 @@ def run(
8383 # Special behavior
8484 make_parents : bool | None = True ,
8585 check_returncode : bool | None = None ,
86+ timeout : float | None = None ,
8687 ** kwargs : t .Any ,
8788 ) -> str :
8889 """Run a command for this SVN working copy.
@@ -119,6 +120,13 @@ def run(
119120 Creates checkout directory (`:attr:`self.path`) if it doesn't already exist.
120121 check_returncode : bool, default: ``None``
121122 Passthrough to :meth:`Svn.run`
123+ timeout : float, optional
124+ Wall-clock seconds to wait before terminating the subprocess.
125+ ``None`` (default) preserves the legacy behaviour of blocking
126+ until the process exits. When the deadline is exceeded the
127+ process is sent ``SIGTERM`` (then ``SIGKILL`` after a grace
128+ period) and :class:`libvcs.exc.CommandTimeoutError` is raised
129+ with any output collected so far.
122130
123131 Examples
124132 --------
@@ -152,6 +160,7 @@ def run(
152160 return run (
153161 args = cli_args ,
154162 check_returncode = True if check_returncode is None else check_returncode ,
163+ timeout = timeout ,
155164 ** kwargs ,
156165 )
157166
Original file line number Diff line number Diff line change 1212from libvcs .cmd import git
1313
1414if t .TYPE_CHECKING :
15+ from pytest_mock import MockerFixture
16+
1517 from libvcs .pytest_plugin import CreateRepoFn , GitCommitEnvVars
1618 from libvcs .sync .git import GitSync
1719
@@ -44,6 +46,24 @@ def test_git_run_accepts_scalar_string(tmp_path: pathlib.Path) -> None:
4446 assert result .startswith ("git version " )
4547
4648
49+ def test_git_run_timeout_propagates_to_runner (
50+ tmp_path : pathlib .Path ,
51+ mocker : MockerFixture ,
52+ ) -> None :
53+ """``Git.run(timeout=X)`` forwards X to the underlying ``run()`` call.
54+
55+ Regression guard for the discoverability fix: ``timeout=`` is part of the
56+ public ``Git.run`` signature rather than reachable only via ``**kwargs``.
57+ """
58+ repo = git .Git (path = tmp_path )
59+ mock_run = mocker .patch ("libvcs.cmd.git.run" , return_value = "" )
60+
61+ repo .run (["--version" ], timeout = 2.5 )
62+
63+ _args , kwargs = mock_run .call_args
64+ assert kwargs .get ("timeout" ) == 2.5
65+
66+
4767def test_git_init_bare (tmp_path : pathlib .Path ) -> None :
4868 """Test git init with bare repository."""
4969 repo = git .Git (path = tmp_path )
Original file line number Diff line number Diff line change 44
55import pathlib
66import shutil
7+ import typing as t
78
89import pytest
910
1011from libvcs .cmd .hg import Hg
1112
13+ if t .TYPE_CHECKING :
14+ from pytest_mock import MockerFixture
15+
1216if not shutil .which ("hg" ):
1317 pytestmark = pytest .mark .skip (reason = "hg is not available" )
1418
@@ -20,3 +24,17 @@ def test_hg_run_accepts_scalar_string(tmp_path: pathlib.Path) -> None:
2024 result = repo .run ("help" )
2125
2226 assert "Mercurial Distributed SCM" in result
27+
28+
29+ def test_hg_run_timeout_propagates_to_runner (
30+ tmp_path : pathlib .Path ,
31+ mocker : MockerFixture ,
32+ ) -> None :
33+ """``Hg.run(timeout=X)`` forwards X to the underlying ``run()``."""
34+ repo = Hg (path = tmp_path )
35+ mock_run = mocker .patch ("libvcs.cmd.hg.run" , return_value = "" )
36+
37+ repo .run (["help" ], timeout = 2.5 )
38+
39+ _args , kwargs = mock_run .call_args
40+ assert kwargs .get ("timeout" ) == 2.5
Original file line number Diff line number Diff line change 44
55import pathlib
66import shutil
7+ import typing as t
78
89import pytest
910
1011from libvcs .cmd .svn import Svn
1112
13+ if t .TYPE_CHECKING :
14+ from pytest_mock import MockerFixture
15+
1216if not shutil .which ("svn" ):
1317 pytestmark = pytest .mark .skip (reason = "svn is not available" )
1418
@@ -20,3 +24,17 @@ def test_svn_run_accepts_scalar_string(tmp_path: pathlib.Path) -> None:
2024 result = repo .run ("help" )
2125
2226 assert "usage: svn <subcommand> [options] [args]" in result
27+
28+
29+ def test_svn_run_timeout_propagates_to_runner (
30+ tmp_path : pathlib .Path ,
31+ mocker : MockerFixture ,
32+ ) -> None :
33+ """``Svn.run(timeout=X)`` forwards X to the underlying ``run()``."""
34+ repo = Svn (path = tmp_path )
35+ mock_run = mocker .patch ("libvcs.cmd.svn.run" , return_value = "" )
36+
37+ repo .run (["help" ], timeout = 2.5 )
38+
39+ _args , kwargs = mock_run .call_args
40+ assert kwargs .get ("timeout" ) == 2.5
You can’t perform that action at this time.
0 commit comments