gitbolt 0.0.0.dev17__tar.gz → 0.0.0.dev19__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.dev17 → gitbolt-0.0.0.dev19}/PKG-INFO +40 -4
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/README.md +39 -3
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/pyproject.toml +1 -1
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/__init__.py +1 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/base.py +119 -31
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/impl/simple.py +7 -2
- gitbolt-0.0.0.dev19/src/gitbolt/subprocess/utils/__init__.py +6 -0
- gitbolt-0.0.0.dev19/src/gitbolt/subprocess/utils/models.py +95 -0
- gitbolt-0.0.0.dev19/src/gitbolt/subprocess/utils/session.py +562 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt.egg-info/PKG-INFO +40 -4
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt.egg-info/SOURCES.txt +5 -3
- gitbolt-0.0.0.dev17/src/gitbolt/subprocess/session.py +0 -33
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/LICENSE +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/setup.cfg +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/_internal_init.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/add.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/base.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/constants.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/exceptions.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/ls_tree.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/models.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/py.typed +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/pytest_plugin.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/__init__.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/_internal_init.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/add.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/constants.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/exceptions.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/impl/__init__.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/ls_tree.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/runner/__init__.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/runner/base.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/subprocess/runner/simple.py +0 -0
- /gitbolt-0.0.0.dev17/src/gitbolt/subprocess/utils.py → /gitbolt-0.0.0.dev19/src/gitbolt/subprocess/utils/cmd.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt/utils.py +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt.egg-info/dependency_links.txt +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt.egg-info/entry_points.txt +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/src/gitbolt.egg-info/requires.txt +0 -0
- {gitbolt-0.0.0.dev17 → gitbolt-0.0.0.dev19}/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.dev19
|
|
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>
|
|
@@ -146,7 +146,7 @@ print(version_stdout)
|
|
|
146
146
|
|
|
147
147
|
#### 🧑💻 Modular at the programmatic level
|
|
148
148
|
|
|
149
|
-
Commands are designed to be passed around as objects. This makes them modular and thus users can opt to use only
|
|
149
|
+
Commands are designed to be passed around as objects. This makes them modular and thus users can opt to use only
|
|
150
150
|
particular commands.
|
|
151
151
|
|
|
152
152
|
```python
|
|
@@ -327,7 +327,7 @@ 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.0.dev4` to
|
|
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
|
|
|
@@ -366,11 +366,47 @@ with git.subcmd_unchecked.popen(["cat-file", "--batch-command"]) as cf:
|
|
|
366
366
|
|
|
367
367
|
Error handling and I/O management is left to the client/caller.
|
|
368
368
|
|
|
369
|
+
#### 🏃 Communicate with long-running git sessions
|
|
370
|
+
|
|
371
|
+
Introduced in `0.0.0.dev17` to:
|
|
372
|
+
- Make long-running git command sessions.
|
|
373
|
+
- Have reentrant sessions.
|
|
374
|
+
- Communicate with long-running git command session using their `stdin` and `stdout`.
|
|
375
|
+
|
|
376
|
+
Get long-running process sessions and communicate with them for batching and faster operations.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
import gitbolt
|
|
380
|
+
from gitbolt.subprocess.utils.session import cat_file_blob_content, cat_file_tree_data, cat_file_tree_content, \
|
|
381
|
+
mktree_tree_make
|
|
382
|
+
|
|
383
|
+
git = gitbolt.get_git_command()
|
|
384
|
+
with git.session(
|
|
385
|
+
cat_file1=["cat-file", "--batch"],
|
|
386
|
+
cat_file2=["cat-file", "--batch"],
|
|
387
|
+
cat_file3=lambda: git.subcmd_unchecked.popen(["cat-file", "--batch"]), # pass your own git subcmd Popen(s)
|
|
388
|
+
mktree=["mktree", "--batch", "-z"]
|
|
389
|
+
) as ses:
|
|
390
|
+
tree_data: list[bytes] = []
|
|
391
|
+
for mode_, sha_, name_ in cat_file_tree_data(ses.commands.cat_file2, b"HEAD^{tree}"):
|
|
392
|
+
print(mode_, sha_, name_)
|
|
393
|
+
for tree_entry in cat_file_tree_content(ses.commands.cat_file2, b"HEAD^{tree}"):
|
|
394
|
+
tree_data.append(tree_entry)
|
|
395
|
+
print("+" * 40)
|
|
396
|
+
print(cat_file_blob_content(ses.commands.cat_file3, b"9854cb4d432a881f59d38582791cf2636e7819d9"))
|
|
397
|
+
print("~" * 40)
|
|
398
|
+
with ses: # is reentrant, if you want
|
|
399
|
+
tree_hash = mktree_tree_make(ses.commands.mktree, b"\0".join(tree_data)) # make tree
|
|
400
|
+
print("Obtained tree: ", tree_hash)
|
|
401
|
+
|
|
402
|
+
assert not ses.active # session no long active
|
|
403
|
+
```
|
|
404
|
+
|
|
369
405
|
#### 💻 Run commands received from CLI
|
|
370
406
|
|
|
371
407
|
Introduced in `0.0.0.dev11` is the ability to take commands from CLI and run it inside `gitbolt`.
|
|
372
408
|
|
|
373
|
-
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
409
|
+
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
374
410
|
would be to make a system that receives CLI commands and does certain modifications/additions inside `gitbolt` before
|
|
375
411
|
actually running them. An example:
|
|
376
412
|
|
|
@@ -110,7 +110,7 @@ print(version_stdout)
|
|
|
110
110
|
|
|
111
111
|
#### 🧑💻 Modular at the programmatic level
|
|
112
112
|
|
|
113
|
-
Commands are designed to be passed around as objects. This makes them modular and thus users can opt to use only
|
|
113
|
+
Commands are designed to be passed around as objects. This makes them modular and thus users can opt to use only
|
|
114
114
|
particular commands.
|
|
115
115
|
|
|
116
116
|
```python
|
|
@@ -291,7 +291,7 @@ 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.0.dev4` to
|
|
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
|
|
|
@@ -330,11 +330,47 @@ with git.subcmd_unchecked.popen(["cat-file", "--batch-command"]) as cf:
|
|
|
330
330
|
|
|
331
331
|
Error handling and I/O management is left to the client/caller.
|
|
332
332
|
|
|
333
|
+
#### 🏃 Communicate with long-running git sessions
|
|
334
|
+
|
|
335
|
+
Introduced in `0.0.0.dev17` to:
|
|
336
|
+
- Make long-running git command sessions.
|
|
337
|
+
- Have reentrant sessions.
|
|
338
|
+
- Communicate with long-running git command session using their `stdin` and `stdout`.
|
|
339
|
+
|
|
340
|
+
Get long-running process sessions and communicate with them for batching and faster operations.
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
import gitbolt
|
|
344
|
+
from gitbolt.subprocess.utils.session import cat_file_blob_content, cat_file_tree_data, cat_file_tree_content, \
|
|
345
|
+
mktree_tree_make
|
|
346
|
+
|
|
347
|
+
git = gitbolt.get_git_command()
|
|
348
|
+
with git.session(
|
|
349
|
+
cat_file1=["cat-file", "--batch"],
|
|
350
|
+
cat_file2=["cat-file", "--batch"],
|
|
351
|
+
cat_file3=lambda: git.subcmd_unchecked.popen(["cat-file", "--batch"]), # pass your own git subcmd Popen(s)
|
|
352
|
+
mktree=["mktree", "--batch", "-z"]
|
|
353
|
+
) as ses:
|
|
354
|
+
tree_data: list[bytes] = []
|
|
355
|
+
for mode_, sha_, name_ in cat_file_tree_data(ses.commands.cat_file2, b"HEAD^{tree}"):
|
|
356
|
+
print(mode_, sha_, name_)
|
|
357
|
+
for tree_entry in cat_file_tree_content(ses.commands.cat_file2, b"HEAD^{tree}"):
|
|
358
|
+
tree_data.append(tree_entry)
|
|
359
|
+
print("+" * 40)
|
|
360
|
+
print(cat_file_blob_content(ses.commands.cat_file3, b"9854cb4d432a881f59d38582791cf2636e7819d9"))
|
|
361
|
+
print("~" * 40)
|
|
362
|
+
with ses: # is reentrant, if you want
|
|
363
|
+
tree_hash = mktree_tree_make(ses.commands.mktree, b"\0".join(tree_data)) # make tree
|
|
364
|
+
print("Obtained tree: ", tree_hash)
|
|
365
|
+
|
|
366
|
+
assert not ses.active # session no long active
|
|
367
|
+
```
|
|
368
|
+
|
|
333
369
|
#### 💻 Run commands received from CLI
|
|
334
370
|
|
|
335
371
|
Introduced in `0.0.0.dev11` is the ability to take commands from CLI and run it inside `gitbolt`.
|
|
336
372
|
|
|
337
|
-
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
373
|
+
While making a system it may be required to run cli commands as received from cli using gitbolt. An obvious example
|
|
338
374
|
would be to make a system that receives CLI commands and does certain modifications/additions inside `gitbolt` before
|
|
339
375
|
actually running them. An example:
|
|
340
376
|
|
|
@@ -17,6 +17,7 @@ from gitbolt.base import LsTree as LsTree
|
|
|
17
17
|
from gitbolt.base import Version as Version
|
|
18
18
|
from gitbolt.base import Add as Add
|
|
19
19
|
from gitbolt.subprocess.base import GitCommand
|
|
20
|
+
from gitbolt.subprocess.base import GitSession as GitSession
|
|
20
21
|
# endregion
|
|
21
22
|
|
|
22
23
|
|
|
@@ -7,6 +7,7 @@ Git command interfaces with default implementation using subprocess calls.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import sys
|
|
10
11
|
from abc import abstractmethod, ABC
|
|
11
12
|
from collections.abc import Callable
|
|
12
13
|
from contextlib import AbstractContextManager
|
|
@@ -306,8 +307,7 @@ class GitCommand(Git, ABC):
|
|
|
306
307
|
@abstractmethod
|
|
307
308
|
def session(self, **commands: list[str] | Callable[[], Popen[bytes]]) -> GitSession:
|
|
308
309
|
"""
|
|
309
|
-
Run long-running git session with multiple unchecked subcommands using ``subprocess.Popen`` and
|
|
310
|
-
communicate with them.
|
|
310
|
+
Run long-running git session with multiple unchecked subcommands using ``subprocess.Popen`` and communicate with them.
|
|
311
311
|
|
|
312
312
|
Client/Caller can communicate with ``GitSession``'s processes with their stdin and stdout.
|
|
313
313
|
|
|
@@ -326,65 +326,155 @@ class GitCommand(Git, ABC):
|
|
|
326
326
|
>>> with _git.session(cat_file=["cat-file", "--batch"]) as ses: # obtain, start and ctx manage the session.
|
|
327
327
|
... pass # any communication can be done by Popen semantics.
|
|
328
328
|
|
|
329
|
+
Run special Popen:
|
|
330
|
+
|
|
331
|
+
>>> import gitbolt
|
|
332
|
+
>>> _git = gitbolt.get_git_command()
|
|
333
|
+
>>> ses = _git.session(ls_tree=["ls-tree", "HEAD"],
|
|
334
|
+
... cat_file=lambda : _git.subcmd_unchecked.popen(["cat-file", "--batch"], env=None))
|
|
335
|
+
>>> with ses: # start the session by ctx mgr
|
|
336
|
+
... pass # any communication can be done by Popen semantics.
|
|
337
|
+
|
|
338
|
+
Is reentrant:
|
|
339
|
+
|
|
340
|
+
>>> import gitbolt
|
|
341
|
+
>>> _git = gitbolt.get_git_command()
|
|
342
|
+
>>> ses = _git.session(cat_file=["cat-file", "--batch-check"])
|
|
343
|
+
>>> with ses: # start the session by ctx mgr
|
|
344
|
+
... with ses: # reentrant
|
|
345
|
+
... pass
|
|
346
|
+
... pass # any communication can be done by Popen semantics.
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
Note: All commands/processes are assumed:
|
|
350
|
+
|
|
351
|
+
- in bytes mode.
|
|
352
|
+
- pipe their stdin and stdout.
|
|
353
|
+
|
|
329
354
|
:param commands: list of string git commands suppliable to ``subprocess.Popen`` or ``subprocess.Popen`` lambdas.
|
|
330
355
|
:returns: A (not yet started) long-running ``GitSession`` context manager.
|
|
331
356
|
"""
|
|
332
357
|
...
|
|
333
358
|
|
|
334
359
|
|
|
360
|
+
# TODO: extract a base session class from this
|
|
335
361
|
class GitSession(HasGitUnderneath[GitCommand], AbstractContextManager):
|
|
336
|
-
|
|
337
362
|
def __init__(self, git: GitCommand, **commands: Callable[[], Popen[bytes]]):
|
|
338
363
|
"""
|
|
339
|
-
|
|
340
|
-
communicated with its stdin and stdout. This is way faster that spawning multiple
|
|
364
|
+
Reusable and reentrant context Manager to start a git long-running session. Useful when a command is to be
|
|
365
|
+
held in open state and be communicated with its stdin and stdout. This is way faster that spawning multiple
|
|
366
|
+
processes each time.
|
|
341
367
|
|
|
342
368
|
Examples:
|
|
343
369
|
|
|
344
370
|
Obtain a session:
|
|
345
371
|
|
|
346
372
|
>>> import gitbolt
|
|
373
|
+
>>> from gitbolt.subprocess.base import GitSession
|
|
347
374
|
>>> _git = gitbolt.get_git_command()
|
|
348
|
-
>>> ses = _git.
|
|
375
|
+
>>> ses = GitSession(_git, ls_tree= lambda : _git.subcmd_unchecked.popen(["ls-tree", "HEAD"], text=False),
|
|
376
|
+
... cat_file= lambda : _git.subcmd_unchecked.popen(["cat-file", "--batch"], text=False))
|
|
349
377
|
>>> with ses: # start the session by ctx mgr
|
|
350
378
|
... pass # any communication can be done by Popen semantics.
|
|
351
379
|
|
|
352
380
|
Start the session in one go:
|
|
353
381
|
|
|
354
|
-
>>> with _git.
|
|
382
|
+
>>> with GitSession(_git, cat_file= lambda : _git.subcmd_unchecked.popen(["cat-file", "--batch"], text=False)) as ses: # obtain, start and ctx manage the session.
|
|
355
383
|
... pass # any communication can be done by Popen semantics.
|
|
356
384
|
|
|
385
|
+
|
|
386
|
+
Note: All processes are assumed:
|
|
387
|
+
|
|
388
|
+
- in bytes mode.
|
|
389
|
+
- pipe their stdin and stdout.
|
|
390
|
+
|
|
357
391
|
:param git: ``gitbolt.subprocess.GitCommand`` instance.
|
|
358
392
|
:param commands: commands in kwargs fashion.
|
|
359
393
|
"""
|
|
360
394
|
self._git = git
|
|
361
395
|
self.unstarted_commands: dict[str, Callable[[], Popen[bytes]]] = commands
|
|
362
396
|
self.started_commands: dict[str, Popen[bytes]] = dict()
|
|
363
|
-
self.
|
|
397
|
+
self._commands: SimpleNamespace | None = None
|
|
398
|
+
self.__started = False
|
|
399
|
+
self.__done = False
|
|
400
|
+
self.__depth = 0
|
|
364
401
|
|
|
402
|
+
@override
|
|
365
403
|
def __enter__(self) -> Self:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
404
|
+
if self.depth == 0:
|
|
405
|
+
processes_started_keys: list[str] = []
|
|
406
|
+
for unstarted_command_key, unstarted_command in self.unstarted_commands.items():
|
|
407
|
+
try:
|
|
408
|
+
self.started_commands[unstarted_command_key] = unstarted_command().__enter__()
|
|
409
|
+
except Exception:
|
|
410
|
+
for k in reversed(processes_started_keys):
|
|
411
|
+
self.started_commands[k].__exit__(*sys.exc_info())
|
|
412
|
+
raise
|
|
413
|
+
processes_started_keys.append(unstarted_command_key)
|
|
414
|
+
self._commands = SimpleNamespace(**self.started_commands)
|
|
415
|
+
for pk in processes_started_keys:
|
|
416
|
+
del self.unstarted_commands[pk]
|
|
417
|
+
self.__started = True
|
|
418
|
+
self.__depth += 1
|
|
419
|
+
return self
|
|
373
420
|
|
|
421
|
+
@override
|
|
374
422
|
@property
|
|
375
423
|
def git(self) -> GitCommand:
|
|
376
424
|
return self._git
|
|
377
425
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
process_done_keys
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
426
|
+
@override
|
|
427
|
+
def __exit__(self, exc_type, exc_value, traceback, /) -> Literal[False]:
|
|
428
|
+
self.__depth -= 1
|
|
429
|
+
if self.__depth == 0:
|
|
430
|
+
process_done_keys: list[str] = []
|
|
431
|
+
for started_popen_key, started_popen in self.started_commands.items():
|
|
432
|
+
started_popen.__exit__(exc_type, exc_value, traceback)
|
|
433
|
+
process_done_keys.append(started_popen_key)
|
|
434
|
+
self._commands = None
|
|
435
|
+
for pk in process_done_keys:
|
|
436
|
+
del self.started_commands[pk]
|
|
437
|
+
self.__done = True
|
|
386
438
|
return False
|
|
387
439
|
|
|
440
|
+
@property
|
|
441
|
+
def started(self) -> bool:
|
|
442
|
+
"""
|
|
443
|
+
:returns: Whether the session has started.
|
|
444
|
+
"""
|
|
445
|
+
return self.__started
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def done(self) -> bool:
|
|
449
|
+
"""
|
|
450
|
+
:returns: Whether the session has completed and thus exited/closed.
|
|
451
|
+
"""
|
|
452
|
+
return self.__done
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def active(self) -> bool:
|
|
456
|
+
"""
|
|
457
|
+
:returns: Whether the session is currently actively running.
|
|
458
|
+
"""
|
|
459
|
+
return self.started and not self.done
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def commands(self) -> SimpleNamespace:
|
|
463
|
+
"""
|
|
464
|
+
:returns: All the registered commands and these can be accessed by member notations.
|
|
465
|
+
:raises RuntimeError: if commands are queried when the session is inactive.
|
|
466
|
+
"""
|
|
467
|
+
if self._commands is None or not self.active:
|
|
468
|
+
raise RuntimeError("GitSession not active")
|
|
469
|
+
return self._commands
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def depth(self) -> int:
|
|
473
|
+
"""
|
|
474
|
+
:returns: The current session depth. Multilevel context managers increment the depth each y one.
|
|
475
|
+
"""
|
|
476
|
+
return self.__depth
|
|
477
|
+
|
|
388
478
|
|
|
389
479
|
class GitSubcmdCommand(GitSubCommand, HasGitUnderneath["GitCommand"], Protocol):
|
|
390
480
|
"""
|
|
@@ -696,30 +786,30 @@ class UncheckedSubcmd(GitSubcmdCommand, RootDirOp, Protocol):
|
|
|
696
786
|
self,
|
|
697
787
|
subcommand_args: list[str],
|
|
698
788
|
*popen_args: Any,
|
|
699
|
-
text: Literal[
|
|
789
|
+
text: Literal[False] = False,
|
|
700
790
|
**popen_kwargs: Any,
|
|
701
|
-
) -> Popen[
|
|
791
|
+
) -> Popen[bytes]: ...
|
|
702
792
|
|
|
703
793
|
@overload
|
|
704
794
|
def popen(
|
|
705
795
|
self,
|
|
706
796
|
subcommand_args: list[str],
|
|
707
797
|
*popen_args: Any,
|
|
708
|
-
text: Literal[
|
|
798
|
+
text: Literal[True] = True,
|
|
709
799
|
**popen_kwargs: Any,
|
|
710
|
-
) -> Popen[
|
|
800
|
+
) -> Popen[str]: ...
|
|
711
801
|
|
|
712
802
|
def popen(
|
|
713
803
|
self,
|
|
714
804
|
subcommand_args: list[str],
|
|
715
805
|
*popen_args: Any,
|
|
716
|
-
text: Literal[
|
|
806
|
+
text: Literal[False, True] = False,
|
|
717
807
|
**popen_kwargs: Any,
|
|
718
808
|
) -> Popen[str] | Popen[bytes]:
|
|
719
809
|
"""
|
|
720
810
|
Open unchecked git subcommand communicable process, using ``subprocess.Popen``.
|
|
721
811
|
|
|
722
|
-
All the arguments are congruent to ``subprocess.Popen`` and mostly
|
|
812
|
+
All the arguments are congruent to ``subprocess.Popen`` and mostly pass as-is.
|
|
723
813
|
|
|
724
814
|
:param subcommand_args: the full subcommand argument list.
|
|
725
815
|
:param popen_args: additional subprocess positionals.
|
|
@@ -734,7 +824,6 @@ class UncheckedSubcmd(GitSubcmdCommand, RootDirOp, Protocol):
|
|
|
734
824
|
stdin = popen_kwargs.pop("stdin", PIPE)
|
|
735
825
|
stdout = popen_kwargs.pop("stdout", PIPE)
|
|
736
826
|
stderr = popen_kwargs.pop("stderr", PIPE)
|
|
737
|
-
bufsize = popen_kwargs.pop("bufsize", 0)
|
|
738
827
|
# Popen the git command
|
|
739
828
|
result = self.git.runner.popen_git_command(
|
|
740
829
|
main_cmd_args,
|
|
@@ -746,7 +835,6 @@ class UncheckedSubcmd(GitSubcmdCommand, RootDirOp, Protocol):
|
|
|
746
835
|
stdin=stdin,
|
|
747
836
|
stdout=stdout,
|
|
748
837
|
stderr=stderr,
|
|
749
|
-
bufsize=bufsize,
|
|
750
838
|
**popen_kwargs,
|
|
751
839
|
)
|
|
752
840
|
return result
|
|
@@ -170,7 +170,6 @@ class UncheckedSubcmdImpl(UncheckedSubcmd, GitSubcmdCommandImpl):
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
class SimpleGitCommand(GitCommand, RootDirOp):
|
|
173
|
-
|
|
174
173
|
def __init__(
|
|
175
174
|
self,
|
|
176
175
|
git_root_dir: Path = Path.cwd(),
|
|
@@ -250,11 +249,17 @@ class SimpleGitCommand(GitCommand, RootDirOp):
|
|
|
250
249
|
|
|
251
250
|
def session(self, **commands: list[str] | Callable[[], Popen[bytes]]) -> GitSession:
|
|
252
251
|
cmds: dict[str, Callable[[], Popen[bytes]]] = {}
|
|
252
|
+
|
|
253
|
+
# curb the lambda-late-binding-trap
|
|
254
|
+
# resource: https://medium.com/skiller-whale/late-binding-variables-its-a-trap-c17af980164f
|
|
255
|
+
def lambda_for_subcmd_popen(_runnable_cmd: list[str]):
|
|
256
|
+
return lambda: self.subcmd_unchecked.popen(_runnable_cmd)
|
|
257
|
+
|
|
253
258
|
for cmd_name, runnable_cmd in commands.items():
|
|
254
259
|
if callable(runnable_cmd):
|
|
255
260
|
cmds[cmd_name] = runnable_cmd
|
|
256
261
|
else:
|
|
257
|
-
cmds[cmd_name] =
|
|
262
|
+
cmds[cmd_name] = lambda_for_subcmd_popen(runnable_cmd)
|
|
258
263
|
return GitSession(self, **cmds)
|
|
259
264
|
|
|
260
265
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Models useful for session utilities.
|
|
6
|
+
"""
|
|
7
|
+
import dataclasses
|
|
8
|
+
import datetime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclasses.dataclass
|
|
12
|
+
class GitRawActor:
|
|
13
|
+
name: bytes
|
|
14
|
+
email: bytes
|
|
15
|
+
time: datetime.datetime
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_commit_bytes(cls, commit_bytes: bytes, email_start_pattern: bytes = b" <",
|
|
19
|
+
email_end_pattern: bytes = b"> ") -> "GitRawActor":
|
|
20
|
+
"""
|
|
21
|
+
Obtain ``Actor`` object from the author/committer bytes.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
|
|
25
|
+
Simple timestamp without timezone:
|
|
26
|
+
|
|
27
|
+
>>> _ss_actor1 = GitRawActor.from_commit_bytes(b"Suhas <sss@ss.ss> 1780211249")
|
|
28
|
+
>>> assert _ss_actor1.name == b"Suhas"
|
|
29
|
+
>>> assert _ss_actor1.email == b"sss@ss.ss"
|
|
30
|
+
>>> assert _ss_actor1.time.timestamp() == 1780211249.0
|
|
31
|
+
|
|
32
|
+
Simple timestamp with timezone:
|
|
33
|
+
|
|
34
|
+
>>> _ss_actor2 = GitRawActor.from_commit_bytes(b"Suhas Srivastava <sss@vaastav.tech> 1780211249 +0530")
|
|
35
|
+
>>> assert _ss_actor2.name == b"Suhas Srivastava"
|
|
36
|
+
>>> assert _ss_actor2.email == b"sss@vaastav.tech"
|
|
37
|
+
>>> assert _ss_actor2.time.timestamp() == 1780211249.0
|
|
38
|
+
|
|
39
|
+
:param commit_bytes: author/committer information in bytes form.
|
|
40
|
+
:param email_start_pattern: email spearates name and timestamp. Thus is required as a separator.
|
|
41
|
+
Marks email beginning.
|
|
42
|
+
:param email_end_pattern: email spearates name and timestamp. Thus is required as a separator.
|
|
43
|
+
Marks email ending.
|
|
44
|
+
:returns: ``Actor`` object with all the parsed and set values.
|
|
45
|
+
"""
|
|
46
|
+
email_pattern_start_index = commit_bytes.find(email_start_pattern)
|
|
47
|
+
email_pattern_end_index = commit_bytes.rfind(email_end_pattern)
|
|
48
|
+
email_start_index = email_pattern_start_index+len(email_start_pattern)
|
|
49
|
+
email_end_index = email_pattern_end_index
|
|
50
|
+
name = commit_bytes[:email_pattern_start_index]
|
|
51
|
+
email = commit_bytes[email_start_index:email_end_index]
|
|
52
|
+
time_bytes = commit_bytes[email_end_index+len(email_end_pattern):]
|
|
53
|
+
time_main, time_zone = time_bytes.split() if b" " in time_bytes else (time_bytes, b"")
|
|
54
|
+
date_time_for_iso_str = datetime.datetime.fromtimestamp(int(time_main))
|
|
55
|
+
date_time = datetime.datetime.fromisoformat(f"{date_time_for_iso_str.date()}T{date_time_for_iso_str.time()}{time_zone.decode()}")
|
|
56
|
+
return GitRawActor(name, email, date_time)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass
|
|
60
|
+
class GitRawSignature:
|
|
61
|
+
signature: bytes
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass
|
|
65
|
+
class GPGGitRawSignature(GitRawSignature):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclasses.dataclass
|
|
70
|
+
class SSHGitRawSignature(GitRawSignature):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclasses.dataclass
|
|
75
|
+
class RawCommitBytesObj:
|
|
76
|
+
"""
|
|
77
|
+
Commit info all in bytes as read from ``git cat-file --batch``.
|
|
78
|
+
"""
|
|
79
|
+
commit_hash: bytes
|
|
80
|
+
tree_hash: bytes
|
|
81
|
+
parents: list[bytes]
|
|
82
|
+
author: GitRawActor
|
|
83
|
+
committer: GitRawActor
|
|
84
|
+
signature: GitRawSignature | None
|
|
85
|
+
message: bytes
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclasses.dataclass
|
|
89
|
+
class RawBytesValsOfCommit:
|
|
90
|
+
tree_val: bytes
|
|
91
|
+
commit_parents_vals: list[bytes]
|
|
92
|
+
author_val: bytes
|
|
93
|
+
committer_val: bytes
|
|
94
|
+
signature_val: bytes | None
|
|
95
|
+
commit_message: bytes
|