Skip to content

Commit c8d4f0f

Browse files
committed
cmd/_async(feat): Add AsyncHg command class
why: Provide async Mercurial command wrapper. what: - Async run(), clone(), update(), pull() methods - Uses async_run() from _internal/async_run.py
1 parent 8fdde6d commit c8d4f0f

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

  • src/libvcs/cmd/_async

src/libvcs/cmd/_async/hg.py

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
"""Async hg (Mercurial) commands directly against a local mercurial repo.
2+
3+
Async equivalent of :mod:`libvcs.cmd.hg`.
4+
5+
Note
6+
----
7+
This is an internal API not covered by versioning policy.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import enum
13+
import pathlib
14+
import typing as t
15+
from collections.abc import Sequence
16+
17+
from libvcs._internal.async_run import (
18+
AsyncProgressCallbackProtocol,
19+
async_run,
20+
)
21+
from libvcs._internal.types import StrOrBytesPath, StrPath
22+
23+
_CMD = StrOrBytesPath | Sequence[StrOrBytesPath]
24+
25+
26+
class HgColorType(enum.Enum):
27+
"""CLI Color enum for Mercurial."""
28+
29+
boolean = "boolean"
30+
always = "always"
31+
auto = "auto"
32+
never = "never"
33+
debug = "debug"
34+
35+
36+
class HgPagerType(enum.Enum):
37+
"""CLI Pagination enum for Mercurial."""
38+
39+
boolean = "boolean"
40+
always = "always"
41+
auto = "auto"
42+
never = "never"
43+
44+
45+
class AsyncHg:
46+
"""Run commands directly on a Mercurial repository asynchronously.
47+
48+
Async equivalent of :class:`~libvcs.cmd.hg.Hg`.
49+
50+
Parameters
51+
----------
52+
path : str | Path
53+
Path to the hg repository
54+
progress_callback : AsyncProgressCallbackProtocol, optional
55+
Async callback for progress reporting
56+
57+
Examples
58+
--------
59+
>>> import asyncio
60+
>>> async def example():
61+
... hg = AsyncHg(path="/path/to/repo")
62+
... output = await hg.pull()
63+
... return output
64+
>>> # asyncio.run(example())
65+
"""
66+
67+
progress_callback: AsyncProgressCallbackProtocol | None = None
68+
69+
def __init__(
70+
self,
71+
*,
72+
path: StrPath,
73+
progress_callback: AsyncProgressCallbackProtocol | None = None,
74+
) -> None:
75+
"""Initialize AsyncHg command wrapper.
76+
77+
Parameters
78+
----------
79+
path : str | Path
80+
Path to the hg repository
81+
progress_callback : AsyncProgressCallbackProtocol, optional
82+
Async callback for progress reporting
83+
"""
84+
self.path: pathlib.Path
85+
if isinstance(path, pathlib.Path):
86+
self.path = path
87+
else:
88+
self.path = pathlib.Path(path)
89+
90+
self.progress_callback = progress_callback
91+
92+
def __repr__(self) -> str:
93+
"""Representation of AsyncHg repo command object."""
94+
return f"<AsyncHg path={self.path}>"
95+
96+
async def run(
97+
self,
98+
args: _CMD,
99+
*,
100+
config: str | None = None,
101+
repository: str | None = None,
102+
quiet: bool | None = None,
103+
_help: bool | None = None,
104+
encoding: str | None = None,
105+
encoding_mode: str | None = None,
106+
verbose: bool | None = None,
107+
traceback: bool | None = None,
108+
debug: bool | None = None,
109+
debugger: bool | None = None,
110+
profile: bool | None = None,
111+
version: bool | None = None,
112+
hidden: bool | None = None,
113+
time: bool | None = None,
114+
pager: HgPagerType | None = None,
115+
color: HgColorType | None = None,
116+
# Pass-through to async_run()
117+
cwd: StrOrBytesPath | None = None,
118+
log_in_real_time: bool = False,
119+
check_returncode: bool = True,
120+
timeout: float | None = None,
121+
**kwargs: t.Any,
122+
) -> str:
123+
"""Run a command for this Mercurial repository asynchronously.
124+
125+
Async equivalent of :meth:`~libvcs.cmd.hg.Hg.run`.
126+
127+
Parameters
128+
----------
129+
args : list[str] | str
130+
Hg subcommand and arguments
131+
quiet : bool, optional
132+
-q / --quiet
133+
repository : str, optional
134+
--repository REPO
135+
cwd : str | Path, optional
136+
Working directory. Defaults to self.path.
137+
verbose : bool, optional
138+
-v / --verbose
139+
color : HgColorType, optional
140+
--color
141+
debug : bool, optional
142+
--debug
143+
config : str, optional
144+
--config CONFIG, section.name=value
145+
check_returncode : bool, default True
146+
Raise on non-zero exit code
147+
timeout : float, optional
148+
Timeout in seconds
149+
150+
Returns
151+
-------
152+
str
153+
Command output
154+
"""
155+
cli_args: list[str]
156+
if isinstance(args, Sequence) and not isinstance(args, (str, bytes)):
157+
cli_args = ["hg", *[str(a) for a in args]]
158+
else:
159+
cli_args = ["hg", str(args)]
160+
161+
run_cwd = cwd if cwd is not None else self.path
162+
163+
# Build flags
164+
if repository is not None:
165+
cli_args.extend(["--repository", repository])
166+
if config is not None:
167+
cli_args.extend(["--config", config])
168+
if pager is not None:
169+
cli_args.extend(["--pager", pager.value])
170+
if color is not None:
171+
cli_args.extend(["--color", color.value])
172+
if verbose is True:
173+
cli_args.append("--verbose")
174+
if quiet is True:
175+
cli_args.append("--quiet")
176+
if debug is True:
177+
cli_args.append("--debug")
178+
if debugger is True:
179+
cli_args.append("--debugger")
180+
if traceback is True:
181+
cli_args.append("--traceback")
182+
if time is True:
183+
cli_args.append("--time")
184+
if profile is True:
185+
cli_args.append("--profile")
186+
if version is True:
187+
cli_args.append("--version")
188+
if _help is True:
189+
cli_args.append("--help")
190+
191+
return await async_run(
192+
cli_args,
193+
cwd=run_cwd,
194+
callback=self.progress_callback if log_in_real_time else None,
195+
check_returncode=check_returncode,
196+
timeout=timeout,
197+
**kwargs,
198+
)
199+
200+
async def clone(
201+
self,
202+
*,
203+
url: str,
204+
no_update: bool | None = None,
205+
update_rev: str | None = None,
206+
rev: str | None = None,
207+
branch: str | None = None,
208+
ssh: str | None = None,
209+
remote_cmd: str | None = None,
210+
pull: bool | None = None,
211+
stream: bool | None = None,
212+
insecure: bool | None = None,
213+
quiet: bool | None = None,
214+
# Special behavior
215+
make_parents: bool | None = True,
216+
# Pass-through
217+
log_in_real_time: bool = False,
218+
check_returncode: bool = True,
219+
timeout: float | None = None,
220+
) -> str:
221+
"""Clone a working copy from a mercurial repo asynchronously.
222+
223+
Async equivalent of :meth:`~libvcs.cmd.hg.Hg.clone`.
224+
225+
Parameters
226+
----------
227+
url : str
228+
URL of the repository to clone
229+
no_update : bool, optional
230+
Don't update the working directory
231+
rev : str, optional
232+
Revision to clone
233+
branch : str, optional
234+
Branch to clone
235+
ssh : str, optional
236+
SSH command to use
237+
make_parents : bool, default True
238+
Creates checkout directory if it doesn't exist
239+
check_returncode : bool, default True
240+
Raise on non-zero exit code
241+
timeout : float, optional
242+
Timeout in seconds
243+
244+
Returns
245+
-------
246+
str
247+
Command output
248+
"""
249+
required_flags: list[str] = [url, str(self.path)]
250+
local_flags: list[str] = []
251+
252+
if ssh is not None:
253+
local_flags.extend(["--ssh", ssh])
254+
if remote_cmd is not None:
255+
local_flags.extend(["--remotecmd", remote_cmd])
256+
if rev is not None:
257+
local_flags.extend(["--rev", rev])
258+
if branch is not None:
259+
local_flags.extend(["--branch", branch])
260+
if no_update is True:
261+
local_flags.append("--noupdate")
262+
if pull is True:
263+
local_flags.append("--pull")
264+
if stream is True:
265+
local_flags.append("--stream")
266+
if insecure is True:
267+
local_flags.append("--insecure")
268+
if quiet is True:
269+
local_flags.append("--quiet")
270+
271+
# libvcs special behavior
272+
if make_parents and not self.path.exists():
273+
self.path.mkdir(parents=True)
274+
275+
return await self.run(
276+
["clone", *local_flags, "--", *required_flags],
277+
log_in_real_time=log_in_real_time,
278+
check_returncode=check_returncode,
279+
timeout=timeout,
280+
)
281+
282+
async def update(
283+
self,
284+
quiet: bool | None = None,
285+
verbose: bool | None = None,
286+
# Pass-through
287+
log_in_real_time: bool = False,
288+
check_returncode: bool = True,
289+
timeout: float | None = None,
290+
) -> str:
291+
"""Update working directory asynchronously.
292+
293+
Async equivalent of :meth:`~libvcs.cmd.hg.Hg.update`.
294+
295+
Parameters
296+
----------
297+
quiet : bool, optional
298+
Suppress output
299+
verbose : bool, optional
300+
Enable verbose output
301+
check_returncode : bool, default True
302+
Raise on non-zero exit code
303+
timeout : float, optional
304+
Timeout in seconds
305+
306+
Returns
307+
-------
308+
str
309+
Command output
310+
"""
311+
local_flags: list[str] = []
312+
313+
if quiet:
314+
local_flags.append("--quiet")
315+
if verbose:
316+
local_flags.append("--verbose")
317+
318+
return await self.run(
319+
["update", *local_flags],
320+
log_in_real_time=log_in_real_time,
321+
check_returncode=check_returncode,
322+
timeout=timeout,
323+
)
324+
325+
async def pull(
326+
self,
327+
quiet: bool | None = None,
328+
verbose: bool | None = None,
329+
update: bool | None = None,
330+
# Pass-through
331+
log_in_real_time: bool = False,
332+
check_returncode: bool = True,
333+
timeout: float | None = None,
334+
) -> str:
335+
"""Pull changes from remote asynchronously.
336+
337+
Async equivalent of :meth:`~libvcs.cmd.hg.Hg.pull`.
338+
339+
Parameters
340+
----------
341+
quiet : bool, optional
342+
Suppress output
343+
verbose : bool, optional
344+
Enable verbose output
345+
update : bool, optional
346+
Update to new branch head after pull
347+
check_returncode : bool, default True
348+
Raise on non-zero exit code
349+
timeout : float, optional
350+
Timeout in seconds
351+
352+
Returns
353+
-------
354+
str
355+
Command output
356+
"""
357+
local_flags: list[str] = []
358+
359+
if quiet:
360+
local_flags.append("--quiet")
361+
if verbose:
362+
local_flags.append("--verbose")
363+
if update:
364+
local_flags.append("--update")
365+
366+
return await self.run(
367+
["pull", *local_flags],
368+
log_in_real_time=log_in_real_time,
369+
check_returncode=check_returncode,
370+
timeout=timeout,
371+
)

0 commit comments

Comments
 (0)