skilleter-thingy 0.0.95__py3-none-any.whl → 0.0.97__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.

@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- """mg - MultiGit - utility for managing multiple Git repos in a hierarchical directory tree"""
3
+ """mg - MultiGit - utility for managing multiple Git working trees in a hierarchical directory tree"""
4
4
 
5
5
  import os
6
6
  import sys
@@ -14,24 +14,32 @@ import thingy.colour as colour
14
14
 
15
15
  ################################################################################
16
16
 
17
- # DONE: / Output name of each git repo as it is processed as command sits there seeming to do nothing otherwise.
18
- # DONE: Better error-handling - e.g. continue/abort option after failure in one repo
17
+ # DONE: / Output name of each working tree as it is processed as command sits there seeming to do nothing otherwise.
18
+ # DONE: Better error-handling - e.g. continue/abort option after failure in one working tree
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 working trees then command line filter to only act on working trees in category (in addition to other filtering options) - +tag <TAG> command tags all selected working trees 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
26
28
  # NOPE: Pull/fetch - only output after running command and only if something updated
27
29
  # NOPE: Switch to tomlkit
28
30
  # TODO: -j option to run in parallel - yes, but it will only work with non-interactive Git commands
29
- # TODO: Command that takes partial repo name and either returns full path or pops up window to autocomplete until single match found
31
+ # TODO: Command that takes partial working tree name and either returns full path or pops up window to autocomplete until single match found
30
32
  # TODO: Consistent colours in output
31
- # TODO: If run in a subdirectory, only process repos in that tree (or have an option to do so)
33
+ # TODO: If run in a subdirectory, only process working trees 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
33
- # TODO: When specifying list of repos, if repo name doesn't contain '/' prefix it with '*'?
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)
37
+ # DONE: When specifying list of working trees, if 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
40
+ # TODO: init option '--update' to update the configuration file with new working trees and remove ones that are no longer there
41
+ # TODO: init option '--set-default' to update the default branch to the current one for specified working trees
42
+ # TODO: Integrat completion with fzf if installed?
35
43
  ################################################################################
36
44
 
37
45
  DEFAULT_CONFIG_FILE = 'multigit.toml'
@@ -43,26 +51,22 @@ DEFAULT_BRANCH = 'DEFAULT'
43
51
 
44
52
  ################################################################################
45
53
 
46
- HELP_INFO = """usage: multigit [-h] [--dryrun] [--debug] [--verbose] [--quiet] [--config CONFIG] [--directory DIRECTORY] [--repos REPOS] [--modified] [--branched]
54
+ HELP_INFO = """usage: multigit [-h] [--verbose] [--quiet] [--config CONFIG] [--repos REPOS] [--modified] [--branched] [--tag TAGS]
47
55
  {+init, +config, +dir, GIT_COMMAND} ...
48
56
 
49
57
  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
58
 
51
59
  options:
52
60
  -h, --help show this help message and exit
53
- --dryrun, --dry-run, -D
54
- Dry-run comands
55
- --debug, -d Debug
56
61
  --verbose, -v Verbosity to the maximum
57
62
  --quiet, -q Minimal console output
58
63
  --config CONFIG, -c CONFIG
59
64
  The configuration file (defaults to multigit.toml)
60
- --directory DIRECTORY, --dir DIRECTORY
61
- The top-level directory of the multigit tree (defaults to the current directory)
62
65
  --repos REPOS, -r REPOS
63
66
  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
67
  --modified, -m Select repos that have local modifications
65
68
  --branched, -b Select repos that do not have the default branch checked out
69
+ --tag TAG, -t TAG Select repos that have the specified tag (can be issued multiple times on the command line)
66
70
  --continue, -C Continue if a git command returns an error (by default, executation terminates when a command fails)
67
71
 
68
72
  Sub-commands:
@@ -70,6 +74,8 @@ Sub-commands:
70
74
  +init Build or update the configuration file using the current branch in each repo as the default branch
71
75
  +config Return the name and location of the configuration file
