skilleter-thingy 0.0.95__py3-none-any.whl → 0.0.96__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.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- skilleter_thingy/multigit.py +107 -33
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/METADATA +14 -4
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/RECORD +7 -7
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/LICENSE +0 -0
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/entry_points.txt +0 -0
- {skilleter_thingy-0.0.95.dist-info → skilleter_thingy-0.0.96.dist-info}/top_level.txt +0 -0
skilleter_thingy/multigit.py
CHANGED
|
@@ -16,10 +16,12 @@ import thingy.colour as colour
|
|
|
16
16
|
|
|
17
17
|
# DONE: / Output name of each git repo as it is processed as command sits there seeming to do nothing otherwise.
|
|
18
18
|
# DONE: Better error-handling - e.g. continue/abort option after failure in one repo
|
|
19
|
+
# DONE: Currently doesn't handle single letter options in concatenated form - e.g. -dv
|
|
19
20
|
# DONE: Don't save the configuration on exit if it hasn't changed
|
|
20
21
|
# DONE: Don't use a fixed list of default branch names
|
|
21
22
|
# DONE: If the config file isn't in the current directory then search up the directory tree for it but run in the current directory
|
|
22
23
|
# DONE: Use the configuration file
|
|
24
|
+
# DONE: command to categorise repos then command line filter to only act on repos in category (in addition to other filtering options) - +tag <TAG> command tags all selected repors and updates the configuration, +untag <TAG> command to remove tags in the same way
|
|
23
25
|
# DONE: init function
|
|
24
26
|
# NOPE: Dry-run option - just pass the option to the Git command
|
|
25
27
|
# NOPE: Is it going to be a problem if the same repo is checked out twice or more in the same workspace - user problem
|
|
@@ -29,9 +31,12 @@ import thingy.colour as colour
|
|
|
29
31
|
# TODO: Command that takes partial repo name and either returns full path or pops up window to autocomplete until single match found
|
|
30
32
|
# TODO: Consistent colours in output
|
|
31
33
|
# TODO: If run in a subdirectory, only process repos in that tree (or have an option to do so)
|
|
34
|
+
# TODO: Option to +dir to return all matches so that caller can select one they want
|
|
32
35
|
# TODO: Verbose option
|
|
36
|
+
# TODO: When filtering by tag, if tag starts with '!' only match if tag isn't present (and don't allow '!' at start of tag otherwise)
|
|
33
37
|
# TODO: When specifying list of repos, if repo name doesn't contain '/' prefix it with '*'?
|
|
34
38
|
# TODO: select_git_repos() and +dir should use consist way of selecting repos if possible
|
|
39
|
+
# TODO: +run command to do things other than git commands
|
|
35
40
|
################################################################################
|
|
36
41
|
|
|
37
42
|
DEFAULT_CONFIG_FILE = 'multigit.toml'
|
|
@@ -43,16 +48,13 @@ DEFAULT_BRANCH = 'DEFAULT'
|
|
|
43
48
|
|
|
44
49
|
################################################################################
|
|
45
50
|
|
|
46
|
-
HELP_INFO = """usage: multigit [-h] [--
|
|
51
|
+
HELP_INFO = """usage: multigit [-h] [--verbose] [--quiet] [--config CONFIG] [--directory DIRECTORY] [--repos REPOS] [--modified] [--branched] [--tag TAGS]
|
|
47
52
|
{+init, +config, +dir, GIT_COMMAND} ...
|
|
48
53
|
|
|
49
54
|
Run git commands in multiple Git repos. DISCLAIMER: This is beta-quality software, with missing features and liable to fail with a stack trace, but shouldn't eat your data
|
|
50
55
|
|
|
51
56
|
options:
|
|
52
57
|
-h, --help show this help message and exit
|
|
53
|
-
--dryrun, --dry-run, -D
|
|
54
|
-
Dry-run comands
|
|
55
|
-
--debug, -d Debug
|
|
56
58
|
--verbose, -v Verbosity to the maximum
|
|
57
59
|
--quiet, -q Minimal console output
|
|
58
60
|
--config CONFIG, -c CONFIG
|
|
@@ -63,6 +65,7 @@ options:
|
|
|
63
65
|
The repo names to work on (defaults to all repos and can contain shell wildcards and can be issued multiple times on the command line)
|
|
64
66
|
--modified, -m Select repos that have local modifications
|
|
65
67
|
--branched, -b Select repos that do not have the default branch checked out
|
|
68
|
+
--tag TAG, -t TAG Select repos that have the specified tag (can be issued multiple times on the command line)
|
|
66
69
|
--continue, -C Continue if a git command returns an error (by default, executation terminates when a command fails)
|
|
67
70
|
|
|
68
71
|
Sub-commands:
|
|
@@ -70,6 +73,8 @@ Sub-commands:
|
|
|
70
73
|
+init Build or update the configuration file using the current branch in each repo as the default branch
|
|
71
74
|
+config Return the name and location of the configuration file
|
|
72
75
|
+dir Return the location of a working tree, given the repo name, or if no parameter specified, the root directory of the multigit tree
|
|
76
|
+
+tag TAG Apply a configuration tag to repos filtered by the command line options (list configuration tags if no parameter specified)
|
|
77
|
+
+untag TAG Remove a configuration tag to repos filtered by the command line options
|
|
73
78
|
GIT_COMMAND Any git command, including options and parameters - this is then run in all specified working trees
|
|
74
79
|
|
|
75
80
|
"""
|
|
@@ -80,19 +85,19 @@ Sub-commands:
|
|
|
80
85
|
class Arguments():
|
|
81
86
|
"""Data class to contain command line options and parameters"""
|
|
82
87
|
|
|
83
|
-
dryrun: bool = False
|
|
84
|
-
debug: bool = False
|
|
85
88
|
quiet: bool = False
|
|
86
89
|
verbose: bool = False
|
|
87
90
|
configuration_file: str = DEFAULT_CONFIG_FILE
|
|
88
91
|
directory: str = '.'
|
|
89
92
|
repos: list[str] = field(default_factory=list)
|
|
93
|
+
tag: list[str] = field(default_factory=list)
|
|
90
94
|
modified: bool = False
|
|
91
95
|
branched: bool = False
|
|
92
96
|
command: str = None
|
|
93
97
|
error_continue: bool = False
|
|
94
98
|
parameters: list[str] = field(default_factory=list)
|
|
95
99
|
internal_command: bool = False
|
|
100
|
+
config_modified: bool = False
|
|
96
101
|
|
|
97
102
|
################################################################################
|
|
98
103
|
|
|
@@ -170,26 +175,27 @@ def find_git_repos(args):
|
|
|
170
175
|
|
|
171
176
|
def select_git_repos(args, config):
|
|
172
177
|
"""Return git repos from the configuration that match the criteria on the
|
|
173
|
-
multigit command line (the --repos, --modified and --branched options)
|
|
178
|
+
multigit command line (the --repos, --tag, --modified and --branched options)
|
|
174
179
|
or, return them all if no relevant options specified"""
|
|
175
180
|
|
|
176
|
-
for
|
|
177
|
-
# If repos are specified, then only match according to
|
|
178
|
-
# path or
|
|
181
|
+
for repo_path in config.sections():
|
|
182
|
+
# If repos are specified, then only match according to exact name match,
|
|
183
|
+
# exact path match or wildcard match
|
|
179
184
|
|
|
180
185
|
if args.repos:
|
|
181
186
|
for entry in args.repos:
|
|
187
|
+
if config[repo_path]['repo name'] == entry:
|
|
188
|
+
matching = True
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
if repo_path == entry:
|
|
192
|
+
matching = True
|
|
193
|
+
break
|
|
194
|
+
|
|
182
195
|
if '?' in entry or '*' in entry:
|
|
183
|
-
if fnmatch.fnmatch(repo, entry):
|
|
196
|
+
if fnmatch.fnmatch(repo_path, entry) or fnmatch.fnmatch(config[repo_path]['repo name'], entry):
|
|
184
197
|
matching = True
|
|
185
198
|
break
|
|
186
|
-
elif '/' in entry:
|
|
187
|
-
if repo == entry:
|
|
188
|
-
matching = True
|
|
189
|
-
break
|
|
190
|
-
elif os.path.basename(repo) == entry:
|
|
191
|
-
matching = True
|
|
192
|
-
break
|
|
193
199
|
|
|
194
200
|
else:
|
|
195
201
|
matching = False
|
|
@@ -199,17 +205,32 @@ def select_git_repos(args, config):
|
|
|
199
205
|
# If branched specified, only match if the repo is matched _and_ branched
|
|
200
206
|
|
|
201
207
|
if matching and args.branched:
|
|
202
|
-
if git.branch(path=
|
|
208
|
+
if git.branch(path=repo_path) == config[repo_path]['default branch']:
|
|
203
209
|
matching = False
|
|
204
210
|
|
|
205
211
|
# If modified specified, only match if the repo is matched _and_ modified
|
|
206
212
|
|
|
207
213
|
if matching and args.modified:
|
|
208
|
-
if not git.status(path=
|
|
214
|
+
if not git.status(path=repo_path):
|
|
215
|
+
matching = False
|
|
216
|
+
|
|
217
|
+
# If tag filtering specified, only match if the repo is tagged with one of the specified tags
|
|
218
|
+
|
|
219
|
+
if matching and args.tag:
|
|
220
|
+
for entry in args.tag:
|
|
221
|
+
try:
|
|
222
|
+
tags = config[repo_path]['tags'].split(',')
|
|
223
|
+
if entry in tags:
|
|
224
|
+
break
|
|
225
|
+
except KeyError:
|
|
226
|
+
pass
|
|
227
|
+
else:
|
|
209
228
|
matching = False
|
|
210
229
|
|
|
230
|
+
# If we have a match, yield the config entry to the caller
|
|
231
|
+
|
|
211
232
|
if matching:
|
|
212
|
-
yield config[
|
|
233
|
+
yield config[repo_path]
|
|
213
234
|
|
|
214
235
|
################################################################################
|
|
215
236
|
|
|
@@ -227,8 +248,8 @@ def mg_init(args, config, console):
|
|
|
227
248
|
|
|
228
249
|
# Sanity checks
|
|
229
250
|
|
|
230
|
-
if args.modified or args.branched:
|
|
231
|
-
error('The "--modified" and "--branched" options cannot be used with the "init" subcommand')
|
|
251
|
+
if args.modified or args.branched or args.tag:
|
|
252
|
+
error('The "--tag", "--modified" and "--branched" options cannot be used with the "init" subcommand')
|
|
232
253
|
elif not config:
|
|
233
254
|
error(f'Unable to location configuration file "{args.configuration_file}"')
|
|
234
255
|
|
|
@@ -253,6 +274,8 @@ def mg_init(args, config, console):
|
|
|
253
274
|
else:
|
|
254
275
|
config[repo]['repo name'] = os.path.basename(repo)
|
|
255
276
|
|
|
277
|
+
args.config_modified = True
|
|
278
|
+
|
|
256
279
|
################################################################################
|
|
257
280
|
|
|
258
281
|
def mg_dir(args, config, console):
|
|
@@ -288,12 +311,15 @@ def mg_dir(args, config, console):
|
|
|
288
311
|
elif fnmatch.fnmatch(repo['repo name'], f'*{search_name}*'):
|
|
289
312
|
wild_location.append(repo.name)
|
|
290
313
|
|
|
314
|
+
# Look for a single exact match, a prefix with '*' match or prefix+suffix
|
|
315
|
+
|
|
291
316
|
destination = None
|
|
292
317
|
for destinations in (location, wild_prefix_location, wild_location):
|
|
293
318
|
if len(destinations) == 1:
|
|
294
319
|
destination = destinations
|
|
295
320
|
break
|
|
296
|
-
|
|
321
|
+
|
|
322
|
+
if len(destinations) > 1:
|
|
297
323
|
destination = destinations
|
|
298
324
|
|
|
299
325
|
if not destination:
|
|
@@ -308,6 +334,51 @@ def mg_dir(args, config, console):
|
|
|
308
334
|
|
|
309
335
|
################################################################################
|
|
310
336
|
|
|
337
|
+
def mg_tag(args, config, console):
|
|
338
|
+
"""Apply a configuration tag"""
|
|
339
|
+
|
|
340
|
+
_ = console
|
|
341
|
+
|
|
342
|
+
if len(args.parameters) > 1:
|
|
343
|
+
error('The +tag command takes no more than one parameter')
|
|
344
|
+
|
|
345
|
+
for repo in select_git_repos(args, config):
|
|
346
|
+
try:
|
|
347
|
+
tags = repo.get('tags').split(',')
|
|
348
|
+
except AttributeError:
|
|
349
|
+
tags = []
|
|
350
|
+
|
|
351
|
+
if args.parameters:
|
|
352
|
+
if args.parameters[0] not in tags:
|
|
353
|
+
tags.append(args.parameters[0])
|
|
354
|
+
repo['tags'] = ','.join(tags)
|
|
355
|
+
args.config_modified = True
|
|
356
|
+
elif tags:
|
|
357
|
+
colour.write(f'[BOLD:{repo["repo name"]}] - {", ".join(tags)}')
|
|
358
|
+
|
|
359
|
+
################################################################################
|
|
360
|
+
|
|
361
|
+
def mg_untag(args, config, console):
|
|
362
|
+
"""Remove a configuration tag"""
|
|
363
|
+
|
|
364
|
+
_ = console
|
|
365
|
+
|
|
366
|
+
if len(args.parameters) > 1:
|
|
367
|
+
error('The +tag command takes no more than one parameter')
|
|
368
|
+
|
|
369
|
+
for repo in select_git_repos(args, config):
|
|
370
|
+
try:
|
|
371
|
+
tags = repo.get('tags', '').split(',')
|
|
372
|
+
except AttributeError:
|
|
373
|
+
tags = []
|
|
374
|
+
|
|
375
|
+
if args.parameters[0] in tags:
|
|
376
|
+
tags.remove(args.parameters[0])
|
|
377
|
+
repo['tags'] = ','.join(tags)
|
|
378
|
+
args.config_modified = True
|
|
379
|
+
|
|
380
|
+
################################################################################
|
|
381
|
+
|
|
311
382
|
def mg_config(args, config, console):
|
|
312
383
|
"""Output the path to the configuration file"""
|
|
313
384
|
|
|
@@ -359,19 +430,13 @@ def parse_command_line():
|
|
|
359
430
|
for c in arg[1:]:
|
|
360
431
|
argv.append('-' + c)
|
|
361
432
|
|
|
362
|
-
#
|
|
433
|
+
# Parse the command line
|
|
363
434
|
|
|
364
435
|
i = 1
|
|
365
436
|
while i < len(argv):
|
|
366
437
|
param = argv[i]
|
|
367
438
|
|
|
368
|
-
if param in ('--
|
|
369
|
-
args.dryrun = True
|
|
370
|
-
|
|
371
|
-
elif param in ('--debug', '-d'):
|
|
372
|
-
args.debug = True
|
|
373
|
-
|
|
374
|
-
elif param in ('--verbose', '-v'):
|
|
439
|
+
if param in ('--verbose', '-v'):
|
|
375
440
|
args.verbose = True
|
|
376
441
|
|
|
377
442
|
elif param in ('--quiet', '-q'):
|
|
@@ -391,6 +456,13 @@ def parse_command_line():
|
|
|
391
456
|
except IndexError:
|
|
392
457
|
error('--repos - missing repo parameter')
|
|
393
458
|
|
|
459
|
+
elif param in ('--tag', '-t'):
|
|
460
|
+
try:
|
|
461
|
+
i += 1
|
|
462
|
+
args.tag.append(argv[i])
|
|
463
|
+
except IndexError:
|
|
464
|
+
error('--tag - missing tag parameter')
|
|
465
|
+
|
|
394
466
|
elif param in ('--modified', '-m'):
|
|
395
467
|
args.modified = True
|
|
396
468
|
|
|
@@ -440,6 +512,8 @@ def main():
|
|
|
440
512
|
'init': mg_init,
|
|
441
513
|
'dir': mg_dir,
|
|
442
514
|
'config': mg_config,
|
|
515
|
+
'tag': mg_tag,
|
|
516
|
+
'untag': mg_untag,
|
|
443
517
|
}
|
|
444
518
|
|
|
445
519
|
args = parse_command_line()
|
|
@@ -478,7 +552,7 @@ def main():
|
|
|
478
552
|
|
|
479
553
|
# Save the updated configuration file if it has changed (currently, only the init command will do this).
|
|
480
554
|
|
|
481
|
-
if config and args.
|
|
555
|
+
if config and args.config_modified:
|
|
482
556
|
with open(args.configuration_file, 'w', encoding='utf8') as configfile:
|
|
483
557
|
config.write(configfile)
|
|
484
558
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.96
|
|
4
4
|
Summary: A collection of useful utilities, mainly aimed at making Git more friendly
|
|
5
5
|
Author-email: John Skilleter <john@skilleter.org.uk>
|
|
6
6
|
Project-URL: Home, https://skilleter.org.uk
|
|
@@ -50,15 +50,21 @@ The multigit command line format is:
|
|
|
50
50
|
|
|
51
51
|
Where COMMAND is an internal multigit command if it starts with a '+' and is a git command otherwise.
|
|
52
52
|
|
|
53
|
-
By default, when multigit is invoked with a git command, it runs a the command in each of the working trees selected by the command line options passed to multigit (if no options are specified then the command is run in all the working trees.
|
|
53
|
+
By default, when multigit is invoked with a git command, it runs a the command in each of the working trees selected by the command line options passed to multigit (if no options are specified then the command is run in *all* the working trees.
|
|
54
54
|
|
|
55
55
|
The command takes a number of options that can be used to select the list of working trees that each of the subcommands that it supports runs in:
|
|
56
56
|
|
|
57
|
-
*--repos / -r* Allows a list of working trees to be specfied, either
|
|
57
|
+
*--repos REPO / -r REPO* Allows a list of working trees to be specfied, either by path, name or a wildcard matching either.
|
|
58
58
|
|
|
59
59
|
*--modified / -m* Run only in working trees containing locally modified files
|
|
60
60
|
|
|
61
|
-
*--branched / -b* Run only working trees where the current branch that is checked out is NOT the default branch
|
|
61
|
+
*--branched / -b* Run only in working trees where the current branch that is checked out is NOT the default branch
|
|
62
|
+
|
|
63
|
+
*--tag TAG / -t TAG* Run only in working trees that are tagged with the specified tag
|
|
64
|
+
|
|
65
|
+
These options are AND-ed together, so specifying `--modified --branched --tag WOMBAT` will select only working trees that are modified AND branched AND tagged with 'WOMBAT', but the parameters to the `--repos` and `--tag` options are OR-ed together, so specifying `--tag WOMBAT --tag EMU` will select repos that are tagged as `WOMBAT` *OR* `EMU`.
|
|
66
|
+
|
|
67
|
+
Multigit tags are stored in the configuration file, not within the working tree and each working tree can have multiple tags.
|
|
62
68
|
|
|
63
69
|
Multigit supports a small list of subcommands, each of which are prefixed with a `+` to distinguish them from Git commands:
|
|
64
70
|
|
|
@@ -68,6 +74,10 @@ Multigit supports a small list of subcommands, each of which are prefixed with a
|
|
|
68
74
|
|
|
69
75
|
*+config* - Print the name and location of the multigit configuration file.
|
|
70
76
|
|
|
77
|
+
*+tag TAG* - If no tag specified list tags applied to the specified working trees. If a tag *is* specified, then *apply* the tag to the specified working trees.
|
|
78
|
+
|
|
79
|
+
*+untag TAG* - Remove the tag from the specified working trees (do nothing if the tag is not applied in the first place).
|
|
80
|
+
|
|
71
81
|
Any command not prefixed with '+' is run in each of the working trees (filtered by the various multigit options) as a git command.
|
|
72
82
|
|
|
73
83
|
For example; `multigit -m commit -ab` would run `git commit -a` in each of the working trees that is branched and contains modified files.
|
|
@@ -25,7 +25,7 @@ skilleter_thingy/gl.py,sha256=9zbGpKxw6lX9RghLkdy-Q5sZlqtbB3uGFO04qTu1dH8,5954
|
|
|
25
25
|
skilleter_thingy/gphotosync.py,sha256=Vb2zYTEFp26BYdkG810SRg9afyfDqvq4CLHTk-MFf60,22388
|
|
26
26
|
skilleter_thingy/linecount.py,sha256=ehTN6VD76i4U5k6dXuYoiqSRHI67_BP-bziklNAJSKY,4309
|
|
27
27
|
skilleter_thingy/moviemover.py,sha256=QzUAWQzQ1AWWREIhl-VMaLo2h8MMhOekBnao5jGWV1s,4470
|
|
28
|
-
skilleter_thingy/multigit.py,sha256=
|
|
28
|
+
skilleter_thingy/multigit.py,sha256=vcwVtjDVehnheP2Kr0WtxtH6tbhwK8w90uK55AE7Fns,20908
|
|
29
29
|
skilleter_thingy/photodupe.py,sha256=l0hbzSLb2Vk2ceteg-x9fHXCEE1uUuFo84hz5rsZUPA,4184
|
|
30
30
|
skilleter_thingy/phototidier.py,sha256=BOu-cKHMivDlBqlRqv7sL3J6Ix1K2dxWWNcderldyZo,7818
|
|
31
31
|
skilleter_thingy/py_audit.py,sha256=xJm5k5qyeA6ii8mODa4dOkmP8L1drv94UHuxR54RsIM,4384
|
|
@@ -61,9 +61,9 @@ skilleter_thingy/thingy/run.py,sha256=6SNKWF01fSxzB10GMU9ajraXYZqAL1w0PXkqjJdr1U
|
|
|
61
61
|
skilleter_thingy/thingy/tfm_pane.py,sha256=oqy5zBzKwfbjbGqetbbhpKi4x5He7sl4qkmhUeqtdZc,19789
|
|
62
62
|
skilleter_thingy/thingy/tidy.py,sha256=71DCyj0VJrj52RmjQyj1eOiQJIfy5EIPHuThOrS6ZTA,5876
|
|
63
63
|
skilleter_thingy/thingy/venv_template.py,sha256=SsVNvSwojd8NnFeQaZPCRQYTNdwJRplpZpygbUEXRnY,1015
|
|
64
|
-
skilleter_thingy-0.0.
|
|
65
|
-
skilleter_thingy-0.0.
|
|
66
|
-
skilleter_thingy-0.0.
|
|
67
|
-
skilleter_thingy-0.0.
|
|
68
|
-
skilleter_thingy-0.0.
|
|
69
|
-
skilleter_thingy-0.0.
|
|
64
|
+
skilleter_thingy-0.0.96.dist-info/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
|
|
65
|
+
skilleter_thingy-0.0.96.dist-info/METADATA,sha256=uHydWMbSFN811U97D-3OTxf5pIV_KNCAcFkN5i0rkRI,9746
|
|
66
|
+
skilleter_thingy-0.0.96.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
67
|
+
skilleter_thingy-0.0.96.dist-info/entry_points.txt,sha256=u5ymS-KPljIGTnprV5yJsAjz7qgeT2BZ-Qo_Con_PFM,2145
|
|
68
|
+
skilleter_thingy-0.0.96.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
|
|
69
|
+
skilleter_thingy-0.0.96.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|