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.
- gitbolt/__init__.py +18 -0
- gitbolt/_internal_init.py +18 -0
- gitbolt/add.py +652 -0
- gitbolt/base.py +333 -0
- gitbolt/exceptions.py +46 -0
- gitbolt/git_subprocess/__init__.py +14 -0
- gitbolt/git_subprocess/_internal_init.py +9 -0
- gitbolt/git_subprocess/add.py +484 -0
- gitbolt/git_subprocess/base.py +436 -0
- gitbolt/git_subprocess/constants.py +13 -0
- gitbolt/git_subprocess/exceptions.py +110 -0
- gitbolt/git_subprocess/impl/__init__.py +6 -0
- gitbolt/git_subprocess/impl/simple.py +185 -0
- gitbolt/git_subprocess/ls_tree.py +384 -0
- gitbolt/git_subprocess/runner/__init__.py +8 -0
- gitbolt/git_subprocess/runner/base.py +64 -0
- gitbolt/git_subprocess/runner/simple_impl.py +89 -0
- gitbolt/git_subprocess/utils.py +179 -0
- gitbolt/ls_tree.py +155 -0
- gitbolt/models.py +686 -0
- gitbolt/py.typed +0 -0
- gitbolt/utils.py +179 -0
- gitbolt-0.0.0.dev1.dist-info/METADATA +308 -0
- gitbolt-0.0.0.dev1.dist-info/RECORD +27 -0
- gitbolt-0.0.0.dev1.dist-info/WHEEL +5 -0
- gitbolt-0.0.0.dev1.dist-info/licenses/LICENSE +201 -0
- gitbolt-0.0.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Git command interfaces with default implementation using subprocess calls.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import abstractmethod, ABC
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import override, Protocol, Unpack, Self, overload, Literal
|
|
13
|
+
|
|
14
|
+
from vt.utils.commons.commons.core_py import is_unset, not_none_not_unset
|
|
15
|
+
|
|
16
|
+
from gitbolt import Git, Version, LsTree, GitSubCommand, HasGitUnderneath, Add
|
|
17
|
+
from gitbolt.git_subprocess.add import AddCLIArgsBuilder, IndividuallyOverridableACAB
|
|
18
|
+
from gitbolt.git_subprocess.ls_tree import (
|
|
19
|
+
LsTreeCLIArgsBuilder,
|
|
20
|
+
IndividuallyOverridableLTCAB,
|
|
21
|
+
)
|
|
22
|
+
from gitbolt.git_subprocess.runner import GitCommandRunner
|
|
23
|
+
from gitbolt.models import GitOpts, GitLsTreeOpts, GitAddOpts, GitEnvVars
|
|
24
|
+
from gitbolt.utils import merge_git_opts, merge_git_envs
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GitCommand(Git, ABC):
|
|
28
|
+
"""
|
|
29
|
+
Runs git as a command.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, runner: GitCommandRunner):
|
|
33
|
+
"""
|
|
34
|
+
:param runner: a ``GitCommandRunner`` which eventually runs the cli command in a subprocess.
|
|
35
|
+
"""
|
|
36
|
+
self.runner: GitCommandRunner = runner
|
|
37
|
+
self._main_cmd_opts: GitOpts = {}
|
|
38
|
+
self._env_vars: GitEnvVars = {}
|
|
39
|
+
|
|
40
|
+
# region build_main_cmd_args
|
|
41
|
+
def build_main_cmd_args(self) -> list[str]:
|
|
42
|
+
"""
|
|
43
|
+
Terminal operation to build and return CLI args for git main cli command.
|
|
44
|
+
|
|
45
|
+
For example, ``--no-pager --no-advice`` is the git main command in ``git --no-pager --no-advice log master -1``.
|
|
46
|
+
|
|
47
|
+
:return: CLI args for git main cli command.
|
|
48
|
+
"""
|
|
49
|
+
return (
|
|
50
|
+
self._main_cmd_cap_c_args()
|
|
51
|
+
+ self._main_cmd_small_c_args()
|
|
52
|
+
+ self._main_cmd_config_env_args()
|
|
53
|
+
+ self._main_cmd_exec_path_args()
|
|
54
|
+
+ self._main_cmd_paginate_args()
|
|
55
|
+
+ self._main_cmd_no_pager_args()
|
|
56
|
+
+ self._main_cmd_git_dir_args()
|
|
57
|
+
+ self._main_cmd_work_tree_args()
|
|
58
|
+
+ self._main_cmd_namespace_args()
|
|
59
|
+
+ self._main_cmd_bare_args()
|
|
60
|
+
+ self._main_cmd_no_replace_objects_args()
|
|
61
|
+
+ self._main_cmd_no_lazy_fetch_args()
|
|
62
|
+
+ self._main_cmd_no_optional_locks_args()
|
|
63
|
+
+ self._main_cmd_no_advice_args()
|
|
64
|
+
+ self._main_cmd_literal_pathspecs_args()
|
|
65
|
+
+ self._main_cmd_glob_pathspecs_args()
|
|
66
|
+
+ self._main_cmd_noglob_pathspecs_args()
|
|
67
|
+
+ self._main_cmd_icase_pathspecs_args()
|
|
68
|
+
+ self._main_cmd_list_cmds_args()
|
|
69
|
+
+ self._main_cmd_attr_source_args()
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
def git_opts_override(self, **overrides: Unpack[GitOpts]) -> Self:
|
|
74
|
+
_git_cmd = self.clone()
|
|
75
|
+
_main_cmd_opts = merge_git_opts(overrides, self._main_cmd_opts)
|
|
76
|
+
_git_cmd._main_cmd_opts = _main_cmd_opts
|
|
77
|
+
return _git_cmd
|
|
78
|
+
|
|
79
|
+
def _main_cmd_cap_c_args(self) -> list[str]:
|
|
80
|
+
val = self._main_cmd_opts.get("C")
|
|
81
|
+
if not_none_not_unset(val):
|
|
82
|
+
return [item for path in val for item in ["-C", str(path)]]
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
def _main_cmd_small_c_args(self) -> list[str]:
|
|
86
|
+
val = self._main_cmd_opts.get("c")
|
|
87
|
+
if not_none_not_unset(val):
|
|
88
|
+
args = []
|
|
89
|
+
for k, v in val.items():
|
|
90
|
+
if is_unset(v):
|
|
91
|
+
continue # explicitly skip unset keys
|
|
92
|
+
if v is True or v is None: # treat None as True
|
|
93
|
+
args += ["-c", k]
|
|
94
|
+
elif v is False:
|
|
95
|
+
args += ["-c", f"{k}="]
|
|
96
|
+
else:
|
|
97
|
+
args += ["-c", f"{k}={v}"]
|
|
98
|
+
return args
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
def _main_cmd_config_env_args(self) -> list[str]:
|
|
102
|
+
val = self._main_cmd_opts.get("config_env")
|
|
103
|
+
if not_none_not_unset(val):
|
|
104
|
+
return [
|
|
105
|
+
item for k, v in val.items() for item in ["--config-env", f"{k}={v}"]
|
|
106
|
+
]
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
def _main_cmd_exec_path_args(self) -> list[str]:
|
|
110
|
+
val = self._main_cmd_opts.get("exec_path")
|
|
111
|
+
if not_none_not_unset(val):
|
|
112
|
+
return ["--exec-path", str(val)]
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
def _main_cmd_paginate_args(self) -> list[str]:
|
|
116
|
+
val = self._main_cmd_opts.get("paginate")
|
|
117
|
+
if not_none_not_unset(val):
|
|
118
|
+
return ["--paginate"]
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
def _main_cmd_no_pager_args(self) -> list[str]:
|
|
122
|
+
val = self._main_cmd_opts.get("no_pager")
|
|
123
|
+
if not_none_not_unset(val):
|
|
124
|
+
return ["--no-pager"]
|
|
125
|
+
return []
|
|
126
|
+
|
|
127
|
+
def _main_cmd_git_dir_args(self) -> list[str]:
|
|
128
|
+
val = self._main_cmd_opts.get("git_dir")
|
|
129
|
+
if not_none_not_unset(val):
|
|
130
|
+
return ["--git-dir", str(val)]
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
def _main_cmd_work_tree_args(self) -> list[str]:
|
|
134
|
+
val = self._main_cmd_opts.get("work_tree")
|
|
135
|
+
if not_none_not_unset(val):
|
|
136
|
+
return ["--work-tree", str(val)]
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
def _main_cmd_namespace_args(self) -> list[str]:
|
|
140
|
+
val = self._main_cmd_opts.get("namespace")
|
|
141
|
+
if not_none_not_unset(val):
|
|
142
|
+
return ["--namespace", val]
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
|
+
def _main_cmd_bare_args(self) -> list[str]:
|
|
146
|
+
val = self._main_cmd_opts.get("bare")
|
|
147
|
+
if not_none_not_unset(val):
|
|
148
|
+
return ["--bare"]
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
def _main_cmd_no_replace_objects_args(self) -> list[str]:
|
|
152
|
+
val = self._main_cmd_opts.get("no_replace_objects")
|
|
153
|
+
if not_none_not_unset(val):
|
|
154
|
+
return ["--no-replace-objects"]
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
def _main_cmd_no_lazy_fetch_args(self) -> list[str]:
|
|
158
|
+
val = self._main_cmd_opts.get("no_lazy_fetch")
|
|
159
|
+
if not_none_not_unset(val):
|
|
160
|
+
return ["--no-lazy-fetch"]
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
def _main_cmd_no_optional_locks_args(self) -> list[str]:
|
|
164
|
+
val = self._main_cmd_opts.get("no_optional_locks")
|
|
165
|
+
if not_none_not_unset(val):
|
|
166
|
+
return ["--no-optional-locks"]
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
def _main_cmd_no_advice_args(self) -> list[str]:
|
|
170
|
+
val = self._main_cmd_opts.get("no_advice")
|
|
171
|
+
if not_none_not_unset(val):
|
|
172
|
+
return ["--no-advice"]
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
def _main_cmd_literal_pathspecs_args(self) -> list[str]:
|
|
176
|
+
val = self._main_cmd_opts.get("literal_pathspecs")
|
|
177
|
+
if not_none_not_unset(val):
|
|
178
|
+
return ["--literal-pathspecs"]
|
|
179
|
+
return []
|
|
180
|
+
|
|
181
|
+
def _main_cmd_glob_pathspecs_args(self) -> list[str]:
|
|
182
|
+
val = self._main_cmd_opts.get("glob_pathspecs")
|
|
183
|
+
if not_none_not_unset(val):
|
|
184
|
+
return ["--glob-pathspecs"]
|
|
185
|
+
return []
|
|
186
|
+
|
|
187
|
+
def _main_cmd_noglob_pathspecs_args(self) -> list[str]:
|
|
188
|
+
val = self._main_cmd_opts.get("noglob_pathspecs")
|
|
189
|
+
if not_none_not_unset(val):
|
|
190
|
+
return ["--noglob-pathspecs"]
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def _main_cmd_icase_pathspecs_args(self) -> list[str]:
|
|
194
|
+
val = self._main_cmd_opts.get("icase_pathspecs")
|
|
195
|
+
if not_none_not_unset(val):
|
|
196
|
+
return ["--icase-pathspecs"]
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
def _main_cmd_list_cmds_args(self) -> list[str]:
|
|
200
|
+
val = self._main_cmd_opts.get("list_cmds")
|
|
201
|
+
if not_none_not_unset(val):
|
|
202
|
+
return [item for cmd in val for item in ["--list-cmds", cmd]]
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
def _main_cmd_attr_source_args(self) -> list[str]:
|
|
206
|
+
val = self._main_cmd_opts.get("attr_source")
|
|
207
|
+
if not_none_not_unset(val):
|
|
208
|
+
return ["--attr-source", val]
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
# endregion
|
|
212
|
+
|
|
213
|
+
# region build_git_envs
|
|
214
|
+
def build_git_envs(self) -> dict[str, str]:
|
|
215
|
+
"""
|
|
216
|
+
Terminal operation to build and return effective Git environment variables
|
|
217
|
+
from the merged ``GitEnvVars`` object.
|
|
218
|
+
|
|
219
|
+
Skips values that are ``Unset`` or ``None``-like using ``not_none_not_unset()``.
|
|
220
|
+
Converts ``Path`` and ``datetime`` instances to ``str``.
|
|
221
|
+
|
|
222
|
+
:return: A cleaned and normalized GitEnvVars dict suitable for use in subprocesses.
|
|
223
|
+
"""
|
|
224
|
+
env: dict[str, str] = {}
|
|
225
|
+
for key, val in self._env_vars.items():
|
|
226
|
+
if not_none_not_unset(val):
|
|
227
|
+
env[key] = str(val)
|
|
228
|
+
return env
|
|
229
|
+
|
|
230
|
+
@override
|
|
231
|
+
def git_envs_override(self, **overrides: Unpack[GitEnvVars]) -> Self:
|
|
232
|
+
_git_cmd = self.clone()
|
|
233
|
+
_env_vars = merge_git_envs(overrides, self._env_vars)
|
|
234
|
+
_git_cmd._env_vars = _env_vars
|
|
235
|
+
return _git_cmd
|
|
236
|
+
|
|
237
|
+
# endregion
|
|
238
|
+
|
|
239
|
+
@override
|
|
240
|
+
@property
|
|
241
|
+
def html_path(self) -> Path:
|
|
242
|
+
html_path_str = "--html-path"
|
|
243
|
+
return self._get_path(html_path_str)
|
|
244
|
+
|
|
245
|
+
@override
|
|
246
|
+
@property
|
|
247
|
+
def info_path(self) -> Path:
|
|
248
|
+
info_path_str = "--info-path"
|
|
249
|
+
return self._get_path(info_path_str)
|
|
250
|
+
|
|
251
|
+
@override
|
|
252
|
+
@property
|
|
253
|
+
def man_path(self) -> Path:
|
|
254
|
+
man_path_str = "--man-path"
|
|
255
|
+
return self._get_path(man_path_str)
|
|
256
|
+
|
|
257
|
+
@override
|
|
258
|
+
@property
|
|
259
|
+
def exec_path(self) -> Path:
|
|
260
|
+
exec_path_str = "--exec-path"
|
|
261
|
+
return self._get_path(exec_path_str)
|
|
262
|
+
|
|
263
|
+
def _get_path(self, path_opt_str: str) -> Path:
|
|
264
|
+
main_opts = self.build_main_cmd_args()
|
|
265
|
+
main_opts.append(path_opt_str)
|
|
266
|
+
_path_str = self.runner.run_git_command(
|
|
267
|
+
main_opts, [], check=True, text=True, capture_output=True
|
|
268
|
+
).stdout.strip()
|
|
269
|
+
return Path(_path_str)
|
|
270
|
+
|
|
271
|
+
@override
|
|
272
|
+
@property
|
|
273
|
+
@abstractmethod
|
|
274
|
+
def version_subcmd(self) -> VersionCommand: ...
|
|
275
|
+
|
|
276
|
+
@override
|
|
277
|
+
@property
|
|
278
|
+
@abstractmethod
|
|
279
|
+
def ls_tree_subcmd(self) -> LsTreeCommand: ...
|
|
280
|
+
|
|
281
|
+
@override
|
|
282
|
+
@property
|
|
283
|
+
@abstractmethod
|
|
284
|
+
def add_subcmd(self) -> AddCommand: ...
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class GitSubcmdCommand(GitSubCommand, HasGitUnderneath["GitCommand"], Protocol):
|
|
288
|
+
"""
|
|
289
|
+
A ``GitSubCommand`` that holds a reference to ``git`` and provides ``git_opts_override`` by default.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
@override
|
|
293
|
+
def git_opts_override(self, **overrides: Unpack[GitOpts]) -> Self:
|
|
294
|
+
overridden_git = self.underlying_git.git_opts_override(**overrides)
|
|
295
|
+
self._set_underlying_git(overridden_git)
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
@override
|
|
299
|
+
def git_envs_override(self, **overrides: Unpack[GitEnvVars]) -> Self:
|
|
300
|
+
overridden_git = self.underlying_git.git_envs_override(**overrides)
|
|
301
|
+
self._set_underlying_git(overridden_git)
|
|
302
|
+
return self
|
|
303
|
+
|
|
304
|
+
@abstractmethod
|
|
305
|
+
def _set_underlying_git(self, git: "GitCommand") -> None:
|
|
306
|
+
"""
|
|
307
|
+
Protected. Designed to be overridden not called publicly.
|
|
308
|
+
|
|
309
|
+
Set the `_underlying_git` in the derived class.
|
|
310
|
+
|
|
311
|
+
:param git: git to override current class's `underlying_git` to.
|
|
312
|
+
"""
|
|
313
|
+
...
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class VersionCommand(Version, GitSubcmdCommand, Protocol):
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class LsTreeCommand(LsTree, GitSubcmdCommand, Protocol):
|
|
321
|
+
"""
|
|
322
|
+
A composable class for building arguments for the `git ls-tree` subcommand, which is run later in a subprocess.
|
|
323
|
+
|
|
324
|
+
Intended usage includes CLI tooling, scripting, or Git plumbing automation, especially in
|
|
325
|
+
contexts where it's useful to dynamically generate Git commands.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
@override
|
|
329
|
+
def ls_tree(self, tree_ish: str, **ls_tree_opts: Unpack[GitLsTreeOpts]) -> str:
|
|
330
|
+
self.args_validator.validate(tree_ish, **ls_tree_opts)
|
|
331
|
+
sub_cmd_args = self.cli_args_builder.build(tree_ish, **ls_tree_opts)
|
|
332
|
+
main_cmd_args = self.underlying_git.build_main_cmd_args()
|
|
333
|
+
|
|
334
|
+
# Run the git command
|
|
335
|
+
result = self.underlying_git.runner.run_git_command(
|
|
336
|
+
main_cmd_args,
|
|
337
|
+
sub_cmd_args,
|
|
338
|
+
check=True,
|
|
339
|
+
text=True,
|
|
340
|
+
capture_output=True,
|
|
341
|
+
cwd=self.root_dir,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return result.stdout.strip()
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def cli_args_builder(self) -> LsTreeCLIArgsBuilder:
|
|
348
|
+
"""
|
|
349
|
+
The builder assembles the subcommand CLI portion of the git command invocation, such as
|
|
350
|
+
in ``git --no-pager ls-tree -r HEAD``, where ``-r HEAD`` is the subcommand argument list.
|
|
351
|
+
|
|
352
|
+
:return: Builder the complete list of subcommand CLI arguments to be passed to ``git ls-tree`` subprocess.
|
|
353
|
+
"""
|
|
354
|
+
return IndividuallyOverridableLTCAB()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class AddCommand(Add, GitSubcmdCommand, Protocol):
|
|
358
|
+
# TODO: check why PyCharm says that add() signature is incompatible with base class but mypy says okay.
|
|
359
|
+
|
|
360
|
+
@override
|
|
361
|
+
@overload
|
|
362
|
+
def add(
|
|
363
|
+
self, pathspec: str, *pathspecs: str, **add_opts: Unpack[GitAddOpts]
|
|
364
|
+
) -> str: ...
|
|
365
|
+
|
|
366
|
+
@override
|
|
367
|
+
@overload
|
|
368
|
+
def add(
|
|
369
|
+
self,
|
|
370
|
+
*,
|
|
371
|
+
pathspec_from_file: Path,
|
|
372
|
+
pathspec_file_nul: bool = False,
|
|
373
|
+
**add_opts: Unpack[GitAddOpts],
|
|
374
|
+
) -> str: ...
|
|
375
|
+
|
|
376
|
+
@override
|
|
377
|
+
@overload
|
|
378
|
+
def add(
|
|
379
|
+
self,
|
|
380
|
+
*,
|
|
381
|
+
pathspec_from_file: Literal["-"],
|
|
382
|
+
pathspec_stdin: str,
|
|
383
|
+
pathspec_file_nul: bool = False,
|
|
384
|
+
**add_opts: Unpack[GitAddOpts],
|
|
385
|
+
) -> str: ...
|
|
386
|
+
|
|
387
|
+
@override
|
|
388
|
+
def add(
|
|
389
|
+
self,
|
|
390
|
+
pathspec: str | None = None,
|
|
391
|
+
*pathspecs: str,
|
|
392
|
+
pathspec_from_file: Path | Literal["-"] | None = None,
|
|
393
|
+
pathspec_stdin: str | None = None,
|
|
394
|
+
pathspec_file_nul: bool = False,
|
|
395
|
+
**add_opts: Unpack[GitAddOpts],
|
|
396
|
+
) -> str:
|
|
397
|
+
self.args_validator.validate(
|
|
398
|
+
pathspec,
|
|
399
|
+
*pathspecs,
|
|
400
|
+
pathspec_from_file=pathspec_from_file,
|
|
401
|
+
pathspec_stdin=pathspec_stdin,
|
|
402
|
+
pathspec_file_nul=pathspec_file_nul,
|
|
403
|
+
**add_opts,
|
|
404
|
+
)
|
|
405
|
+
sub_cmd_args = self.cli_args_builder.build(
|
|
406
|
+
pathspec,
|
|
407
|
+
*pathspecs,
|
|
408
|
+
pathspec_from_file=pathspec_from_file,
|
|
409
|
+
pathspec_file_nul=pathspec_file_nul,
|
|
410
|
+
**add_opts,
|
|
411
|
+
)
|
|
412
|
+
main_cmd_args = self.underlying_git.build_main_cmd_args()
|
|
413
|
+
|
|
414
|
+
# Run the git command
|
|
415
|
+
result = self.underlying_git.runner.run_git_command(
|
|
416
|
+
main_cmd_args,
|
|
417
|
+
sub_cmd_args,
|
|
418
|
+
_input=pathspec_stdin,
|
|
419
|
+
check=True,
|
|
420
|
+
text=True,
|
|
421
|
+
capture_output=True,
|
|
422
|
+
cwd=self.root_dir,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return result.stdout.strip()
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def cli_args_builder(self) -> AddCLIArgsBuilder:
|
|
429
|
+
"""
|
|
430
|
+
The builder assembles the subcommand CLI portion of the git command invocation, such as
|
|
431
|
+
in ``git --no-pager add --ignore-missing add-file.py``, where ``--ignore-missing add-file.py`` is the
|
|
432
|
+
subcommand argument list.
|
|
433
|
+
|
|
434
|
+
:return: Builder the complete list of subcommand CLI arguments to be passed to ``git add`` subprocess.
|
|
435
|
+
"""
|
|
436
|
+
return IndividuallyOverridableACAB()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
constants wrt of git commands using subprocess.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Final
|
|
9
|
+
|
|
10
|
+
GIT_CMD: Final[str] = "git"
|
|
11
|
+
VERSION_CMD: Final[str] = "version"
|
|
12
|
+
LS_TREE_CMD: Final[str] = "ls-tree"
|
|
13
|
+
ADD_CMD: Final[str] = "add"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Exceptions specific to git using subprocess.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from vt.utils.errors.error_specs.exceptions import VTCmdException
|
|
9
|
+
|
|
10
|
+
from gitbolt.exceptions import GitException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GitCmdException(GitException, VTCmdException):
|
|
14
|
+
"""
|
|
15
|
+
A ``GitException`` that is also a ``VTCmdException``.
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
|
|
19
|
+
>>> from subprocess import CalledProcessError
|
|
20
|
+
|
|
21
|
+
* raise without message:
|
|
22
|
+
|
|
23
|
+
>>> raise GitCmdException(called_process_error=CalledProcessError(1, ['git', 'status'])) # always use `from` clause.
|
|
24
|
+
Traceback (most recent call last):
|
|
25
|
+
gitbolt.git_subprocess.exceptions.GitCmdException: CalledProcessError: Command '['git', 'status']' returned non-zero exit status 1.
|
|
26
|
+
|
|
27
|
+
* raise with a message:
|
|
28
|
+
|
|
29
|
+
>>> raise GitCmdException('Git failed', called_process_error=CalledProcessError(1, ['git', 'push'])) # always use `from` clause.
|
|
30
|
+
Traceback (most recent call last):
|
|
31
|
+
gitbolt.git_subprocess.exceptions.GitCmdException: CalledProcessError: Git failed
|
|
32
|
+
|
|
33
|
+
* raise with overridden exit code:
|
|
34
|
+
|
|
35
|
+
>>> raise GitCmdException('Git push failed', called_process_error=CalledProcessError(1, ['git', 'push']), exit_code=42) # always use `from` clause.
|
|
36
|
+
Traceback (most recent call last):
|
|
37
|
+
gitbolt.git_subprocess.exceptions.GitCmdException: CalledProcessError: Git push failed
|
|
38
|
+
|
|
39
|
+
* raise without message, override with stderr inside CalledProcessError:
|
|
40
|
+
|
|
41
|
+
>>> err = CalledProcessError(128, ['git', 'fetch'], stderr='fatal: not a git repository')
|
|
42
|
+
>>> raise GitCmdException(called_process_error=err) # always use `from` clause.
|
|
43
|
+
Traceback (most recent call last):
|
|
44
|
+
gitbolt.git_subprocess.exceptions.GitCmdException: CalledProcessError: Command '['git', 'fetch']' returned non-zero exit status 128.
|
|
45
|
+
|
|
46
|
+
* raise exception using `from` clause (chaining):
|
|
47
|
+
|
|
48
|
+
>>> try:
|
|
49
|
+
... raise CalledProcessError(129, ['git', 'clone'], stderr='fatal: repo not found')
|
|
50
|
+
... except CalledProcessError as e:
|
|
51
|
+
... raise GitCmdException('Clone failed', called_process_error=e) from e
|
|
52
|
+
Traceback (most recent call last):
|
|
53
|
+
gitbolt.git_subprocess.exceptions.GitCmdException: CalledProcessError: Clone failed
|
|
54
|
+
|
|
55
|
+
* cause reflects original CalledProcessError when chained:
|
|
56
|
+
|
|
57
|
+
>>> try:
|
|
58
|
+
... raise CalledProcessError(2, ['git', 'commit'])
|
|
59
|
+
... except CalledProcessError as e:
|
|
60
|
+
... try:
|
|
61
|
+
... raise GitCmdException('Commit failed', called_process_error=e) from e
|
|
62
|
+
... except GitCmdException as g:
|
|
63
|
+
... isinstance(g.cause, CalledProcessError)
|
|
64
|
+
True
|
|
65
|
+
|
|
66
|
+
* cause falls back to `called_process_error` when not chained:
|
|
67
|
+
|
|
68
|
+
>>> e = CalledProcessError(3, ['git', 'diff'])
|
|
69
|
+
>>> g = GitCmdException('Diff fail', called_process_error=e)
|
|
70
|
+
>>> g.cause is g.called_process_error
|
|
71
|
+
True
|
|
72
|
+
|
|
73
|
+
* access exit code:
|
|
74
|
+
|
|
75
|
+
>>> e = CalledProcessError(100, ['git', 'log'])
|
|
76
|
+
>>> ex = GitCmdException('Failure', called_process_error=e)
|
|
77
|
+
>>> ex.exit_code
|
|
78
|
+
100
|
|
79
|
+
|
|
80
|
+
* override exit code manually:
|
|
81
|
+
|
|
82
|
+
>>> GitCmdException('Overridden', called_process_error=e, exit_code=77).exit_code
|
|
83
|
+
77
|
|
84
|
+
|
|
85
|
+
* access structured information:
|
|
86
|
+
|
|
87
|
+
>>> g = GitCmdException('Git structured', called_process_error=e)
|
|
88
|
+
>>> info = g.to_dict()
|
|
89
|
+
>>> info['type'], 'Git structured' in info['message']
|
|
90
|
+
('GitCmdException', True)
|
|
91
|
+
|
|
92
|
+
* raise exception with extra metadata:
|
|
93
|
+
|
|
94
|
+
>>> e = CalledProcessError(4, ['git', 'tag'])
|
|
95
|
+
>>> x = GitCmdException('Failed tagging', called_process_error=e, context='tagging-op')
|
|
96
|
+
>>> x.kwargs['context']
|
|
97
|
+
'tagging-op'
|
|
98
|
+
|
|
99
|
+
* demonstrate subclass relationship:
|
|
100
|
+
|
|
101
|
+
>>> isinstance(GitCmdException('x', called_process_error=e), VTCmdException)
|
|
102
|
+
True
|
|
103
|
+
|
|
104
|
+
>>> isinstance(GitCmdException('x', called_process_error=e), GitException)
|
|
105
|
+
True
|
|
106
|
+
|
|
107
|
+
... rest examples mimic ``VTCmdException``.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
pass
|