72
76
  +dir Return the location of a working tree, given the repo name, or if no parameter specified, the root directory of the multigit tree
77
+ +tag TAG Apply a configuration tag to repos filtered by the command line options (list configuration tags if no parameter specified)
78
+ +untag TAG Remove a configuration tag to repos filtered by the command line options
73
79
  GIT_COMMAND Any git command, including options and parameters - this is then run in all specified working trees
74
80
 
75
81
  """
@@ -80,20 +86,40 @@ Sub-commands:
80
86
  class Arguments():
81
87
  """Data class to contain command line options and parameters"""
82
88
 
83
- dryrun: bool = False
84
- debug: bool = False
89
+ # Command line options for output noise
90
+
85
91
  quiet: bool = False
86
92
  verbose: bool = False
87
- configuration_file: str = DEFAULT_CONFIG_FILE
88
- directory: str = '.'
93
+
94
+ # True if we continue after a git command returns an error
95
+
96
+ error_continue: bool = False
97
+
98
+ # Default and current configuration file
99
+
100
+ default_configuration_file: str = DEFAULT_CONFIG_FILE
101
+ configuration_file: str = None
102
+
103
+ # Command line filter options
104
+
89
105
  repos: list[str] = field(default_factory=list)
106
+ tag: list[str] = field(default_factory=list)
90
107
  modified: bool = False
91
108
  branched: bool = False
109
+
110
+ # Command to run with parameters
111
+
92
112
  command: str = None
93
- error_continue: bool = False
94
113
  parameters: list[str] = field(default_factory=list)
114
+
115
+ # True if running an internal command
116
+
95
117
  internal_command: bool = False
96
118
 
119
+ # True if the configuration data needs to be written back on completion
120
+
121
+ config_modified: bool = False
122
+
97
123
  ################################################################################
98
124
 
99
125
  def error(msg, status=1):
@@ -104,21 +130,21 @@ def error(msg, status=1):
104
130
 
105
131
  ################################################################################
106
132
 
107
- def find_configuration(args):
133
+ def find_configuration(default_config_file):
108
134
  """If the configuration file name has path elements, try and read it, otherwise
109
135
  search up the directory tree looking for the configuration file.
110
136
  Returns configuration file path or None if the configuration file
111
137
  could not be found."""
112
138
 
113
- if '/' in args.configuration_file:
114
- config_file = args.configuration_file
139
+ if '/' in default_config_file:
140
+ config_file = default_config_file
115
141
  else:
116
142
  config_path = os.getcwd()
117
- config_file = os.path.join(config_path, args.configuration_file)
143
+ config_file = os.path.join(config_path, default_config_file)
118
144
 
119
145
  while not os.path.isfile(config_file) and config_path != '/':
120
146
  config_path = os.path.dirname(config_path)
121
- config_file = os.path.join(config_path, args.configuration_file)
147
+ config_file = os.path.join(config_path, default_config_file)
122
148
 
123
149
  return config_file if os.path.isfile(config_file) else None
124
150
 
@@ -138,7 +164,7 @@ def show_progress(width, msg):
138
164
 
139
165
  ################################################################################
140
166
 
141
- def find_git_repos(args):
167
+ def find_working_trees(args):
142
168
  """Locate and return a list of '.git' directory parent directories in the
143
169
  specified path.
144
170
 
@@ -170,26 +196,27 @@ def find_git_repos(args):
170
196
 
171
197
  def select_git_repos(args, config):
172
198
  """Return git repos from the configuration that match the criteria on the
173
- multigit command line (the --repos, --modified and --branched options)
199
+ multigit command line (the --repos, --tag, --modified and --branched options)
174
200
  or, return them all if no relevant options specified"""
175
201
 
176
- for repo in config.sections():
177
- # If repos are specified, then only match according to wildcards, full
178
- # path or just basename.
202
+ for repo_path in config.sections():
203
+ # If repos are specified, then only match according to exact name match,
204
+ # exact path match or wildcard match
179
205
 
180
206
  if args.repos:
181
207
  for entry in args.repos:
