gitbolt 0.0.0.dev15__tar.gz → 0.0.0.dev17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/PKG-INFO +30 -4
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/README.md +28 -2
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/pyproject.toml +3 -5
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/base.py +190 -7
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/impl/simple.py +13 -1
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/runner/base.py +34 -1
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/runner/simple.py +49 -2
- gitbolt-0.0.0.dev17/src/gitbolt/subprocess/session.py +33 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/PKG-INFO +30 -4
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/SOURCES.txt +1 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/requires.txt +1 -1
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/LICENSE +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/setup.cfg +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/__init__.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/_internal_init.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/add.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/base.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/constants.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/exceptions.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/ls_tree.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/models.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/py.typed +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/pytest_plugin.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/__init__.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/_internal_init.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/add.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/constants.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/exceptions.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/impl/__init__.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/ls_tree.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/runner/__init__.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/subprocess/utils.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt/utils.py +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/dependency_links.txt +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/entry_points.txt +0 -0
- {gitbolt-0.0.0.dev15 → gitbolt-0.0.0.dev17}/src/gitbolt.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitbolt
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev17
|
|
4
4
|
Summary: Fast, flexible and type-safe Git commands in Python.
|
|
5
5
|
Author-email: Suhas Krishna Srivastava <suhas.srivastava@vaastav.tech>
|
|
6
6
|
Maintainer-email: Suhas Krishna Srivastava <suhas.srivastava@vaastav.tech>
|
|
@@ -27,7 +27,7 @@ Requires-Python: >=3.12
|
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Requires-Dist: vt-err-hndlr>=0.0.0dev6
|
|
30
|
-
Requires-Dist: vt-commons>=0.0.1.
|
|
30
|
+
Requires-Dist: vt-commons>=0.0.1.dev11
|
|
31
31
|
Provides-Extra: pygit2
|
|
32
32
|
Requires-Dist: pygit2; extra == "pygit2"
|
|
33
33
|
Provides-Extra: test
|
|
@@ -327,10 +327,12 @@ no_advice_reset_git = overridden_git.git_opts_override(no_advice=False)
|
|
|
327
327
|
|
|
328
328
|
At last, run unchecked commands in git.
|
|
329
329
|
|
|
330
|
-
Introduced in `0.0.
|
|
330
|
+
Introduced in `0.0.0.dev4` to
|
|
331
331
|
- experiment.
|
|
332
332
|
- have consistent interfaced commands run until all subcommands are provided by the library.
|
|
333
333
|
|
|
334
|
+
#### 🖥️ Run one process per command
|
|
335
|
+
|
|
334
336
|
```python
|
|
335
337
|
import gitbolt
|
|
336
338
|
|
|
@@ -340,9 +342,33 @@ git.subcmd_unchecked.run(['--version']) # run the version option for git.
|
|
|
340
342
|
git.subcmd_unchecked.run(['version']) # run the version subcommand.
|
|
341
343
|
```
|
|
342
344
|
|
|
345
|
+
#### 🖥️ Run one long-running process and communicate with it
|
|
346
|
+
|
|
347
|
+
Introduced in `0.0.0.dev16` to:
|
|
348
|
+
- Make communicable processes using `subprocess.Popen`.
|
|
349
|
+
|
|
350
|
+
Get a long-running process and communicate with it for batching and faster operations.
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
import gitbolt
|
|
354
|
+
import sys
|
|
355
|
+
|
|
356
|
+
git = gitbolt.get_git_command()
|
|
357
|
+
with git.subcmd_unchecked.popen(["cat-file", "--batch-command"]) as cf:
|
|
358
|
+
cf.stdin.write(b"contents HEAD\n")
|
|
359
|
+
cf.stdin.flush()
|
|
360
|
+
header = cf.stdout.readline().strip()
|
|
361
|
+
print(f"HEADER: {header}", file=sys.stderr)
|
|
362
|
+
obj, typ, size = header.split()
|
|
363
|
+
print(cf.stdout.read(int(size)))
|
|
364
|
+
cf.stdout.readline()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Error handling and I/O management is left to the client/caller.
|
|
368
|
+
|
|
343
369
|
#### 💻 Run commands received from CLI
|
|
344
370
|
|
|
345
|
-
Introduced in `0.0.
|
|
371
|
+
Introduced in `0.0.0.dev11` is the ability to take commands from CLI and run it inside `gitbolt`.
|
|
346
372
|
|
|
347
373
|
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
348
374
|
would be to make a system that receives CLI commands and does certain modifications/additions inside `gitbolt` before
|
|
@@ -291,10 +291,12 @@ no_advice_reset_git = overridden_git.git_opts_override(no_advice=False)
|
|
|
291
291
|
|
|
292
292
|
At last, run unchecked commands in git.
|
|
293
293
|
|
|
294
|
-
Introduced in `0.0.
|
|
294
|
+
Introduced in `0.0.0.dev4` to
|
|
295
295
|
- experiment.
|
|
296
296
|
- have consistent interfaced commands run until all subcommands are provided by the library.
|
|
297
297
|
|
|
298
|
+
#### 🖥️ Run one process per command
|
|
299
|
+
|
|
298
300
|
```python
|
|
299
301
|
import gitbolt
|
|
300
302
|
|
|
@@ -304,9 +306,33 @@ git.subcmd_unchecked.run(['--version']) # run the version option for git.
|
|
|
304
306
|
git.subcmd_unchecked.run(['version']) # run the version subcommand.
|
|
305
307
|
```
|
|
306
308
|
|
|
309
|
+
#### 🖥️ Run one long-running process and communicate with it
|
|
310
|
+
|
|
311
|
+
Introduced in `0.0.0.dev16` to:
|
|
312
|
+
- Make communicable processes using `subprocess.Popen`.
|
|
313
|
+
|
|
314
|
+
Get a long-running process and communicate with it for batching and faster operations.
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
import gitbolt
|
|
318
|
+
import sys
|
|
319
|
+
|
|
320
|
+
git = gitbolt.get_git_command()
|
|
321
|
+
with git.subcmd_unchecked.popen(["cat-file", "--batch-command"]) as cf:
|
|
322
|
+
cf.stdin.write(b"contents HEAD\n")
|
|
323
|
+
cf.stdin.flush()
|
|
324
|
+
header = cf.stdout.readline().strip()
|
|
325
|
+
print(f"HEADER: {header}", file=sys.stderr)
|
|
326
|
+
obj, typ, size = header.split()
|
|
327
|
+
print(cf.stdout.read(int(size)))
|
|
328
|
+
cf.stdout.readline()
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Error handling and I/O management is left to the client/caller.
|
|
332
|
+
|
|
307
333
|
#### 💻 Run commands received from CLI
|
|
308
334
|
|
|
309
|
-
Introduced in `0.0.
|
|
335
|
+
Introduced in `0.0.0.dev11` is the ability to take commands from CLI and run it inside `gitbolt`.
|
|
310
336
|
|
|
311
337
|
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
312
338
|
would be to make a system that receives CLI commands and does certain modifications/additions inside `gitbolt` before
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gitbolt"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.0.dev17"
|
|
4
4
|
description = "Fast, flexible and type-safe Git commands in Python."
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
readme = "README.md"
|
|
@@ -37,7 +37,7 @@ classifiers = [
|
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
39
|
"vt-err-hndlr >= 0.0.0dev6",
|
|
40
|
-
"vt-commons >= 0.0.1.
|
|
40
|
+
"vt-commons >= 0.0.1.dev11",
|
|
41
41
|
]
|
|
42
42
|
|
|
43
43
|
[project.optional-dependencies]
|
|
@@ -45,9 +45,7 @@ pygit2 = ['pygit2']
|
|
|
45
45
|
test = ['pytest']
|
|
46
46
|
|
|
47
47
|
[dependency-groups]
|
|
48
|
-
|
|
49
|
-
# btw, this error doesn't show in the latest developer version. So, this error will go away with 1.17.0 relase
|
|
50
|
-
dev = ["mypy==1.15.0", "ruff"]
|
|
48
|
+
dev = ["mypy", "ruff"]
|
|
51
49
|
doc = ['sphinx', 'sphinx-argparse']
|
|
52
50
|
test = ["pytest", 'pytest-cov', "pytest-xdist"]
|
|
53
51
|
multitest = ['tox']
|
|
@@ -9,8 +9,10 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from abc import abstractmethod, ABC
|
|
11
11
|
from collections.abc import Callable
|
|
12
|
+
from contextlib import AbstractContextManager
|
|
12
13
|
from pathlib import Path
|
|
13
|
-
from subprocess import CompletedProcess
|
|
14
|
+
from subprocess import CompletedProcess, Popen, PIPE
|
|
15
|
+
from types import SimpleNamespace
|
|
14
16
|
from typing import override, Protocol, Unpack, Self, overload, Literal, Any
|
|
15
17
|
|
|
16
18
|
from vt.utils.commons.commons.core_py import is_unset, not_none_not_unset
|
|
@@ -295,9 +297,94 @@ class GitCommand(Git, ABC):
|
|
|
295
297
|
def subcmd_unchecked(self) -> UncheckedSubcmd:
|
|
296
298
|
"""
|
|
297
299
|
Run an unchecked git subcommand using subprocess.
|
|
300
|
+
|
|
301
|
+
:returns: An unchecked subcommand instance that simply runs the asked command in a ``subprocess.run()`` with
|
|
302
|
+
optional ``subprocess.Popen()``.
|
|
298
303
|
"""
|
|
299
304
|
...
|
|
300
305
|
|
|
306
|
+
@abstractmethod
|
|
307
|
+
def session(self, **commands: list[str] | Callable[[], Popen[bytes]]) -> GitSession:
|
|
308
|
+
"""
|
|
309
|
+
Run long-running git session with multiple unchecked subcommands using ``subprocess.Popen`` and
|
|
310
|
+
communicate with them.
|
|
311
|
+
|
|
312
|
+
Client/Caller can communicate with ``GitSession``'s processes with their stdin and stdout.
|
|
313
|
+
|
|
314
|
+
Examples:
|
|
315
|
+
|
|
316
|
+
Obtain a session:
|
|
317
|
+
|
|
318
|
+
>>> import gitbolt
|
|
319
|
+
>>> _git = gitbolt.get_git_command()
|
|
320
|
+
>>> ses = _git.session(ls_tree=["ls-tree", "HEAD"], cat_file=["cat-file", "--batch"])
|
|
321
|
+
>>> with ses: # start the session by ctx mgr
|
|
322
|
+
... pass # any communication can be done by Popen semantics.
|
|
323
|
+
|
|
324
|
+
Start the session in one go:
|
|
325
|
+
|
|
326
|
+
>>> with _git.session(cat_file=["cat-file", "--batch"]) as ses: # obtain, start and ctx manage the session.
|
|
327
|
+
... pass # any communication can be done by Popen semantics.
|
|
328
|
+
|
|
329
|
+
:param commands: list of string git commands suppliable to ``subprocess.Popen`` or ``subprocess.Popen`` lambdas.
|
|
330
|
+
:returns: A (not yet started) long-running ``GitSession`` context manager.
|
|
331
|
+
"""
|
|
332
|
+
...
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class GitSession(HasGitUnderneath[GitCommand], AbstractContextManager):
|
|
336
|
+
|
|
337
|
+
def __init__(self, git: GitCommand, **commands: Callable[[], Popen[bytes]]):
|
|
338
|
+
"""
|
|
339
|
+
Context Manager to start a git long-running session. Useful when a command is to be held in open state and be
|
|
340
|
+
communicated with its stdin and stdout. This is way faster that spawning multiple processes each time.
|
|
341
|
+
|
|
342
|
+
Examples:
|
|
343
|
+
|
|
344
|
+
Obtain a session:
|
|
345
|
+
|
|
346
|
+
>>> import gitbolt
|
|
347
|
+
>>> _git = gitbolt.get_git_command()
|
|
348
|
+
>>> ses = _git.session(ls_tree=["ls-tree", "HEAD"], cat_file=["cat-file", "--batch"])
|
|
349
|
+
>>> with ses: # start the session by ctx mgr
|
|
350
|
+
... pass # any communication can be done by Popen semantics.
|
|
351
|
+
|
|
352
|
+
Start the session in one go:
|
|
353
|
+
|
|
354
|
+
>>> with _git.session(cat_file=["cat-file", "--batch"]) as ses: # obtain, start and ctx manage the session.
|
|
355
|
+
... pass # any communication can be done by Popen semantics.
|
|
356
|
+
|
|
357
|
+
:param git: ``gitbolt.subprocess.GitCommand`` instance.
|
|
358
|
+
:param commands: commands in kwargs fashion.
|
|
359
|
+
"""
|
|
360
|
+
self._git = git
|
|
361
|
+
self.unstarted_commands: dict[str, Callable[[], Popen[bytes]]] = commands
|
|
362
|
+
self.started_commands: dict[str, Popen[bytes]] = dict()
|
|
363
|
+
self.commands: SimpleNamespace | None = None
|
|
364
|
+
|
|
365
|
+
def __enter__(self) -> Self:
|
|
366
|
+
processes_started_keys: list[str] = []
|
|
367
|
+
for unstarted_command_key, unstarted_command in self.unstarted_commands.items():
|
|
368
|
+
self.started_commands[unstarted_command_key] = unstarted_command().__enter__()
|
|
369
|
+
processes_started_keys.append(unstarted_command_key)
|
|
370
|
+
self.commands = SimpleNamespace(**self.started_commands)
|
|
371
|
+
for pk in processes_started_keys:
|
|
372
|
+
del self.unstarted_commands[pk]
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def git(self) -> GitCommand:
|
|
376
|
+
return self._git
|
|
377
|
+
|
|
378
|
+
def __exit__(self, exc_type, exc_value, traceback, /):
|
|
379
|
+
process_done_keys: list[str] = []
|
|
380
|
+
for started_popen_key, started_popen in self.started_commands.items():
|
|
381
|
+
started_popen.__exit__(exc_type, exc_value, traceback)
|
|
382
|
+
process_done_keys.append(started_popen_key)
|
|
383
|
+
self.commands = None
|
|
384
|
+
for pk in process_done_keys:
|
|
385
|
+
del self.started_commands[pk]
|
|
386
|
+
return False
|
|
387
|
+
|
|
301
388
|
|
|
302
389
|
class GitSubcmdCommand(GitSubCommand, HasGitUnderneath["GitCommand"], Protocol):
|
|
303
390
|
"""
|
|
@@ -584,12 +671,8 @@ class UncheckedSubcmd(GitSubcmdCommand, RootDirOp, Protocol):
|
|
|
584
671
|
|
|
585
672
|
:return: ``CompletedProcess`` capturing all the required stdout, stderr, return-code etc.
|
|
586
673
|
"""
|
|
587
|
-
main_cmd_args = self.
|
|
588
|
-
envs_vars = self.
|
|
589
|
-
another_supplied_env = subprocess_run_kwargs.pop("env", None)
|
|
590
|
-
if another_supplied_env:
|
|
591
|
-
if envs_vars is not None:
|
|
592
|
-
envs_vars.update(another_supplied_env)
|
|
674
|
+
main_cmd_args = self.git_main_cmd_args()
|
|
675
|
+
envs_vars = self.git_envs(subprocess_run_kwargs.pop("env", None))
|
|
593
676
|
cwd = subprocess_run_kwargs.pop("cwd", self.root_dir)
|
|
594
677
|
capture_output = subprocess_run_kwargs.pop("capture_output", True)
|
|
595
678
|
check = subprocess_run_kwargs.pop("check", True)
|
|
@@ -607,3 +690,103 @@ class UncheckedSubcmd(GitSubcmdCommand, RootDirOp, Protocol):
|
|
|
607
690
|
**subprocess_run_kwargs,
|
|
608
691
|
)
|
|
609
692
|
return result
|
|
693
|
+
|
|
694
|
+
@overload
|
|
695
|
+
def popen(
|
|
696
|
+
self,
|
|
697
|
+
subcommand_args: list[str],
|
|
698
|
+
*popen_args: Any,
|
|
699
|
+
text: Literal[True] = True,
|
|
700
|
+
**popen_kwargs: Any,
|
|
701
|
+
) -> Popen[str]: ...
|
|
702
|
+
|
|
703
|
+
@overload
|
|
704
|
+
def popen(
|
|
705
|
+
self,
|
|
706
|
+
subcommand_args: list[str],
|
|
707
|
+
*popen_args: Any,
|
|
708
|
+
text: Literal[False] = False,
|
|
709
|
+
**popen_kwargs: Any,
|
|
710
|
+
) -> Popen[bytes]: ...
|
|
711
|
+
|
|
712
|
+
def popen(
|
|
713
|
+
self,
|
|
714
|
+
subcommand_args: list[str],
|
|
715
|
+
*popen_args: Any,
|
|
716
|
+
text: Literal[True, False] = False,
|
|
717
|
+
**popen_kwargs: Any,
|
|
718
|
+
) -> Popen[str] | Popen[bytes]:
|
|
719
|
+
"""
|
|
720
|
+
Open unchecked git subcommand communicable process, using ``subprocess.Popen``.
|
|
721
|
+
|
|
722
|
+
All the arguments are congruent to ``subprocess.Popen`` and mostly passes as-is.
|
|
723
|
+
|
|
724
|
+
:param subcommand_args: the full subcommand argument list.
|
|
725
|
+
:param popen_args: additional subprocess positionals.
|
|
726
|
+
:param text: ``_input`` and returns both are str if this value is ``True``. Else, bytes are considered.
|
|
727
|
+
:param popen_kwargs: additional subprocess keyword arguments.
|
|
728
|
+
|
|
729
|
+
:return: ``Popen`` capturing all the required stdout, stderr etc and streaming stdin.
|
|
730
|
+
"""
|
|
731
|
+
main_cmd_args = self.git_main_cmd_args()
|
|
732
|
+
envs_vars = self.git_envs(popen_kwargs.pop("env", None))
|
|
733
|
+
cwd = popen_kwargs.pop("cwd", self.root_dir)
|
|
734
|
+
stdin = popen_kwargs.pop("stdin", PIPE)
|
|
735
|
+
stdout = popen_kwargs.pop("stdout", PIPE)
|
|
736
|
+
stderr = popen_kwargs.pop("stderr", PIPE)
|
|
737
|
+
bufsize = popen_kwargs.pop("bufsize", 0)
|
|
738
|
+
# Popen the git command
|
|
739
|
+
result = self.git.runner.popen_git_command(
|
|
740
|
+
main_cmd_args,
|
|
741
|
+
subcommand_args,
|
|
742
|
+
*popen_args,
|
|
743
|
+
text=text,
|
|
744
|
+
env=envs_vars,
|
|
745
|
+
cwd=cwd,
|
|
746
|
+
stdin=stdin,
|
|
747
|
+
stdout=stdout,
|
|
748
|
+
stderr=stderr,
|
|
749
|
+
bufsize=bufsize,
|
|
750
|
+
**popen_kwargs,
|
|
751
|
+
)
|
|
752
|
+
return result
|
|
753
|
+
|
|
754
|
+
def make_cmd(self, subcommand_args: list[str]) -> list[str]:
|
|
755
|
+
"""
|
|
756
|
+
Make full runnable command for execution from the supplied ``subcommand_args``.
|
|
757
|
+
|
|
758
|
+
As knowledge of the git program and the main command is encapsulated within this ``UncheckedSubcmd`` thus,
|
|
759
|
+
this is a convenience method for any external entities that want to run commands in their own subprocess.
|
|
760
|
+
|
|
761
|
+
:param subcommand_args: arguments for subcommand.
|
|
762
|
+
:returns: a fully made and runnable command for some external ``subprocess`` call.
|
|
763
|
+
"""
|
|
764
|
+
return self.git.runner.make_cmd(self.git.build_main_cmd_args(), subcommand_args)
|
|
765
|
+
|
|
766
|
+
def git_main_cmd_args(self) -> list[str]:
|
|
767
|
+
"""
|
|
768
|
+
Get CLI args for git main cli command.
|
|
769
|
+
|
|
770
|
+
For example, ``--no-pager --no-advice`` is the git main command in ``git --no-pager --no-advice log master -1``.
|
|
771
|
+
|
|
772
|
+
:return: CLI args for git main cli command.
|
|
773
|
+
"""
|
|
774
|
+
return self.git.build_main_cmd_args()
|
|
775
|
+
|
|
776
|
+
def git_envs(
|
|
777
|
+
self, extra_git_envs: dict[str, str] | None = None
|
|
778
|
+
) -> dict[str, str] | None:
|
|
779
|
+
"""
|
|
780
|
+
Get Git environment variables from the merged ``GitEnvVars`` object.
|
|
781
|
+
|
|
782
|
+
Skips values that are ``Unset`` or ``None``-like using ``not_none_not_unset()``.
|
|
783
|
+
Converts ``Path`` and ``datetime`` instances to ``str``.
|
|
784
|
+
|
|
785
|
+
:param extra_git_envs: extraneous git envs supplied by the caller. These will be merged into the resultant
|
|
786
|
+
git envs and then returned.
|
|
787
|
+
:return: A cleaned and normalized GitEnvVars dict suitable for use in subprocesses.
|
|
788
|
+
"""
|
|
789
|
+
env_vars = self.git.build_git_envs()
|
|
790
|
+
if extra_git_envs and env_vars is not None:
|
|
791
|
+
env_vars.update(extra_git_envs)
|
|
792
|
+
return env_vars
|
|
@@ -9,7 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from abc import ABC
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from
|
|
12
|
+
from subprocess import Popen
|
|
13
|
+
from typing import override, Literal, overload, Callable
|
|
13
14
|
|
|
14
15
|
from vt.utils.commons.commons.op import RootDirOp
|
|
15
16
|
|
|
@@ -24,6 +25,7 @@ from gitbolt.subprocess import (
|
|
|
24
25
|
UncheckedSubcmd,
|
|
25
26
|
)
|
|
26
27
|
from gitbolt.subprocess.add import AddCLIArgsBuilder
|
|
28
|
+
from gitbolt.subprocess.base import GitSession
|
|
27
29
|
from gitbolt.subprocess.constants import VERSION_CMD
|
|
28
30
|
from gitbolt.subprocess.ls_tree import LsTreeCLIArgsBuilder
|
|
29
31
|
from gitbolt.subprocess.runner import GitCommandRunner
|
|
@@ -168,6 +170,7 @@ class UncheckedSubcmdImpl(UncheckedSubcmd, GitSubcmdCommandImpl):
|
|
|
168
170
|
|
|
169
171
|
|
|
170
172
|
class SimpleGitCommand(GitCommand, RootDirOp):
|
|
173
|
+
|
|
171
174
|
def __init__(
|
|
172
175
|
self,
|
|
173
176
|
git_root_dir: Path = Path.cwd(),
|
|
@@ -245,6 +248,15 @@ class SimpleGitCommand(GitCommand, RootDirOp):
|
|
|
245
248
|
subcmd_unchecked._set_underlying_git(self)
|
|
246
249
|
return subcmd_unchecked
|
|
247
250
|
|
|
251
|
+
def session(self, **commands: list[str] | Callable[[], Popen[bytes]]) -> GitSession:
|
|
252
|
+
cmds: dict[str, Callable[[], Popen[bytes]]] = {}
|
|
253
|
+
for cmd_name, runnable_cmd in commands.items():
|
|
254
|
+
if callable(runnable_cmd):
|
|
255
|
+
cmds[cmd_name] = runnable_cmd
|
|
256
|
+
else:
|
|
257
|
+
cmds[cmd_name] = lambda : self.subcmd_unchecked.popen(runnable_cmd, text=False)
|
|
258
|
+
return GitSession(self, **cmds)
|
|
259
|
+
|
|
248
260
|
|
|
249
261
|
class CLISimpleGitCommand(SimpleGitCommand):
|
|
250
262
|
"""
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import pathlib
|
|
11
11
|
from abc import abstractmethod
|
|
12
|
-
from subprocess import CompletedProcess
|
|
12
|
+
from subprocess import CompletedProcess, Popen
|
|
13
13
|
from typing import Protocol, overload, Any, Literal
|
|
14
14
|
|
|
15
15
|
|
|
@@ -64,6 +64,39 @@ class GitCommandRunner(Protocol):
|
|
|
64
64
|
**subprocess_run_kwargs: Any,
|
|
65
65
|
) -> CompletedProcess[bytes]: ...
|
|
66
66
|
|
|
67
|
+
@overload
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def popen_git_command(
|
|
70
|
+
self,
|
|
71
|
+
main_cmd_args: list[str],
|
|
72
|
+
subcommand_args: list[str],
|
|
73
|
+
*popen_run_args: Any,
|
|
74
|
+
text: Literal[False],
|
|
75
|
+
**popen_run_kwargs: Any,
|
|
76
|
+
) -> Popen[bytes]: ...
|
|
77
|
+
|
|
78
|
+
@overload
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def popen_git_command(
|
|
81
|
+
self,
|
|
82
|
+
main_cmd_args: list[str],
|
|
83
|
+
subcommand_args: list[str],
|
|
84
|
+
*popen_run_args: Any,
|
|
85
|
+
text: Literal[True],
|
|
86
|
+
**popen_run_kwargs: Any,
|
|
87
|
+
) -> Popen[str]: ...
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def make_cmd(self, main_cmd_args: list[str], sub_cmd_args: list[str]) -> list[str]:
|
|
91
|
+
"""
|
|
92
|
+
Make command for execution for use in an external subprocess.
|
|
93
|
+
|
|
94
|
+
:param main_cmd_args: arguments for the main command.
|
|
95
|
+
:param sub_cmd_args: arguments for subcommand.
|
|
96
|
+
:returns: a fully made and runnable command for some external ``subprocess`` call.
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
67
100
|
@property
|
|
68
101
|
@abstractmethod
|
|
69
102
|
def git_prog(self) -> str | pathlib.Path:
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import pathlib
|
|
11
11
|
import subprocess
|
|
12
|
-
from subprocess import CompletedProcess
|
|
12
|
+
from subprocess import CompletedProcess, Popen
|
|
13
13
|
from typing import overload, override, Any, Literal
|
|
14
14
|
|
|
15
15
|
from gitbolt.subprocess.constants import GIT_CMD
|
|
@@ -86,7 +86,7 @@ class SimpleGitCR(GitCommandRunner):
|
|
|
86
86
|
) -> CompletedProcess[str] | CompletedProcess[bytes]:
|
|
87
87
|
try:
|
|
88
88
|
return subprocess.run(
|
|
89
|
-
|
|
89
|
+
self.make_cmd(main_cmd_args, subcommand_args),
|
|
90
90
|
*subprocess_run_args,
|
|
91
91
|
input=_input,
|
|
92
92
|
text=text,
|
|
@@ -97,6 +97,53 @@ class SimpleGitCR(GitCommandRunner):
|
|
|
97
97
|
e.stderr, called_process_error=e, exit_code=e.returncode
|
|
98
98
|
) from e
|
|
99
99
|
|
|
100
|
+
@overload
|
|
101
|
+
@override
|
|
102
|
+
def popen_git_command(
|
|
103
|
+
self,
|
|
104
|
+
main_cmd_args: list[str],
|
|
105
|
+
subcommand_args: list[str],
|
|
106
|
+
*popen_run_args: Any,
|
|
107
|
+
text: Literal[False],
|
|
108
|
+
**popen_run_kwargs: Any,
|
|
109
|
+
) -> Popen[bytes]: ...
|
|
110
|
+
|
|
111
|
+
@overload
|
|
112
|
+
@override
|
|
113
|
+
def popen_git_command(
|
|
114
|
+
self,
|
|
115
|
+
main_cmd_args: list[str],
|
|
116
|
+
subcommand_args: list[str],
|
|
117
|
+
*popen_run_args: Any,
|
|
118
|
+
text: Literal[True],
|
|
119
|
+
**popen_run_kwargs: Any,
|
|
120
|
+
) -> Popen[str]: ...
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
def popen_git_command(
|
|
124
|
+
self,
|
|
125
|
+
main_cmd_args: list[str],
|
|
126
|
+
subcommand_args: list[str],
|
|
127
|
+
*popen_run_args: Any,
|
|
128
|
+
text: Literal[True, False],
|
|
129
|
+
**popen_run_kwargs: Any,
|
|
130
|
+
) -> Popen[str] | Popen[bytes]:
|
|
131
|
+
try:
|
|
132
|
+
return subprocess.Popen(
|
|
133
|
+
self.make_cmd(main_cmd_args, subcommand_args),
|
|
134
|
+
*popen_run_args,
|
|
135
|
+
text=text,
|
|
136
|
+
**popen_run_kwargs,
|
|
137
|
+
)
|
|
138
|
+
except subprocess.CalledProcessError as e:
|
|
139
|
+
raise GitCmdException(
|
|
140
|
+
e.stderr, called_process_error=e, exit_code=e.returncode
|
|
141
|
+
) from e
|
|
142
|
+
|
|
143
|
+
@override
|
|
144
|
+
def make_cmd(self, main_cmd_args: list[str], sub_cmd_args: list[str]) -> list[str]:
|
|
145
|
+
return [str(self.git_prog), *main_cmd_args, *sub_cmd_args]
|
|
146
|
+
|
|
100
147
|
@override
|
|
101
148
|
@property
|
|
102
149
|
def git_prog(self) -> str | pathlib.Path:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Git command sessions utilities for long-running commands.
|
|
6
|
+
|
|
7
|
+
Create sessions for long-running commands and communicate with them using their stdin/stdout.
|
|
8
|
+
|
|
9
|
+
Much faster that subprocess creation for each input/output pair.
|
|
10
|
+
"""
|
|
11
|
+
import subprocess
|
|
12
|
+
|
|
13
|
+
from vt.utils.commons.commons.core_py import read_exact
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cat_file_blob_content(cat_file_popen: subprocess.Popen[bytes], blob_hash: bytes) -> bytes:
|
|
17
|
+
"""
|
|
18
|
+
Read git blob contents using a long-running batched cat-file process.
|
|
19
|
+
|
|
20
|
+
Spawning new ``git show`` processes can be slower and resource consuming.
|
|
21
|
+
|
|
22
|
+
:param cat_file_popen: long-running ``git cat-file --batch`` process in bytes mode.
|
|
23
|
+
:param blob_hash: hash to be read from cat-file.
|
|
24
|
+
:return: contents of blob hash in bytes.
|
|
25
|
+
"""
|
|
26
|
+
write_obj = blob_hash + b"\n"
|
|
27
|
+
cat_file_popen.stdin.write(write_obj)
|
|
28
|
+
cat_file_popen.stdin.flush()
|
|
29
|
+
header = cat_file_popen.stdout.readline()
|
|
30
|
+
obj, typ, size = header.split()
|
|
31
|
+
blob_content: bytes = read_exact(cat_file_popen.stdout, int(size))
|
|
32
|
+
cat_file_popen.stdout.readline()
|
|
33
|
+
return blob_content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitbolt
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev17
|
|
4
4
|
Summary: Fast, flexible and type-safe Git commands in Python.
|
|
5
5
|
Author-email: Suhas Krishna Srivastava <suhas.srivastava@vaastav.tech>
|
|
6
6
|
Maintainer-email: Suhas Krishna Srivastava <suhas.srivastava@vaastav.tech>
|
|
@@ -27,7 +27,7 @@ Requires-Python: >=3.12
|
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Requires-Dist: vt-err-hndlr>=0.0.0dev6
|
|
30
|
-
Requires-Dist: vt-commons>=0.0.1.
|
|
30
|
+
Requires-Dist: vt-commons>=0.0.1.dev11
|
|
31
31
|
Provides-Extra: pygit2
|
|
32
32
|
Requires-Dist: pygit2; extra == "pygit2"
|
|
33
33
|
Provides-Extra: test
|
|
@@ -327,10 +327,12 @@ no_advice_reset_git = overridden_git.git_opts_override(no_advice=False)
|
|
|
327
327
|
|
|
328
328
|
At last, run unchecked commands in git.
|
|
329
329
|
|
|
330
|
-
Introduced in `0.0.
|
|
330
|
+
Introduced in `0.0.0.dev4` to
|
|
331
331
|
- experiment.
|
|
332
332
|
- have consistent interfaced commands run until all subcommands are provided by the library.
|
|
333
333
|
|
|
334
|
+
#### 🖥️ Run one process per command
|
|
335
|
+
|
|
334
336
|
```python
|
|
335
337
|
import gitbolt
|
|
336
338
|
|
|
@@ -340,9 +342,33 @@ git.subcmd_unchecked.run(['--version']) # run the version option for git.
|
|
|
340
342
|
git.subcmd_unchecked.run(['version']) # run the version subcommand.
|
|
341
343
|
```
|
|
342
344
|
|
|
345
|
+
#### 🖥️ Run one long-running process and communicate with it
|
|
346
|
+
|
|
347
|
+
Introduced in `0.0.0.dev16` to:
|
|
348
|
+
- Make communicable processes using `subprocess.Popen`.
|
|
349
|
+
|
|
350
|
+
Get a long-running process and communicate with it for batching and faster operations.
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
import gitbolt
|
|
354
|
+
import sys
|
|
355
|
+
|
|
356
|
+
git = gitbolt.get_git_command()
|
|
357
|
+
with git.subcmd_unchecked.popen(["cat-file", "--batch-command"]) as cf:
|
|
358
|
+
cf.stdin.write(b"contents HEAD\n")
|
|
359
|
+
cf.stdin.flush()
|
|
360
|
+
header = cf.stdout.readline().strip()
|
|
361
|
+
print(f"HEADER: {header}", file=sys.stderr)
|
|
362
|
+
obj, typ, size = header.split()
|
|
363
|
+
print(cf.stdout.read(int(size)))
|
|
364
|
+
cf.stdout.readline()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Error handling and I/O management is left to the client/caller.
|
|
368
|
+
|
|
343
369
|
#### 💻 Run commands received from CLI
|
|
344
370
|
|
|
345
|
-
Introduced in `0.0.
|
|
371
|
+
Introduced in `0.0.0.dev11` is the ability to take commands from CLI and run it inside `gitbolt`.
|
|
346
372
|
|
|
347
373
|
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
348
374
|
would be to make a system that receives CLI commands and does certain modifications/additions inside `gitbolt` before
|
|
@@ -25,6 +25,7 @@ src/gitbolt/subprocess/base.py
|
|
|
25
25
|
src/gitbolt/subprocess/constants.py
|
|
26
26
|
src/gitbolt/subprocess/exceptions.py
|
|
27
27
|
src/gitbolt/subprocess/ls_tree.py
|
|
28
|
+
src/gitbolt/subprocess/session.py
|
|
28
29
|
src/gitbolt/subprocess/utils.py
|
|
29
30
|
src/gitbolt/subprocess/impl/__init__.py
|
|
30
31
|
src/gitbolt/subprocess/impl/simple.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|