gitbolt 0.0.0.dev1__py3-none-any.whl

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.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ """
5
+ A simple and straight-forward git command subprocess runner implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import subprocess
11
+ from subprocess import CompletedProcess
12
+ from typing import overload, override, Any, Literal
13
+
14
+ from gitbolt.git_subprocess.constants import GIT_CMD
15
+ from gitbolt.git_subprocess.exceptions import GitCmdException
16
+ from gitbolt.git_subprocess.runner import GitCommandRunner
17
+
18
+
19
+ class SimpleGitCR(GitCommandRunner):
20
+ """
21
+ Simple git command runner that simply runs everything `as-is` in a subprocess.
22
+ """
23
+
24
+ @overload
25
+ @override
26
+ def run_git_command(
27
+ self,
28
+ main_cmd_args: list[str],
29
+ subcommand_args: list[str],
30
+ *subprocess_run_args: Any,
31
+ _input: str,
32
+ text: Literal[True],
33
+ **subprocess_run_kwargs: Any,
34
+ ) -> CompletedProcess[str]: ...
35
+
36
+ @overload
37
+ @override
38
+ def run_git_command(
39
+ self,
40
+ main_cmd_args: list[str],
41
+ subcommand_args: list[str],
42
+ *subprocess_run_args: Any,
43
+ _input: bytes,
44
+ text: Literal[False],
45
+ **subprocess_run_kwargs: Any,
46
+ ) -> CompletedProcess[bytes]: ...
47
+
48
+ @overload
49
+ @override
50
+ def run_git_command(
51
+ self,
52
+ main_cmd_args: list[str],
53
+ subcommand_args: list[str],
54
+ *subprocess_run_args: Any,
55
+ text: Literal[True],
56
+ **subprocess_run_kwargs: Any,
57
+ ) -> CompletedProcess[str]: ...
58
+
59
+ @overload
60
+ @override
61
+ def run_git_command(
62
+ self,
63
+ main_cmd_args: list[str],
64
+ subcommand_args: list[str],
65
+ *subprocess_run_args: Any,
66
+ text: Literal[False] = ...,
67
+ **subprocess_run_kwargs: Any,
68
+ ) -> CompletedProcess[bytes]: ...
69
+
70
+ @override
71
+ def run_git_command(
72
+ self,
73
+ main_cmd_args: list[str],
74
+ subcommand_args: list[str],
75
+ *subprocess_run_args: Any,
76
+ _input: str | bytes | None = None,
77
+ text: Literal[True, False] = False,
78
+ **subprocess_run_kwargs: Any,
79
+ ) -> CompletedProcess[str] | CompletedProcess[bytes]:
80
+ try:
81
+ return subprocess.run(
82
+ [GIT_CMD, *main_cmd_args, *subcommand_args],
83
+ *subprocess_run_args,
84
+ input=_input,
85
+ text=text,
86
+ **subprocess_run_kwargs,
87
+ )
88
+ except subprocess.CalledProcessError as e:
89
+ raise GitCmdException(called_process_error=e, exit_code=e.returncode) from e
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ """
5
+ Utility functions related to processors specific to git commands using subprocess.
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from vt.utils.commons.commons.core_py import is_unset, not_none_not_unset, Unset
13
+
14
+
15
+ def git_main_cmd_repeating_flag_args(
16
+ val: Sequence[Path] | Unset | None, cmd_flag: str
17
+ ) -> list[str]:
18
+ """
19
+ Returns a flattened list of repeating flags and values.
20
+
21
+ Used for flags like `-C` which may be repeated with multiple values.
22
+
23
+ Args:
24
+ val: A list of values to apply with the flag.
25
+ cmd_flag: The flag to apply to each value.
26
+
27
+ Returns:
28
+ A flattened list like ['-C', 'repo1', '-C', 'repo2']
29
+
30
+ Examples:
31
+ >>> git_main_cmd_repeating_flag_args([Path("repo1"), Path("repo2")], "-C")
32
+ ['-C', 'repo1', '-C', 'repo2']
33
+
34
+ >>> git_main_cmd_repeating_flag_args([], "-C")
35
+ []
36
+
37
+ >>> from vt.utils.commons.commons.core_py import UNSET
38
+ >>> git_main_cmd_repeating_flag_args([Path("repo1"), UNSET, Path("repo2")], "-C")
39
+ ['-C', 'repo1', '-C', 'repo2']
40
+
41
+ >>> git_main_cmd_repeating_flag_args(None, "-C")
42
+ []
43
+
44
+ >>> git_main_cmd_repeating_flag_args(UNSET, "-C")
45
+ []
46
+ """
47
+ if not_none_not_unset(val):
48
+ return [
49
+ item
50
+ for entry in val
51
+ if not is_unset(entry)
52
+ for item in [cmd_flag, str(entry)]
53
+ ]
54
+ return []
55
+
56
+
57
+ def git_main_cmd_dict_flag_args(
58
+ val: dict[str, str | bool | None | Unset] | None | Unset, cmd_flag: str
59
+ ) -> list[str]:
60
+ """
61
+ Converts a dictionary into flag pairs used by commands like `-c key=value`.
62
+
63
+ - `True` or `None` => `-c key`
64
+ - `False` => `-c key=`
65
+ - `UNSET` keys are skipped
66
+
67
+ Args:
68
+ val: Dictionary of key-value pairs.
69
+ cmd_flag: The flag to apply.
70
+
71
+ Returns:
72
+ A list of CLI arguments.
73
+
74
+ Examples:
75
+ >>> git_main_cmd_dict_flag_args({"foo.bar": "baz"}, "-c")
76
+ ['-c', 'foo.bar=baz']
77
+
78
+ >>> git_main_cmd_dict_flag_args({"foo.bar": ""}, "-c")
79
+ ['-c', 'foo.bar=']
80
+
81
+ >>> git_main_cmd_dict_flag_args({"foo.bar": True}, "-c")
82
+ ['-c', 'foo.bar']
83
+
84
+ >>> git_main_cmd_dict_flag_args({"foo.bar": False}, "-c")
85
+ ['-c', 'foo.bar=']
86
+
87
+ >>> git_main_cmd_dict_flag_args({"foo.bar": None}, "-c")
88
+ ['-c', 'foo.bar']
89
+
90
+ >>> from vt.utils.commons.commons.core_py import UNSET
91
+ >>> git_main_cmd_dict_flag_args({"foo.bar": UNSET}, "-c")
92
+ []
93
+
94
+ >>> git_main_cmd_dict_flag_args({
95
+ ... "a.b": "x", "c.d": "", "e.f": True, "g.h": False, "i.j": None
96
+ ... }, "-c")
97
+ ['-c', 'a.b=x', '-c', 'c.d=', '-c', 'e.f', '-c', 'g.h=', '-c', 'i.j']
98
+
99
+ >>> git_main_cmd_dict_flag_args(None, "-c")
100
+ []
101
+
102
+ >>> git_main_cmd_dict_flag_args(UNSET, "-c")
103
+ []
104
+
105
+ >>> git_main_cmd_dict_flag_args({}, "-c")
106
+ []
107
+ """
108
+ if not_none_not_unset(val):
109
+ args = []
110
+ for k, v in val.items():
111
+ if is_unset(v):
112
+ continue
113
+ if v is True or v is None:
114
+ args += [cmd_flag, k]
115
+ elif v is False:
116
+ args += [cmd_flag, f"{k}="]
117
+ else:
118
+ args += [cmd_flag, f"{k}={v}"]
119
+ return args
120
+ return []
121
+
122
+
123
+ def git_main_cmd_simple_flag_args(val: bool | Unset | None, cmd_flag: str) -> list[str]:
124
+ """
125
+ Returns a single flag if the value is set and truthy.
126
+
127
+ Args:
128
+ val: A boolean-like value.
129
+ cmd_flag: The flag to apply.
130
+
131
+ Returns:
132
+ A list with just the flag, or empty.
133
+
134
+ Examples:
135
+ >>> git_main_cmd_simple_flag_args(True, "--no-pager")
136
+ ['--no-pager']
137
+
138
+ >>> git_main_cmd_simple_flag_args(False, "--no-pager")
139
+ []
140
+
141
+ >>> git_main_cmd_simple_flag_args(None, "--no-pager")
142
+ []
143
+
144
+ >>> from vt.utils.commons.commons.core_py import UNSET
145
+ >>> git_main_cmd_simple_flag_args(UNSET, "--no-pager")
146
+ []
147
+ """
148
+ if not_none_not_unset(val):
149
+ return [cmd_flag] if val else []
150
+ else:
151
+ return []
152
+
153
+
154
+ def git_main_cmd_pair_flag_args(val: Any, cmd_flag: str) -> list[str]:
155
+ """
156
+ Returns a flag and value pair, or nothing if not set.
157
+
158
+ Args:
159
+ val: The value to attach to the flag.
160
+ cmd_flag: The flag name.
161
+
162
+ Returns:
163
+ A list like ['--exec-path', 'tmp'] or []
164
+
165
+ Examples:
166
+ >>> git_main_cmd_pair_flag_args("foo", "--namespace")
167
+ ['--namespace', 'foo']
168
+
169
+ >>> git_main_cmd_pair_flag_args(Path("repo"), "--namespace")
170
+ ['--namespace', 'repo']
171
+
172
+ >>> git_main_cmd_pair_flag_args(None, "--namespace")
173
+ []
174
+
175
+ >>> from vt.utils.commons.commons.core_py import UNSET
176
+ >>> git_main_cmd_pair_flag_args(UNSET, "--namespace")
177
+ []
178
+ """
179
+ return [cmd_flag, str(val)] if not_none_not_unset(val) else []
gitbolt/ls_tree.py ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ """
5
+ Helper interfaces specific to ``git ls-tree`` subcommand.
6
+ """
7
+
8
+ from abc import abstractmethod
9
+ from typing import Protocol, Unpack, override, Literal
10
+
11
+ from vt.utils.errors.error_specs import ERR_INVALID_USAGE
12
+ from vt.utils.errors.error_specs.utils import require_type, require_iterable
13
+
14
+ from gitbolt.exceptions import GitExitingException
15
+ from gitbolt.models import GitLsTreeOpts
16
+
17
+
18
+ class LsTreeArgsValidator(Protocol):
19
+ """
20
+ The argument validator for ``git ls-tree`` subcommand.
21
+ """
22
+
23
+ @abstractmethod
24
+ def validate(self, tree_ish: str, **ls_tree_opts: Unpack[GitLsTreeOpts]) -> None:
25
+ """
26
+ Validate arguments passed to the ls-tree() method.
27
+
28
+ :param tree_ish: A tree-ish identifier (commit SHA, branch name, etc.).
29
+ :param ls_tree_opts: Keyword arguments mapping to supported options for ``git ls-tree``.
30
+ """
31
+ ...
32
+
33
+
34
+ class UtilLsTreeArgsValidator(LsTreeArgsValidator):
35
+ """
36
+ Independent utility function sort of interface to perform ``ls_tree()`` arguments validation.
37
+ """
38
+
39
+ @override
40
+ def validate(self, tree_ish: str, **ls_tree_opts: Unpack[GitLsTreeOpts]) -> None:
41
+ """
42
+ Validate the inputs provided to the ``git ls-tree`` command.
43
+
44
+ This utility ensures type safety and logical correctness of arguments
45
+ passed to the ``git ls-tree`` subcommand.
46
+
47
+ This includes:
48
+ * Enforcing that ``tree_ish`` is a string.
49
+ * Checking that each supported boolean flag (like ``d``, ``r``, ``z``, etc.)
50
+ is a valid boolean if provided.
51
+ * Validating that ``abbrev`` is an integer between 0 and 40 (inclusive).
52
+ * Ensuring ``format_`` is a string if specified.
53
+ * Checking that ``path`` is a list of strings if specified.
54
+
55
+ All validations will raise a ``GitExitingException`` with a specific
56
+ exit code depending on the nature of the failure:
57
+
58
+ * ``TypeError`` leads to ``ERR_DATA_FORMAT_ERR``.
59
+ * ``ValueError`` leads to ``ERR_INVALID_USAGE``.
60
+
61
+ See: `git ls-tree documentation <https://git-scm.com/docs/git-ls-tree>`_.
62
+
63
+ :param tree_ish: A valid Git tree-ish identifier (e.g., branch name, commit hash).
64
+ :param ls_tree_opts: Keyword arguments that map to valid ls-tree options.
65
+ :raises GitExitingException: When validation fails.
66
+
67
+ Examples::
68
+
69
+ >>> UtilLsTreeArgsValidator().validate("HEAD", d=True, abbrev=10)
70
+ >>> UtilLsTreeArgsValidator().validate("abc123", format_="%(objectname)", path=["src/", "README.md"])
71
+ >>> UtilLsTreeArgsValidator().validate("main", name_only=False, z=True)
72
+
73
+
74
+ Invalid Examples (will raise GitExitingException), printing these just for doctesting::
75
+
76
+ >>> UtilLsTreeArgsValidator().validate(42) # type: ignore[arg-type] # tree_ish expects str and int is provided
77
+ Traceback (most recent call last):
78
+ gitbolt.exceptions.GitExitingException: TypeError: 'tree_ish' must be a string
79
+
80
+ >>> UtilLsTreeArgsValidator().validate("HEAD", abbrev="abc") # type: ignore[arg-type] # abbrev expects int and str is provided
81
+ Traceback (most recent call last):
82
+ gitbolt.exceptions.GitExitingException: TypeError: 'abbrev' must be an int
83
+
84
+ >>> UtilLsTreeArgsValidator().validate("HEAD", abbrev=True) # type: ignore[arg-type] # abbrev expects int and bool is provided
85
+ Traceback (most recent call last):
86
+ gitbolt.exceptions.GitExitingException: TypeError: 'abbrev' must be an int
87
+
88
+ >>> UtilLsTreeArgsValidator().validate("HEAD", abbrev=100)
89
+ Traceback (most recent call last):
90
+ gitbolt.exceptions.GitExitingException: ValueError: abbrev must be between 0 and 40.
91
+
92
+ >>> UtilLsTreeArgsValidator().validate("HEAD",
93
+ ... path="src/") # type: ignore[arg-type] as path expects list[str] and str is provided.
94
+ Traceback (most recent call last):
95
+ gitbolt.exceptions.GitExitingException: TypeError: 'path' must be a non-str iterable
96
+
97
+ >>> UtilLsTreeArgsValidator().validate("HEAD",
98
+ ... path=1) # type: ignore[arg-type] as path expects list[str] and str is provided.
99
+ Traceback (most recent call last):
100
+ gitbolt.exceptions.GitExitingException: TypeError: 'path' must be a non-str iterable
101
+
102
+ >>> UtilLsTreeArgsValidator().validate("HEAD",
103
+ ... z="yes") # type: ignore[arg-type] as z expects bool and str is provided.
104
+ Traceback (most recent call last):
105
+ gitbolt.exceptions.GitExitingException: TypeError: 'z' must be a boolean
106
+ """
107
+ require_type(tree_ish, "tree_ish", str, GitExitingException)
108
+
109
+ bool_keys: list[
110
+ Literal[
111
+ "d",
112
+ "r",
113
+ "t",
114
+ "long",
115
+ "z",
116
+ "name_only",
117
+ "object_only",
118
+ "full_name",
119
+ "full_tree",
120
+ "name_status",
121
+ ]
122
+ ] = [
123
+ "d",
124
+ "r",
125
+ "t",
126
+ "long",
127
+ "z",
128
+ "name_only",
129
+ "object_only",
130
+ "full_name",
131
+ "full_tree",
132
+ "name_status",
133
+ ]
134
+
135
+ for key in bool_keys:
136
+ if key in ls_tree_opts:
137
+ the_key = ls_tree_opts[key]
138
+ require_type(the_key, key, bool, GitExitingException)
139
+
140
+ if "abbrev" in ls_tree_opts:
141
+ abbrev = ls_tree_opts["abbrev"]
142
+ require_type(abbrev, "abbrev", int, GitExitingException)
143
+ if not (0 <= abbrev <= 40):
144
+ errmsg = "abbrev must be between 0 and 40."
145
+ raise GitExitingException(
146
+ errmsg, exit_code=ERR_INVALID_USAGE
147
+ ) from ValueError(errmsg)
148
+
149
+ if "format_" in ls_tree_opts:
150
+ format_ = ls_tree_opts["format_"]
151
+ require_type(format_, "format_", str, GitExitingException)
152
+
153
+ if "path" in ls_tree_opts:
154
+ path = ls_tree_opts["path"]
155
+ require_iterable(path, "path", str, list, GitExitingException)