208
+ if config[repo_path]['repo name'] == entry:
209
+ matching = True
210
+ break
211
+
212
+ if repo_path == entry:
213
+ matching = True
214
+ break
215
+
182
216
  if '?' in entry or '*' in entry:
183
- if fnmatch.fnmatch(repo, entry):
217
+ if fnmatch.fnmatch(repo_path, entry) or fnmatch.fnmatch(config[repo_path]['repo name'], entry):
184
218
  matching = True
185
219
  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
220
 
194
221
  else:
195
222
  matching = False
@@ -199,17 +226,32 @@ def select_git_repos(args, config):
199
226
  # If branched specified, only match if the repo is matched _and_ branched
200
227
 
201
228
  if matching and args.branched:
202
- if git.branch(path=repo) == config[repo]['default branch']:
229
+ if git.branch(path=repo_path) == config[repo_path]['default branch']:
203
230
  matching = False
204
231
 
205
232
  # If modified specified, only match if the repo is matched _and_ modified
206
233
 
207
234
  if matching and args.modified:
208
- if not git.status(path=repo):
235
+ if not git.status(path=repo_path):
209
236
  matching = False
210
237
 
238
+ # If tag filtering specified, only match if the repo is tagged with one of the specified tags
239
+
240
+ if matching and args.tag:
241
+ for entry in args.tag:
242
+ try:
243
+ tags = config[repo_path]['tags'].split(',')
244
+ if entry in tags:
245
+ break
246
+ except KeyError:
247
+ pass
248
+ else:
249
+ matching = False
250
+
251
+ # If we have a match, yield the config entry to the caller
252
+
211
253
  if matching:
212
- yield config[repo]
254
+ yield config[repo_path]
213
255
 
214
256
  ################################################################################
215
257
 
@@ -227,31 +269,52 @@ def mg_init(args, config, console):
227
269
 
228
270
  # Sanity checks
229
271
 
230
- if args.modified or args.branched:
231
- error('The "--modified" and "--branched" options cannot be used with the "init" subcommand')
232
- elif not config:
233
- error(f'Unable to location configuration file "{args.configuration_file}"')
234
-
235
- # TODO: Update should remove or warn about repos that are no longer present
272
+ if args.modified or args.branched or args.tag:
273
+ error('The "--tag", "--modified" and "--branched" options cannot be used with the "init" subcommand')
236
274
 
237
- # Search for .git directories
275
+ # Search for .git directories and add any that aren't already in the configuration
238
276
 
239
- for repo in find_git_repos(args):
277
+ repo_list = []
278
+ for repo in find_working_trees(args):
240
279
  if not args.quiet:
241
- show_progress(console.columns, repo.name)
280
+ show_progress(console.columns, repo)
281
+
282
+ repo_list.append(repo)
242
283
 
243
284
  if repo not in config:
285
+ default_branch = git.branch(path=repo)
286
+
244
287
  config[repo] = {
245
- 'default branch': git.branch(path=repo)
288
+ 'default branch': default_branch
246
289
  }
247
290
 
248
- remote = git.remotes(path=repo)
291
+ remote = git.remotes(path=repo)
249
292
 
250
- if 'origin' in remote:
251
- config[repo]['origin'] = remote['origin']
252
- config[repo]['repo name']= os.path.basename(remote['origin']).removesuffix('.git')
253
- else:
254
- config[repo]['repo name'] = os.path.basename(repo)
293
+ if 'origin' in remote:
294
+ config[repo]['origin'] = remote['origin']
295
+ config[repo]['repo name']= os.path.basename(remote['origin']).removesuffix('.git')
296
+ else:
297
+ config[repo]['repo name'] = os.path.basename(repo)
298
+
299
+ colour.write(f'Added [BOLD:{repo}] with default branch [BLUE:{default_branch}]')
300
+
301
+ # Look for configuration entries that are no longer present and delete them
302
+
303
+ colour.write()
304
+
305
+ removals = []
306
+
307
+ for repo in config:
308
+ if repo != 'DEFAULT' and repo not in repo_list:
309
+ removals.append(repo)
310
+
311
+ for entry in removals:
312
+ del config[repo]
313
+ colour.write(f'Removed [BOLD:{repo}] as it no longer exists')
314
+
315
+ # The configuration file needs to be updated
316
+
317
+ args.config_modified = True
255
318
 
