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/add.py ADDED
@@ -0,0 +1,652 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ """
5
+ Helper interfaces specific to ``git add`` subcommand.
6
+ """
7
+
8
+ from abc import abstractmethod
9
+ from pathlib import Path
10
+ from typing import Protocol, Unpack, override, Literal
11
+
12
+ from gitbolt._internal_init import errmsg_creator
13
+ from gitbolt.exceptions import GitExitingException
14
+ from gitbolt.models import GitAddOpts
15
+ from vt.utils.commons.commons.core_py import has_atleast_one_arg, ensure_atleast_one_arg
16
+ from vt.utils.errors.error_specs import ERR_DATA_FORMAT_ERR, ERR_INVALID_USAGE
17
+ from vt.utils.errors.error_specs.utils import require_type
18
+
19
+
20
+ class AddArgsValidator(Protocol):
21
+ """
22
+ The argument validator for ``git add`` subcommand.
23
+ """
24
+
25
+ @abstractmethod
26
+ def validate(
27
+ self,
28
+ pathspec: str | None = None,
29
+ *pathspecs: str,
30
+ pathspec_from_file: Path | Literal["-"] | None = None,
31
+ pathspec_stdin: str | None = None,
32
+ pathspec_file_nul: bool = False,
33
+ **add_opts: Unpack[GitAddOpts],
34
+ ) -> None:
35
+ """
36
+ Validate the inputs provided to the ``git add`` command.
37
+
38
+ This function ensures logical and type-safe usage of arguments that map to the
39
+ ``git add`` subcommand, enforcing mutual exclusivity, argument completeness,
40
+ and correct typing.
41
+
42
+ Specifically, it validates:
43
+
44
+ * That at least one of ``pathspec`` or ``pathspecs`` is provided unless using ``pathspec_from_file``.
45
+ * That ``pathspec_from_file`` and ``pathspec_stdin`` are not used alongside direct pathspecs.
46
+ * That if ``pathspec_from_file == '-'``, then ``pathspec_stdin`` must be provided.
47
+ * That ``pathspec_stdin`` is not provided unless ``pathspec_from_file == '-'``.
48
+ * That ``pathspec_file_nul`` is a boolean.
49
+ * That each field in ``add_opts`` is correctly typed according to the GitAddOpts spec.
50
+
51
+ All validations will raise a ``GitExitingException`` with a specific exit code depending
52
+ on the nature of the failure:
53
+
54
+ * ``TypeError`` leads to ``ERR_DATA_FORMAT_ERR``.
55
+ * ``ValueError`` leads to ``ERR_INVALID_USAGE``.
56
+
57
+ See: `git add documentation <https://git-scm.com/docs/git-add>`_.
58
+
59
+ :param pathspec: A direct file or directory to stage. Cannot be used with ``pathspec_from_file``.
60
+ :type pathspec: str | None
61
+ :param pathspecs: Additional pathspecs to stage.
62
+ :type pathspecs: str
63
+ :param pathspec_from_file: A file to read pathspecs from. Use ``'-'`` to read from stdin.
64
+ :type pathspec_from_file: Path | Literal['-'] | None
65
+ :param pathspec_stdin: Required if ``pathspec_from_file == '-'``. Denotes stdin content.
66
+ :type pathspec_stdin: str | None
67
+ :param pathspec_file_nul: Whether input lines in ``pathspec_from_file`` are NUL-separated.
68
+ :type pathspec_file_nul: bool
69
+ :param add_opts: Options accepted by the ``git add`` subcommand.
70
+ :type add_opts: Unpack[GitAddOpts]
71
+ :raises GitExitingException: When validation fails.
72
+ """
73
+ ...
74
+
75
+
76
+ class UtilAddArgsValidator(AddArgsValidator):
77
+ """
78
+ Independent utility function sort of interface to perform add() arguments validation.
79
+ """
80
+
81
+ @override
82
+ def validate(
83
+ self,
84
+ pathspec: str | None = None,
85
+ *pathspecs: str,
86
+ pathspec_from_file: Path | Literal["-"] | None = None,
87
+ pathspec_stdin: str | None = None,
88
+ pathspec_file_nul: bool = False,
89
+ **add_opts: Unpack[GitAddOpts],
90
+ ) -> None:
91
+ """
92
+ Examples::
93
+
94
+ >>> UtilAddArgsValidator().validate("README.md", verbose=True)
95
+ >>> UtilAddArgsValidator().validate(pathspec_from_file='-', pathspec_stdin="README.md")
96
+ >>> UtilAddArgsValidator().validate(pathspec_from_file=Path("list.txt"))
97
+ >>> UtilAddArgsValidator().validate("foo/bar.txt", dry_run=True)
98
+ >>> UtilAddArgsValidator().validate(pathspec_from_file=Path("paths.txt"), pathspec_file_nul=True)
99
+ >>> UtilAddArgsValidator().validate("a.txt", "b.txt", force=True, chmod="+x")
100
+
101
+ Invalid Examples::
102
+
103
+ >>> UtilAddArgsValidator().validate("file.txt", pathspec_file_nul=True)
104
+ Traceback (most recent call last):
105
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_file_nul and pathspec are not allowed together
106
+
107
+ >>> UtilAddArgsValidator().validate(pathspec_from_file='-', pathspec_stdin=None)
108
+ Traceback (most recent call last):
109
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin must be provided when pathspec_form_file is -
110
+
111
+ >>> UtilAddArgsValidator().validate(pathspec_from_file=Path("x.txt"), pathspec_stdin="foo")
112
+ Traceback (most recent call last):
113
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin is not allowed unless pathspec_from_file is '-'.
114
+
115
+ >>> UtilAddArgsValidator().validate(123) # type: ignore[arg-type]
116
+ Traceback (most recent call last):
117
+ gitbolt.exceptions.GitExitingException: TypeError: pathspec must be a string.
118
+
119
+ >>> UtilAddArgsValidator().validate("README.md", pathspec_from_file=Path("foo"))
120
+ Traceback (most recent call last):
121
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec and pathspec_from_file are not allowed together
122
+
123
+ >>> UtilAddArgsValidator().validate("README.md", chmod="bad") # type: ignore[arg-type]
124
+ Traceback (most recent call last):
125
+ gitbolt.exceptions.GitExitingException: ValueError: Unexpected chmod value. Choose from '+x' and '-x'.
126
+
127
+ >>> UtilAddArgsValidator().validate("README.md", verbose="true") # type: ignore[arg-type]
128
+ Traceback (most recent call last):
129
+ gitbolt.exceptions.GitExitingException: TypeError: 'verbose' must be a boolean
130
+
131
+ >>> UtilAddArgsValidator().validate(pathspec_from_file="file.txt") # type: ignore[arg-type]
132
+ Traceback (most recent call last):
133
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_from_file' must be a pathlib.Path or the string literal '-'.
134
+
135
+ >>> UtilAddArgsValidator().validate(pathspec_stdin="README.md")
136
+ Traceback (most recent call last):
137
+ gitbolt.exceptions.GitExitingException: ValueError: Either pathspec or pathspec_from_file is required
138
+
139
+ >>> UtilAddArgsValidator().validate(pathspec="a.py",
140
+ ... chmod="777") # type: ignore[arg-type] # expected +x, -x or None # provided int
141
+ Traceback (most recent call last):
142
+ gitbolt.exceptions.GitExitingException: ValueError: Unexpected chmod value. Choose from '+x' and '-x'.
143
+
144
+ >>> UtilAddArgsValidator().validate(pathspec="a.py",
145
+ ... no_all="yes") # type: ignore[arg-type] # expected bool # provided int
146
+ Traceback (most recent call last):
147
+ gitbolt.exceptions.GitExitingException: TypeError: 'no_all' must be either True, False, or None
148
+
149
+ >>> UtilAddArgsValidator().validate(pathspec="a.py",
150
+ ... renormalize="sometimes") # type: ignore[arg-type] # expected bool # provided str
151
+ Traceback (most recent call last):
152
+ gitbolt.exceptions.GitExitingException: TypeError: 'renormalize' must be a boolean
153
+
154
+ >>> UtilAddArgsValidator().validate(verbose=True)
155
+ Traceback (most recent call last):
156
+ gitbolt.exceptions.GitExitingException: ValueError: Either pathspec or pathspec_from_file is required
157
+ """
158
+ self.mandate_required_arguments(
159
+ pathspec,
160
+ *pathspecs,
161
+ pathspec_from_file=pathspec_from_file,
162
+ pathspec_stdin=pathspec_stdin,
163
+ )
164
+ self.validate_exclusive_args(
165
+ pathspec,
166
+ *pathspecs,
167
+ pathspec_from_file=pathspec_from_file,
168
+ pathspec_stdin=pathspec_stdin,
169
+ pathspec_file_nul=pathspec_file_nul,
170
+ )
171
+ self.validate_git_add_opts(**add_opts)
172
+ self.validate_non_git_add_opts(
173
+ pathspec_file_nul, pathspec_from_file, pathspec_stdin
174
+ )
175
+
176
+ def mandate_required_arguments(
177
+ self,
178
+ pathspec: str | None,
179
+ *pathspecs: str,
180
+ pathspec_from_file: Path | Literal["-"] | None,
181
+ pathspec_stdin: str | None,
182
+ ):
183
+ """
184
+ One of ``pathspec``, ``pathspec_from_file`` or ``pathspec_from_file=- pathspec_stdin`` must be provided.
185
+
186
+ Examples:
187
+
188
+ * All three provided, no issue:
189
+
190
+ >>> UtilAddArgsValidator().mandate_required_arguments('add.py', 'more-add.py', 'even-more.txt',
191
+ ... pathspec_from_file=Path('a-file.txt'), pathspec_stdin='a str')
192
+
193
+ * Pairs provided, no issue:
194
+
195
+ >>> UtilAddArgsValidator().mandate_required_arguments('add.py', 'more-add.py', 'even-more.txt',
196
+ ... pathspec_from_file=Path('a-file.txt'), pathspec_stdin=None)
197
+ >>> UtilAddArgsValidator().mandate_required_arguments('add.py', 'more-add.py', 'even-more.txt',
198
+ ... pathspec_from_file=None, pathspec_stdin='a stdin str')
199
+ >>> UtilAddArgsValidator().mandate_required_arguments(None, pathspec_from_file=Path('a-file.txt'),
200
+ ... pathspec_stdin='a stdin str')
201
+
202
+ * Provided single required, no issue:
203
+
204
+ >>> UtilAddArgsValidator().mandate_required_arguments('add.py', pathspec_from_file=None, pathspec_stdin=None)
205
+ >>> UtilAddArgsValidator().mandate_required_arguments('add.py', 'more-add.py', 'even-more.txt', pathspec_from_file=None, pathspec_stdin=None)
206
+ >>> UtilAddArgsValidator().mandate_required_arguments(None, pathspec_from_file=Path('a-file.txt'), pathspec_stdin=None)
207
+
208
+ * Provided ``pathspec_stdin`` with pathpec_from_file=-``, no issue:
209
+
210
+ >>> UtilAddArgsValidator().mandate_required_arguments(None, pathspec_from_file='-', pathspec_stdin='stdin pathspec')
211
+
212
+ Error examples:
213
+
214
+ * No mandatory args provided:
215
+
216
+ >>> UtilAddArgsValidator().mandate_required_arguments(None, pathspec_from_file=None, pathspec_stdin=None)
217
+ Traceback (most recent call last):
218
+ gitbolt.exceptions.GitExitingException: ValueError: Either pathspec or pathspec_from_file is required
219
+
220
+ * ``pathspec_stdin`` not provided when ``pathspec_from_file=-``:
221
+
222
+ >>> UtilAddArgsValidator().mandate_required_arguments(None, pathspec_from_file='-', pathspec_stdin=None)
223
+ Traceback (most recent call last):
224
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin must be provided when pathspec_form_file is -
225
+
226
+ :raises GitExitingException: if no mandatory args are provided.
227
+ """
228
+ if (
229
+ not has_atleast_one_arg(pathspec, *pathspecs, enforce_type=False)
230
+ and pathspec_from_file is None
231
+ ):
232
+ errmsg = errmsg_creator.at_least_one_required(
233
+ "pathspec", "pathspec_from_file"
234
+ )
235
+ raise GitExitingException(
236
+ errmsg, exit_code=ERR_INVALID_USAGE
237
+ ) from ValueError(errmsg)
238
+ if pathspec_from_file == "-" and pathspec_stdin is None:
239
+ errmsg = "pathspec_stdin must be provided when pathspec_form_file is -"
240
+ raise GitExitingException(
241
+ errmsg, exit_code=ERR_INVALID_USAGE
242
+ ) from ValueError(errmsg)
243
+
244
+ def validate_exclusive_args(
245
+ self,
246
+ pathspec: str | None,
247
+ *pathspecs: str,
248
+ pathspec_from_file: Path | Literal["-"] | None,
249
+ pathspec_stdin: str | None,
250
+ pathspec_file_nul: bool | None,
251
+ ) -> None:
252
+ """
253
+ Method added so that subclasses may override as they please.
254
+
255
+ Check that exclusive args are not provided together.
256
+
257
+ Examples::
258
+
259
+ >>> UtilAddArgsValidator().validate("file.txt", pathspec_file_nul=True)
260
+ Traceback (most recent call last):
261
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_file_nul and pathspec are not allowed together
262
+
263
+ >>> UtilAddArgsValidator().validate(pathspec_from_file='-', pathspec_stdin=None)
264
+ Traceback (most recent call last):
265
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin must be provided when pathspec_form_file is -
266
+
267
+ >>> UtilAddArgsValidator().validate(pathspec_from_file='-')
268
+ Traceback (most recent call last):
269
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin must be provided when pathspec_form_file is -
270
+
271
+ >>> UtilAddArgsValidator().validate(pathspec_from_file=Path("x.txt"), pathspec_stdin="foo")
272
+ Traceback (most recent call last):
273
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec_stdin is not allowed unless pathspec_from_file is '-'.
274
+
275
+ >>> UtilAddArgsValidator().validate("README.md", pathspec_from_file=Path("foo"))
276
+ Traceback (most recent call last):
277
+ gitbolt.exceptions.GitExitingException: ValueError: pathspec and pathspec_from_file are not allowed together
278
+
279
+ >>> UtilAddArgsValidator().validate(pathspec_from_file="file.txt") # type: ignore[arg-type]
280
+ Traceback (most recent call last):
281
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_from_file' must be a pathlib.Path or the string literal '-'.
282
+
283
+ >>> UtilAddArgsValidator().validate(pathspec_stdin="README.md")
284
+ Traceback (most recent call last):
285
+ gitbolt.exceptions.GitExitingException: ValueError: Either pathspec or pathspec_from_file is required
286
+
287
+ :raises GitExitingException: if exclusive args are provided together.
288
+ """
289
+ if has_atleast_one_arg(pathspec, *pathspecs, enforce_type=False):
290
+ try:
291
+ ensure_atleast_one_arg(pathspec, *pathspecs, enforce_type=str)
292
+ except TypeError as te:
293
+ raise GitExitingException(
294
+ "pathspec must be a string.", exit_code=ERR_DATA_FORMAT_ERR
295
+ ) from te
296
+
297
+ if pathspec_from_file is not None:
298
+ errmsg = errmsg_creator.not_allowed_together(
299
+ "pathspec", "pathspec_from_file"
300
+ )
301
+ raise GitExitingException(
302
+ errmsg, exit_code=ERR_INVALID_USAGE
303
+ ) from ValueError(errmsg)
304
+ if pathspec_stdin is not None:
305
+ errmsg = errmsg_creator.not_allowed_together(
306
+ "pathspec", "pathspec_stdin"
307
+ )
308
+ raise GitExitingException(
309
+ errmsg, exit_code=ERR_INVALID_USAGE
310
+ ) from ValueError(errmsg)
311
+ if pathspec_file_nul:
312
+ errmsg = errmsg_creator.not_allowed_together(
313
+ "pathspec_file_nul", "pathspec"
314
+ )
315
+ raise GitExitingException(
316
+ errmsg, exit_code=ERR_INVALID_USAGE
317
+ ) from ValueError(errmsg)
318
+ if pathspec_from_file == "-" and pathspec_stdin is None:
319
+ errmsg = errmsg_creator.all_required(
320
+ "pathspec_stdin",
321
+ "pathspec_from_file",
322
+ suffix=" when pathspec_from_file is '-'.",
323
+ )
324
+ raise GitExitingException(
325
+ errmsg, exit_code=ERR_INVALID_USAGE
326
+ ) from ValueError(errmsg)
327
+ if pathspec_from_file != "-" and pathspec_stdin is not None:
328
+ errmsg = "pathspec_stdin is not allowed unless pathspec_from_file is '-'."
329
+ raise GitExitingException(
330
+ errmsg, exit_code=ERR_INVALID_USAGE
331
+ ) from ValueError(errmsg)
332
+
333
+ # region validate_git_add_opts
334
+ def validate_git_add_opts(self, **add_opts: Unpack[GitAddOpts]) -> None:
335
+ """
336
+ Validate Git add command specific options.
337
+ Delegates to specialized validation methods.
338
+
339
+ >>> UtilAddArgsValidator().validate_git_add_opts(verbose=True, chmod='+x', no_all=False)
340
+ >>> UtilAddArgsValidator().validate_git_add_opts(verbose='yes') # type: ignore[arg-type] # expected bool provided str
341
+ Traceback (most recent call last):
342
+ gitbolt.exceptions.GitExitingException: TypeError: 'verbose' must be a boolean
343
+ """
344
+ self.validate_bool_args(**add_opts)
345
+ self.validate_tri_state_args(**add_opts)
346
+ self.validate_chmod_arg(add_opts.get("chmod"))
347
+
348
+ # region validate_bool_args
349
+ def validate_bool_args(self, **bool_add_opts: Unpack[GitAddOpts]) -> None:
350
+ """
351
+ Validate boolean Git add options like 'verbose', 'dry_run', etc.
352
+
353
+ >>> UtilAddArgsValidator().validate_bool_args(verbose=True)
354
+ >>> UtilAddArgsValidator().validate_bool_args(verbose='true') # type: ignore[arg-type] # expected bool provided str
355
+ Traceback (most recent call last):
356
+ gitbolt.exceptions.GitExitingException: TypeError: 'verbose' must be a boolean
357
+ """
358
+ if "verbose" in bool_add_opts:
359
+ self.validate_verbose_bool_arg(bool_add_opts["verbose"])
360
+ if "dry_run" in bool_add_opts:
361
+ self.validate_dry_run_bool_arg(bool_add_opts["dry_run"])
362
+ if "force" in bool_add_opts:
363
+ self.validate_force_bool_arg(bool_add_opts["force"])
364
+ if "interactive" in bool_add_opts:
365
+ self.validate_interactive_bool_arg(bool_add_opts["interactive"])
366
+ if "patch" in bool_add_opts:
367
+ self.validate_patch_bool_arg(bool_add_opts["patch"])
368
+ if "edit" in bool_add_opts:
369
+ self.validate_edit_bool_arg(bool_add_opts["edit"])
370
+ if "sparse" in bool_add_opts:
371
+ self.validate_sparse_bool_arg(bool_add_opts["sparse"])
372
+ if "intent_to_add" in bool_add_opts:
373
+ self.validate_intent_to_add_bool_arg(bool_add_opts["intent_to_add"])
374
+ if "refresh" in bool_add_opts:
375
+ self.validate_refresh_bool_arg(bool_add_opts["refresh"])
376
+ if "ignore_errors" in bool_add_opts:
377
+ self.validate_ignore_errors_bool_arg(bool_add_opts["ignore_errors"])
378
+ if "ignore_missing" in bool_add_opts:
379
+ self.validate_ignore_missing_bool_arg(bool_add_opts["ignore_missing"])
380
+ if "renormalize" in bool_add_opts:
381
+ self.validate_renormalize_bool_arg(bool_add_opts["renormalize"])
382
+
383
+ def validate_verbose_bool_arg(self, verbose: bool) -> None:
384
+ """Validate `verbose` argument.
385
+
386
+ >>> UtilAddArgsValidator().validate_verbose_bool_arg(True)
387
+ >>> UtilAddArgsValidator().validate_verbose_bool_arg('yes') # type: ignore[arg-type] # expected bool provided str
388
+ Traceback (most recent call last):
389
+ gitbolt.exceptions.GitExitingException: TypeError: 'verbose' must be a boolean
390
+ """
391
+ require_type(verbose, "verbose", bool, GitExitingException)
392
+
393
+ def validate_dry_run_bool_arg(self, dry_run: bool) -> None:
394
+ """Validate `dry_run` argument.
395
+
396
+ >>> UtilAddArgsValidator().validate_dry_run_bool_arg(False)
397
+ >>> UtilAddArgsValidator().validate_dry_run_bool_arg(1) # type: ignore[arg-type] # expected bool provided int
398
+ Traceback (most recent call last):
399
+ gitbolt.exceptions.GitExitingException: TypeError: 'dry_run' must be a boolean
400
+ """
401
+ require_type(dry_run, "dry_run", bool, GitExitingException)
402
+
403
+ def validate_force_bool_arg(self, force: bool) -> None:
404
+ """Validate `force` argument.
405
+
406
+ >>> UtilAddArgsValidator().validate_force_bool_arg(True)
407
+ >>> UtilAddArgsValidator().validate_force_bool_arg('true') # type: ignore[arg-type] # expected bool provided str
408
+ Traceback (most recent call last):
409
+ gitbolt.exceptions.GitExitingException: TypeError: 'force' must be a boolean
410
+ """
411
+ require_type(force, "force", bool, GitExitingException)
412
+
413
+ def validate_interactive_bool_arg(self, interactive: bool) -> None:
414
+ """Validate `interactive` argument.
415
+
416
+ >>> UtilAddArgsValidator().validate_interactive_bool_arg(False)
417
+ >>> UtilAddArgsValidator().validate_interactive_bool_arg('false') # type: ignore[arg-type] # expected bool provided str
418
+ Traceback (most recent call last):
419
+ gitbolt.exceptions.GitExitingException: TypeError: 'interactive' must be a boolean
420
+ """
421
+ require_type(interactive, "interactive", bool, GitExitingException)
422
+
423
+ def validate_patch_bool_arg(self, patch: bool) -> None:
424
+ """Validate `patch` argument.
425
+
426
+ >>> UtilAddArgsValidator().validate_patch_bool_arg(True)
427
+ >>> UtilAddArgsValidator().validate_patch_bool_arg(0.1) # type: ignore[arg-type] # expected bool provided float
428
+ Traceback (most recent call last):
429
+ gitbolt.exceptions.GitExitingException: TypeError: 'patch' must be a boolean
430
+ """
431
+ require_type(patch, "patch", bool, GitExitingException)
432
+
433
+ def validate_edit_bool_arg(self, edit: bool) -> None:
434
+ """Validate `edit` argument.
435
+
436
+ >>> UtilAddArgsValidator().validate_edit_bool_arg(True)
437
+ >>> UtilAddArgsValidator().validate_edit_bool_arg('no') # type: ignore[arg-type] # expected bool provided str
438
+ Traceback (most recent call last):
439
+ gitbolt.exceptions.GitExitingException: TypeError: 'edit' must be a boolean
440
+ """
441
+ require_type(edit, "edit", bool, GitExitingException)
442
+
443
+ def validate_sparse_bool_arg(self, sparse: bool) -> None:
444
+ """Validate `sparse` argument.
445
+
446
+ >>> UtilAddArgsValidator().validate_sparse_bool_arg(True)
447
+ >>> UtilAddArgsValidator().validate_sparse_bool_arg(None) # type: ignore[arg-type] # expected bool provided None
448
+ Traceback (most recent call last):
449
+ gitbolt.exceptions.GitExitingException: TypeError: 'sparse' must be a boolean
450
+ """
451
+ require_type(sparse, "sparse", bool, GitExitingException)
452
+
453
+ def validate_intent_to_add_bool_arg(self, intent_to_add: bool) -> None:
454
+ """Validate `intent_to_add` argument.
455
+
456
+ >>> UtilAddArgsValidator().validate_intent_to_add_bool_arg(False)
457
+ >>> UtilAddArgsValidator().validate_intent_to_add_bool_arg('maybe') # type: ignore[arg-type] # expected bool provided str
458
+ Traceback (most recent call last):
459
+ gitbolt.exceptions.GitExitingException: TypeError: 'intent_to_add' must be a boolean
460
+ """
461
+ require_type(intent_to_add, "intent_to_add", bool, GitExitingException)
462
+
463
+ def validate_refresh_bool_arg(self, refresh: bool) -> None:
464
+ """Validate `refresh` argument.
465
+
466
+ >>> UtilAddArgsValidator().validate_refresh_bool_arg(True)
467
+ >>> UtilAddArgsValidator().validate_refresh_bool_arg([]) # type: ignore[arg-type] # expected bool provided list
468
+ Traceback (most recent call last):
469
+ gitbolt.exceptions.GitExitingException: TypeError: 'refresh' must be a boolean
470
+ """
471
+ require_type(refresh, "refresh", bool, GitExitingException)
472
+
473
+ def validate_ignore_errors_bool_arg(self, ignore_errors: bool) -> None:
474
+ """Validate `ignore_errors` argument.
475
+
476
+ >>> UtilAddArgsValidator().validate_ignore_errors_bool_arg(True)
477
+ >>> UtilAddArgsValidator().validate_ignore_errors_bool_arg('error') # type: ignore[arg-type] # expected bool provided str
478
+ Traceback (most recent call last):
479
+ gitbolt.exceptions.GitExitingException: TypeError: 'ignore_errors' must be a boolean
480
+ """
481
+ require_type(ignore_errors, "ignore_errors", bool, GitExitingException)
482
+
483
+ def validate_ignore_missing_bool_arg(self, ignore_missing: bool) -> None:
484
+ """Validate `ignore_missing` argument.
485
+
486
+ >>> UtilAddArgsValidator().validate_ignore_missing_bool_arg(False)
487
+ >>> UtilAddArgsValidator().validate_ignore_missing_bool_arg('none') # type: ignore[arg-type] # expected bool provided str
488
+ Traceback (most recent call last):
489
+ gitbolt.exceptions.GitExitingException: TypeError: 'ignore_missing' must be a boolean
490
+ """
491
+ require_type(ignore_missing, "ignore_missing", bool, GitExitingException)
492
+
493
+ def validate_renormalize_bool_arg(self, renormalize: bool) -> None:
494
+ """
495
+ Validate `renormalize` argument.
496
+
497
+ >>> UtilAddArgsValidator().validate_renormalize_bool_arg(True)
498
+ >>> UtilAddArgsValidator().validate_renormalize_bool_arg('false') # type: ignore[arg-type] # expected bool provided str
499
+ Traceback (most recent call last):
500
+ gitbolt.exceptions.GitExitingException: TypeError: 'renormalize' must be a boolean
501
+ """
502
+ require_type(renormalize, "renormalize", bool, GitExitingException)
503
+
504
+ # endregion
505
+
506
+ # region validate_tri_state_args
507
+ def validate_tri_state_args(self, **tri_state_add_opts: Unpack[GitAddOpts]) -> None:
508
+ """
509
+ Validate tri-state arguments that accept True, False, or None.
510
+
511
+ >>> UtilAddArgsValidator().validate_tri_state_args(no_all=None)
512
+ >>> UtilAddArgsValidator().validate_tri_state_args(no_all='yes') # type: ignore[arg-type] # expected [bool | None] provided str
513
+ Traceback (most recent call last):
514
+ ...
515
+ gitbolt.exceptions.GitExitingException: TypeError: 'no_all' must be either True, False, or None
516
+ """
517
+ if "no_all" in tri_state_add_opts:
518
+ self.validate_no_all_tri_state_arg(tri_state_add_opts["no_all"])
519
+ if "no_ignore_removal" in tri_state_add_opts:
520
+ self.validate_no_ignore_removal_tri_state_arg(
521
+ tri_state_add_opts["no_ignore_removal"]
522
+ )
523
+
524
+ def validate_no_all_tri_state_arg(self, no_all: bool | None) -> None:
525
+ """
526
+ Validate `no_all` tri-state argument.
527
+
528
+ >>> UtilAddArgsValidator().validate_no_all_tri_state_arg(True)
529
+ >>> UtilAddArgsValidator().validate_no_all_tri_state_arg(None)
530
+ >>> UtilAddArgsValidator().validate_no_all_tri_state_arg('bad') # type: ignore[arg-type] # expected [bool | None] provided str
531
+ Traceback (most recent call last):
532
+ gitbolt.exceptions.GitExitingException: TypeError: 'no_all' must be either True, False, or None
533
+ """
534
+ if no_all not in (True, False, None):
535
+ errmsg = "'no_all' must be either True, False, or None"
536
+ raise GitExitingException(
537
+ errmsg, exit_code=ERR_DATA_FORMAT_ERR
538
+ ) from TypeError(errmsg)
539
+
540
+ def validate_no_ignore_removal_tri_state_arg(
541
+ self, no_ignore_removal: bool | None
542
+ ) -> None:
543
+ """
544
+ Validate `no_ignore_removal` tri-state argument.
545
+
546
+ >>> UtilAddArgsValidator().validate_no_ignore_removal_tri_state_arg(False)
547
+ >>> UtilAddArgsValidator().validate_no_ignore_removal_tri_state_arg('nope') # type: ignore[arg-type] # expected [bool | None] provided str
548
+ Traceback (most recent call last):
549
+ gitbolt.exceptions.GitExitingException: TypeError: 'no_ignore_removal' must be either True, False, or None
550
+ """
551
+ if no_ignore_removal not in (True, False, None):
552
+ errmsg = "'no_ignore_removal' must be either True, False, or None"
553
+ raise GitExitingException(
554
+ errmsg, exit_code=ERR_DATA_FORMAT_ERR
555
+ ) from TypeError(errmsg)
556
+
557
+ # endregion
558
+
559
+ def validate_chmod_arg(self, chmod: Literal["+x", "-x"] | None) -> None:
560
+ """
561
+ Validate `chmod` argument.
562
+
563
+ >>> UtilAddArgsValidator().validate_chmod_arg('+x')
564
+ >>> UtilAddArgsValidator().validate_chmod_arg(None)
565
+ >>> UtilAddArgsValidator().validate_chmod_arg('bad') # type: ignore[arg-type] # expected Literal[+x, -x] provided str
566
+ Traceback (most recent call last):
567
+ gitbolt.exceptions.GitExitingException: ValueError: Unexpected chmod value. Choose from '+x' and '-x'.
568
+ """
569
+ if chmod:
570
+ if chmod not in ("+x", "-x"):
571
+ errmsg = errmsg_creator.errmsg_for_choices(
572
+ emphasis="chmod", choices=["+x", "-x"]
573
+ )
574
+ raise GitExitingException(
575
+ errmsg, exit_code=ERR_INVALID_USAGE
576
+ ) from ValueError(errmsg)
577
+
578
+ # endregion
579
+
580
+ # region validate_non_git_add_opts
581
+ def validate_non_git_add_opts(
582
+ self,
583
+ pathspec_file_nul: bool,
584
+ pathspec_from_file: Path | Literal["-"] | None,
585
+ pathspec_stdin: str | None,
586
+ ) -> None:
587
+ """
588
+ Validate non-GitAddOpts arguments like pathspec_from_file and pathspec_stdin.
589
+
590
+ >>> validator = UtilAddArgsValidator()
591
+ >>> validator.validate_non_git_add_opts(False, None, None)
592
+ >>> validator.validate_non_git_add_opts(True, '-', 'abc')
593
+ >>> validator.validate_non_git_add_opts(True, '-', 123) # type: ignore[arg-type] # expected str # provided int
594
+ Traceback (most recent call last):
595
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_stdin' must be a string
596
+ """
597
+ self.validate_pathspec_file_nul(pathspec_file_nul)
598
+ self.validate_pathspec_from_file(pathspec_from_file)
599
+ self.validate_pathspec_stdin(pathspec_stdin)
600
+
601
+ def validate_pathspec_file_nul(self, pathspec_file_nul: bool) -> None:
602
+ """
603
+ Validate `pathspec_file_nul` argument.
604
+
605
+ >>> UtilAddArgsValidator().validate_pathspec_file_nul(True)
606
+ >>> UtilAddArgsValidator().validate_pathspec_file_nul('yes') # type: ignore[arg-type] # expected bool # provided str
607
+ Traceback (most recent call last):
608
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_file_nul' must be a boolean
609
+ """
610
+ require_type(pathspec_file_nul, "pathspec_file_nul", bool, GitExitingException)
611
+
612
+ def validate_pathspec_from_file(
613
+ self, pathspec_from_file: Path | Literal["-"] | None
614
+ ) -> None:
615
+ """
616
+ Validate `pathspec_from_file` argument.
617
+
618
+ >>> UtilAddArgsValidator().validate_pathspec_from_file(Path("foo.txt"))
619
+ >>> UtilAddArgsValidator().validate_pathspec_from_file('-')
620
+ >>> UtilAddArgsValidator().validate_pathspec_from_file(123) # type: ignore[arg-type] # expected Path | Literal['-'] # provided int
621
+ Traceback (most recent call last):
622
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_from_file' must be a pathlib.Path or the string literal '-'.
623
+ >>> UtilAddArgsValidator().validate_pathspec_from_file('file.txt') # type: ignore[arg-type] # expected Path | Literal['-'] # provided str
624
+ Traceback (most recent call last):
625
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_from_file' must be a pathlib.Path or the string literal '-'.
626
+ """
627
+ if pathspec_from_file is not None:
628
+ if (
629
+ not isinstance(pathspec_from_file, (Path, str))
630
+ or pathspec_from_file != "-"
631
+ and not isinstance(pathspec_from_file, Path)
632
+ ):
633
+ errmsg = "'pathspec_from_file' must be a pathlib.Path or the string literal '-'."
634
+ raise GitExitingException(
635
+ errmsg, exit_code=ERR_DATA_FORMAT_ERR
636
+ ) from TypeError(errmsg)
637
+
638
+ def validate_pathspec_stdin(self, pathspec_stdin: str | None):
639
+ """
640
+
641
+ Validate `pathspec_stdin` argument.
642
+
643
+ >>> UtilAddArgsValidator().validate_pathspec_stdin('-')
644
+ >>> UtilAddArgsValidator().validate_pathspec_stdin('some stdin')
645
+ >>> UtilAddArgsValidator().validate_pathspec_stdin(123) # type: ignore[arg-type] # expected str # provided int
646
+ Traceback (most recent call last):
647
+ gitbolt.exceptions.GitExitingException: TypeError: 'pathspec_stdin' must be a string
648
+ """
649
+ if pathspec_stdin is not None:
650
+ require_type(pathspec_stdin, "pathspec_stdin", str, GitExitingException)
651
+
652
+ # endregion