256
319
  ################################################################################
257
320
 
@@ -288,12 +351,15 @@ def mg_dir(args, config, console):
288
351
  elif fnmatch.fnmatch(repo['repo name'], f'*{search_name}*'):
289
352
  wild_location.append(repo.name)
290
353
 
354
+ # Look for a single exact match, a prefix with '*' match or prefix+suffix
355
+
291
356
  destination = None
292
357
  for destinations in (location, wild_prefix_location, wild_location):
293
358
  if len(destinations) == 1:
294
359
  destination = destinations
295
360
  break
296
- elif len(destinations) > 1:
361
+
362
+ if len(destinations) > 1:
297
363
  destination = destinations
298
364
 
299
365
  if not destination:
@@ -308,6 +374,51 @@ def mg_dir(args, config, console):
308
374
 
309
375
  ################################################################################
310
376
 
377
+ def mg_tag(args, config, console):
378
+ """Apply a configuration tag"""
379
+
380
+ _ = console
381
+
382
+ if len(args.parameters) > 1:
383
+ error('The +tag command takes no more than one parameter')
384
+
385
+ for repo in select_git_repos(args, config):
386
+ try:
387
+ tags = repo.get('tags').split(',')
388
+ except AttributeError:
389
+ tags = []
390
+
391
+ if args.parameters:
392
+ if args.parameters[0] not in tags:
393
+ tags.append(args.parameters[0])
394
+ repo['tags'] = ','.join(tags)
395
+ args.config_modified = True
396
+ elif tags:
397
+ colour.write(f'[BOLD:{repo["repo name"]}] - {", ".join(tags)}')
398
+
399
+ ################################################################################
400
+
401
+ def mg_untag(args, config, console):
402
+ """Remove a configuration tag"""
403
+
404
+ _ = console
405
+
406
+ if len(args.parameters) > 1:
407
+ error('The +tag command takes no more than one parameter')
408
+
409
+ for repo in select_git_repos(args, config):
410
+ try:
411
+ tags = repo.get('tags', '').split(',')
412
+ except AttributeError:
413
+ tags = []
414
+
415
+ if args.parameters[0] in tags:
416
+ tags.remove(args.parameters[0])
417
+ repo['tags'] = ','.join(tags)
418
+ args.config_modified = True
419
+
420
+ ################################################################################
421
+
311
422
  def mg_config(args, config, console):
312
423
  """Output the path to the configuration file"""
313
424
 
@@ -359,19 +470,13 @@ def parse_command_line():
359
470
  for c in arg[1:]:
360
471
  argv.append('-' + c)
361
472
 
362
- # Currently doesn't handle single letter options in concatenated form - e.g. -dv
473
+ # Parse the command line
363
474
 
364
475
  i = 1
365
476
  while i < len(argv):
366
477
  param = argv[i]
367
478
 
368
- if param in ('--dryrun', '--dry-run', '-D'):
369
- args.dryrun = True
370
-
371
- elif param in ('--debug', '-d'):
372
- args.debug = True
373
-
374
- elif param in ('--verbose', '-v'):
479
+ if param in ('--verbose', '-v'):
375
480
  args.verbose = True
376
481
 
377
482
  elif param in ('--quiet', '-q'):
@@ -380,7 +485,7 @@ def parse_command_line():
380
485
  elif param in ('--config', '-c'):
381
486
  try:
382
487
  i += 1
383
- args.configuration_file = argv[i]
488
+ args.default_configuration_file = argv[i]
384
489
  except IndexError:
385
490
  error('--config - missing configuration file parameter')
386
491
 
@@ -391,6 +496,13 @@ def parse_command_line():
391
496
  except IndexError:
392
497
  error('--repos - missing repo parameter')
393
498
 
499
+ elif param in ('--tag', '-t'):
500
+ try:
501
+ i += 1
502
+ args.tag.append(argv[i])
503
+ except IndexError:
504
+ error('--tag - missing tag parameter')
505
+
394
506
  elif param in ('--modified', '-m'):
395
507
  args.modified = True
396
508
 
@@ -427,35 +539,41 @@ def parse_command_line():
427
539
 
428
540
  args.parameters = argv[i+1:]
429
541
 
430
- args.configuration_file = find_configuration(args)
542
+ args.configuration_file = find_configuration(args.default_configuration_file)
431
543
 
432
544
  return args
433
545
 
434
546
  ################################################################################
435
547
 
548
+ COMMANDS = {
549
+ 'init': mg_init,
550
+ 'dir': mg_dir,
551
+ 'config': mg_config,
552
+ 'tag': mg_tag,
553
+ 'untag': mg_untag,
554
+ }
555
+
436
556
  def main():
437
557
  """Main function"""
438
558
 
439
- commands = {
440
- 'init': mg_init,
441
- 'dir': mg_dir,
442
- 'config': mg_config,
443
- }
444
-
445
559
  args = parse_command_line()
446
560
 
447
- if args.internal_command and args.command not in commands:
561
+ if args.internal_command and args.command not in COMMANDS:
448
562
  error(f'Invalid command "{args.command}"')
449
563
 
450
564
  # If the configuration file exists, read it
451
565
 
452
566
  config = configparser.ConfigParser()
453
567
 
454
- if not (args.internal_command and args.command == 'init'):
455
- if not args.configuration_file:
568
+ # If running the '+init' command without an existing configuration file
569
+ # use the default one (which may have been overridden on the command line)
570
+ # Otherwise, fail if we can't find the configuration file.
571
+
572
+ if not args.configuration_file:
573
+ if args.internal_command and args.command == 'init':
574
+ args.configuration_file = args.default_configuration_file
575
+ else:
456
576
  error('Cannot locate configuration file')
457
- elif not os.path.isfile(args.configuration_file):
458
- error(f'Cannot read configuration file {args.configuration_file}')
459
577
 
460
578
  if os.path.isfile(args.configuration_file):
461
579
  config.read(args.configuration_file)
@@ -474,11 +592,11 @@ def main():
474
592
  if args.internal_command:
475
593
  # Run the subcommand
476
594
 
477
- commands[args.command](args, config, console)
595
+ COMMANDS[args.command](args, config, console)
478
596
 
479
597
  # Save the updated configuration file if it has changed (currently, only the init command will do this).
480
598
 
481
- if config and args.command == 'init':
599
+ if config and args.config_modified:
482
600
  with open(args.configuration_file, 'w', encoding='utf8') as configfile:
483
601
  config.write(configfile)
484
602
 
@@ -1106,10 +1106,6 @@ def default_branch():
1106
1106
 
1107
1107
  return None
1108
1108
 
1109
- ################################################################################
1110
-
1111
- return None
1112
-
1113
1109
  ################################################################################
1114
1110
 
1115
1111
  def matching_branch(branchname, case=False):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: skilleter_thingy
3
- Version: 0.0.95
3
+ Version: 0.0.97
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
@@ -36,43 +36,59 @@ This README just contains a summary of the functionality of each command.
36
36
 
37
37
  # Git Repo Management
38
38
 
39
- ## multigit
39
+ ## Multigit
40
40
 
41
- Manage a collection of related git working trees.
41
+ Multigit is a tool for managing a collection of related git working trees.
42
42
 
43
- This is intended for use in a situation where you have a collection of related git working trees organised in a directory hierarchy and not necessarily managed using git submodules or any other tool. It allows you to run git commands on multiple working trees at once, without navigating around the different working trees to do so.
43
+ This is intended for use in a situation where you have a collection of related git working trees organised in a directory hierarchy which aren't managed using git submodules or any other tool. It allows you to run git commands on multiple working trees at once, without navigating around the different working trees to do so and to select which working trees commands are run in.
44
+
45
+ ## Initialisation
44
46
 
45
47
  Start by running ensuring that the default branch (e.g. `main`) is checked out in each of the working trees and, in the top-level directory, run `multigit init` to create the configuration file which, by default is called `multigit.toml` - this is just a text file that sets the configuration for each working tree in terms of name, origin, default branch and location.
46
48
 
49
+ ## Multigit Command Line
50
+
47
51
  The multigit command line format is:
48
52
 
49
53
  multigit OPTIONS COMMAND
50
54
 
51
- Where COMMAND is an internal multigit command if it starts with a '+' and is a git command otherwise.
55
+ Where COMMAND is an internal multigit command if it starts with a `+` and is a git command otherwise (including the additional git commands described in this document).
52
56
 
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.
57
+ 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
58
 
55
59
  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
60
 
57
- *--repos / -r* Allows a list of working trees to be specfied, either as the full or relative path, the name or a wildcard.
61
+ `--repos REPO` / `-r REPO` Allows a list of working trees to be specfied, either by path, name or a wildcard matching either.
62
+
63
+ `--modified` / `-m` Run only in working trees containing locally modified files
64
+
65
+ `--branched` / `-b` Run only in working trees where the current branch that is checked out is NOT the default branch
58
66
 
59
- *--modified / -m* Run only in working trees containing locally modified files
67
+ `--tag TAG` / `-t TAG` Run only in working trees that are tagged with the specified tag
60
68
 
61
- *--branched / -b* Run only working trees where the current branch that is checked out is NOT the default branch
69
+ 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`.
70
+
71
+ Multigit tags are stored in the configuration file, not within the working tree and each working tree can have multiple tags.
72
+
73
+ ## Multigit Commands
62
74
 
63
75
  Multigit supports a small list of subcommands, each of which are prefixed with a `+` to distinguish them from Git commands:
64
76
 
65
- *+init* - Create or update the configuration file
77
+ `+init` - Create or update the configuration file
78
+
79
+ `+dir` - Given the name of a working tree, output the location within the multigit tree of that working tree if the name matches uniquely, or the name of the directory where the multigit configuration file resides if no parameter is specified.
66
80
 
67
- *+dir* - Given the name of a working tree, output the location within the multigit tree of that working tree if the name matches uniquely, or the name of the directory where the multigit configuration file resides if no parameter is specified.
81
+ `+config` - Print the name and location of the multigit configuration file.
68
82
 
69
- *+config* - Print the name and location of the multigit configuration file.
83
+ `+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.
70
84
 
71
- Any command not prefixed with '+' is run in each of the working trees (filtered by the various multigit options) as a git command.
85
+ `+untag TAG` - Remove the tag from the specified working trees (do nothing if the tag is not applied in the first place).
86
+
87
+ Any command *not* prefixed with `+` is run in each of the working trees (filtered by the various multigit options) as a git command.
72
88
 
73
89
  For example; `multigit -m commit -ab` would run `git commit -a` in each of the working trees that is branched and contains modified files.
74
90
 
75
- The `+dir` command can be used with shell aliases (or their equivalent in the user's shell of choice) to create an alias to run, for example, `cd (multigit +dir "$@")` (Bash) or `cd (multigit +dir $argv)` (for the Fish shell) that would cd to the top level direcory.
91
+ The `+dir` command can be used with shell aliases (or their equivalent in the user's shell of choice) to create an alias to run, for example, `cd (multigit +dir "$@")` (Bash) or `cd (multigit +dir $argv)` (for the Fish shell) that would cd to the top level directory.
76
92
 
77
93
  # Miscellaneous Git Utilities
78
94
 
@@ -82,23 +98,96 @@ Output a string containing colour-coded shell nesting level, current directory a
82
98
 
83
99
  # Git Extensions
84
100
 
85
- Due to the way that the git command works, these can be run as they were additional git subcommands.
101
+ Due to the way that the git command works, these can be run as they were additional git subcommands, although, due to a limitation in git, the only things that does not work is the `--help` option where the command has to be run with a hyphen between `git` and the subcommand - for example `git ca --help` does not work, but `git-ca --help` does.
102
+
103
+ ## Branch Names
104
+
105
+ Where one of the git extensions takes an existing branch name as a parameter, the branch name can be abbreviated and the abbreviated form is expanded according to the following rules:
106
+
107
+ * If the specified branch name exactly matches an existing branch, tag or commit ID then that is used (this includes remote branches where appropriate, if no local branches match).
108
+ * Otherwise, the branch name is compared to existing branches (again, including remote branches where appropriate, if no local branches match) and, if the specified name uniquely partially matches an existing branch (optionally using `*` and `?` wildcard characters) that branch is used. If it matches multiple branches than an error is reported.
109
+
110
+ For example, given a repo with the following branches:
111
+
112
+ origin/wombat
113
+ origin/platypus
114
+ wombat
115
+ emu
116
+ battery
117
+ chaos
118
+
119
+ Then:
120
+
121
+ * 'emu' will match 'emu'
122
+ * 'wombat' will match 'wombat' but not 'origin/wombat' since the local branch takes precedence
123
+ * 'at' will match both 'wombat' and 'battery' and will report an error
124
+ * 'pus' will match 'origin/platypus'
125
+
126
+ This is most useful where branches contain ticket numbers so, for instance given a branch called `feature/SKIL-103` you can check it out using `git co 103` assuming no other local branches contain `103` in their name.
127
+
128
+ Note that the concept of the default branch `DEFAULT` mentioned above *only* applies when using the `multigit` command, although some of the commands will treat branches called `master` or `main` as special cases (see the individual command documentation).
86
129
 
87
130
  ## git ca
88
131
 
89
132
  Improved version of 'git commit --amend'. Updates files that are already in the commit and, optionally, adds and commits additional files.
90
133
 
134
+ usage: git-ca [-h] [--added] [--all] [--everything] [--ignored] [--patch] [--verbose] [--dry-run] [files ...]
135
+
136
+ positional arguments:
137
+ files List of files to add to the commit
138
+
139
+ options:
140
+ -h, --help show this help message and exit
141
+ --added, -A Update files in the current commit, including files added with `git add`
142
+ --all, -a Append all locally-modified, tracked files to the current commit
143
+ --everything, -e Append all modified and untracked files to the current commit (implies `~--all`)
144
+ --ignored, -i Include files normally hidden by `.gitignore`
145
+ --patch, -p Use the interactive patch selection interface to chose which changes to commit.
146
+ --verbose, -v Verbose mode
147
+ --dry-run, -D Dry-run
148
+
91
149
  ## git cleanup
92
150
 
93
151
  List or delete branches that have already been merged and delete tracking branches that are no longer on the remote.
94
152
 
153
+ git-cleanup [-h] [--delete] [--master MASTER] [--force] [--unmerged] [--yes] [--debug] [branches ...]
154
+
155
+ positional arguments:
156
+
157
+ branches List of branches to check (default is all branches)
158
+
159
+ options:
160
+ -h, --help show this help message and exit
161
+ --delete, -d Delete all branches that have been merged
162
+ --master MASTER, -m MASTER, --main MASTER
163
+ Specify the master branch (Attempts to read this from GitLab or defaults to "develop" if present or "master" or "main" otherwise
164
+ --force, -f Allow protected branches (e.g. master) to be removed
165
+ --unmerged, -u List branches that have NOT been merged
166
+ --yes, -y Assume "yes" in response to any prompts (e.g. to delete branches)
167
+ --debug Enable debug output
168
+
95
169
  ## git co
96
170
 
97
- Equivalent to 'git checkout' but with intelligent branch matching, so specifying a partial branch name will work if it uniquely matches an existing branch
171
+ Equivalent to `git checkout` but with enhanced branch matching as described above. The command does not support the full range of options supported by the `git checkout` comamnd which should still be used where additional functionality is required.
172
+
173
+ git-co [-h] [--branch] [--update] [--rebase] [--force] [--exact] [--debug] branchname
174
+
175
+ positional arguments:
176
+
177
+ branchname The branch name (or a partial name that matches uniquely against a local branch, remote branch, commit ID or tag)
178
+
179
+ options:
180
+ -h, --help show this help message and exit
181
+ --branch, -b Create the specified branch
182
+ --update, -u If a remote branch exists, delete any local branch and check out the remote version
183
+ --rebase, -r Rebase the branch onto its parent after checking it out
184
+ --force, -f When using the update option, recreate the local branch even if it is owned by the current user (based on the author of the most recent commit)
185
+ --exact, -e Do not use branch name matching - check out the branch as specified (if it exists)
186
+ --debug Enable debug output
98
187
 
99
188
  ## git parent
100
189
 
101
- Attempt to determine the parent branch for the specified branch (defaulting to the current one)
190
+ Attempt to determine the parent branch for the specified branch (defaulting to the current one).
102
191
 
103
192
  ## git update
104
193
 
@@ -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=4NoT1rtsj2rO8MzilOGwb4dRh_gBZzVWVs5NqEVTMPQ,17885
28
+ skilleter_thingy/multigit.py,sha256=xcCG5M-lgeF1LFLvHAyIjEpXYa3vLZREladpFEVdMRA,22121
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
@@ -52,7 +52,7 @@ skilleter_thingy/thingy/dircolors.py,sha256=5NbXMsGWdABLvvZfB70VPmN6N5HyyihfpgoQ
52
52
  skilleter_thingy/thingy/docker.py,sha256=9EFatudoVPfB1UbDEtzdJDB3o6ToHiNHv8-oLsUeqiQ,2449
53
53
  skilleter_thingy/thingy/files.py,sha256=oW6E6WWwVFSUPdrZnKMx7P_w_hh3etjoN7RrqvYHCHc,4705
54
54
  skilleter_thingy/thingy/git.py,sha256=qXWIduF4jbP5pKFYt_hW9Ex5iL9mSBBrcNKBkULhRTg,38834
55
- skilleter_thingy/thingy/git2.py,sha256=WfX85zWz-0h3FiP1ME5VJ-OUBVMX9Vr2JGo401jk9oc,37156
55
+ skilleter_thingy/thingy/git2.py,sha256=9BOBfQs1WNmrJy5MvPbPwJME1XwyMMq_2hyFzXzPU5c,37057
56
56
  skilleter_thingy/thingy/gitlab.py,sha256=uXAF918xnPk6qQyiwPQDbMZfqtJzhiRqDS7yEtJEIAg,6079
57
57
  skilleter_thingy/thingy/path.py,sha256=8uM2Q9zFRWv_SaVOX49PeecQXttl7J6lsmBuRXWsXKY,4732
58
58
  skilleter_thingy/thingy/popup.py,sha256=jW-nbpdeswqEMTli7OmBv1J8XQsvFoMI0J33O6dOeu8,2529
@@ -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.95.dist-info/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
65
- skilleter_thingy-0.0.95.dist-info/METADATA,sha256=lPNdi82HQgWSYHv74vHD2EYglAYtSOiQCtyQ4DXhz10,8894
66
- skilleter_thingy-0.0.95.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
67
- skilleter_thingy-0.0.95.dist-info/entry_points.txt,sha256=u5ymS-KPljIGTnprV5yJsAjz7qgeT2BZ-Qo_Con_PFM,2145
68
- skilleter_thingy-0.0.95.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
69
- skilleter_thingy-0.0.95.dist-info/RECORD,,
64
+ skilleter_thingy-0.0.97.dist-info/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
65
+ skilleter_thingy-0.0.97.dist-info/METADATA,sha256=MkSkIQGlsrgKS3NvYtPwA2u8rAQAtr-uo9FkdyM48ps,14528
66
+ skilleter_thingy-0.0.97.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
67
+ skilleter_thingy-0.0.97.dist-info/entry_points.txt,sha256=u5ymS-KPljIGTnprV5yJsAjz7qgeT2BZ-Qo_Con_PFM,2145
68
+ skilleter_thingy-0.0.97.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
69
+ skilleter_thingy-0.0.97.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5