skilleter-thingy 0.3.14__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.
Files changed (39) hide show
  1. skilleter_thingy/__init__.py +0 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/console_colours.py +63 -0
  4. skilleter_thingy/ffind.py +535 -0
  5. skilleter_thingy/ggit.py +88 -0
  6. skilleter_thingy/ggrep.py +155 -0
  7. skilleter_thingy/git_br.py +186 -0
  8. skilleter_thingy/git_ca.py +147 -0
  9. skilleter_thingy/git_cleanup.py +297 -0
  10. skilleter_thingy/git_co.py +227 -0
  11. skilleter_thingy/git_common.py +68 -0
  12. skilleter_thingy/git_hold.py +162 -0
  13. skilleter_thingy/git_parent.py +84 -0
  14. skilleter_thingy/git_retag.py +67 -0
  15. skilleter_thingy/git_review.py +1450 -0
  16. skilleter_thingy/git_update.py +398 -0
  17. skilleter_thingy/git_wt.py +72 -0
  18. skilleter_thingy/gitcmp_helper.py +328 -0
  19. skilleter_thingy/gitprompt.py +293 -0
  20. skilleter_thingy/linecount.py +154 -0
  21. skilleter_thingy/multigit.py +915 -0
  22. skilleter_thingy/py_audit.py +133 -0
  23. skilleter_thingy/remdir.py +127 -0
  24. skilleter_thingy/rpylint.py +98 -0
  25. skilleter_thingy/strreplace.py +82 -0
  26. skilleter_thingy/test.py +34 -0
  27. skilleter_thingy/tfm.py +948 -0
  28. skilleter_thingy/tfparse.py +101 -0
  29. skilleter_thingy/trimpath.py +82 -0
  30. skilleter_thingy/venv_create.py +47 -0
  31. skilleter_thingy/venv_template.py +47 -0
  32. skilleter_thingy/xchmod.py +124 -0
  33. skilleter_thingy/yamlcheck.py +89 -0
  34. skilleter_thingy-0.3.14.dist-info/METADATA +606 -0
  35. skilleter_thingy-0.3.14.dist-info/RECORD +39 -0
  36. skilleter_thingy-0.3.14.dist-info/WHEEL +5 -0
  37. skilleter_thingy-0.3.14.dist-info/entry_points.txt +31 -0
  38. skilleter_thingy-0.3.14.dist-info/licenses/LICENSE +619 -0
  39. skilleter_thingy-0.3.14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,398 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """Thingy 'git-update' command - update the repo and rebase one more branches
5
+ against their parent branch, if this can be unambiguously determined.
6
+
7
+ Author: John Skilleter
8
+
9
+ Licence: GPL v3 or later
10
+
11
+ TODO: This is a partial solution - to do things properly, we'd have to work
12
+ out the branch tree structure, start at the bottom, pull and/or rebase
13
+ each one working upwards.
14
+
15
+ As it is, I'm assuming that we don't have a tree, but a bush, with
16
+ most things branched off a main, master or develop branch, so we pull
17
+ fixed branches first then rebase everything in no particular order.
18
+
19
+ TODO: Avoid lots of pulls - should be able to fetch then updated each local branch.
20
+ TODO: Add option to specify name of master branch or additional names to add to the list
21
+ TODO: Config entry for regularly-ignored branches
22
+ TODO: Config entry for branches that shouldn't be rebased (main, master, release/*)
23
+ TODO: Command line option when using -a to skip working trees that are modified
24
+ """
25
+ ################################################################################
26
+
27
+ import os
28
+ import sys
29
+ import argparse
30
+ import fnmatch
31
+ import logging
32
+
33
+ from skilleter_modules import git
34
+ from skilleter_modules import colour
35
+
36
+ ################################################################################
37
+
38
+ def parse_command_line():
39
+ """Parse the command line"""
40
+
41
+ parser = argparse.ArgumentParser(description='Rebase branch(es) against their parent branch, updating both in the process')
42
+
43
+ parser.add_argument('--cleanup', '-c', action='store_true',
44
+ help='After updating a branch, delete it if there are no differences between it and its parent branch')
45
+ parser.add_argument('--all', '-a', action='store_true', help='Update all local branches, not just the current one')
46
+ parser.add_argument('--everything', '-A', action='store_true',
47
+ help='Update all local branches, not just the current one and ignore the default ignore list specified in the Git configuration')
48
+ parser.add_argument('--default', '-d', action='store_true', help='Checkout the main or master branch on completion')
49
+ parser.add_argument('--parent', '-p', action='store', help='Specify the parent branch, rather than trying to work it out')
50
+ parser.add_argument('--all-parents', '-P', action='store_true',
51
+ help='Feature branches are not considered as alternative parents unless this option is specified')
52
+ parser.add_argument('--stop', '-s', action='store_true', help='Stop if a rebase problem occurs, instead of skipping the branch')
53
+ parser.add_argument('--ignore', '-i', action='store', default=None,
54
+ help='List of one or more wildcard branch names not to attempt to update (uses update.ignore from the Git configuration if not specified)')
55
+ parser.add_argument('--verbose', action='store_true', help='Enable verbose output')
56
+ parser.add_argument('--dry-run', action='store_true', help='Report what would be done without actually doing it')
57
+
58
+ return parser.parse_args()
59
+
60
+ ################################################################################
61
+
62
+ class UpdateFailure(Exception):
63
+ """Exception raised when a branch fails to update"""
64
+
65
+ def __init__(self, branchname: str) -> None:
66
+ self.branchname = branchname
67
+ super().__init__()
68
+
69
+ ################################################################################
70
+
71
+ def branch_rebase(args, results, branch):
72
+ """Attempt to rebase a branch"""
73
+
74
+ # Either use the specified parent or try to determine the parent branch
75
+
76
+ if args.parent:
77
+ parent = args.parent
78
+ else:
79
+ try:
80
+ git.checkout(branch)
81
+ except git.GitError as exc:
82
+ colour.error(exc.msg)
83
+ sys.exit(1)
84
+
85
+ # Ignore feature branches as potential alternative parents unless told otherwise
86
+ # If they are the only possible parent(s) then we still consider them.
87
+
88
+ if args.all_parents:
89
+ parents, _ = git.parents()
90
+ else:
91
+ parents, _ = git.parents(ignore='feature/*')
92
+
93
+ logging.debug('Probable parents of %s: %s', branch, parents)
94
+
95
+ if not parents:
96
+ parents, _ = git.parents()
97
+
98
+ logging.debug('No non-feature-branch parents found for %s. Feature branch parents could be: %s', branch, parents)
99
+
100
+ if not parents:
101
+ colour.write(f'[RED:WARNING]: Unable to rebase [BLUE:{branch}] branch as unable to determine its parent (no obvious candidates))', indent=4)
102
+ results['failed'].add(branch)
103
+ return
104
+
105
+ # Cheat - if we have multiple possible parents and one is 'develop', 'main' or 'master'
106
+ # choose it.
107
+
108
+ if len(parents) > 1:
109
+ if 'master' in parents:
110
+ parent = 'master'
111
+ if 'main' in parents:
112
+ parent = 'main'
113
+ elif 'develop' in parents:
114
+ parent = 'develop'
115
+ elif 'scv-poc' in parents:
116
+ parent = 'scv-poc'
117
+ else:
118
+ colour.write(f'[RED:WARNING]: Unable to rebase [BLUE:{branch}] branch as unable to determine its parent (could be any of {", ".join(parents)})',
119
+ indent=4)
120
+ results['failed'].add(branch)
121
+ return
122
+
123
+ elif len(parents) == 1:
124
+ parent = parents[0]
125
+
126
+ if args.dry_run:
127
+ colour.write(f'[BOLD:Checking if] [BLUE:{branch}] [BOLD:needs to be rebased onto] [BLUE:{parent}]', indent=4)
128
+
129
+ else:
130
+ if parent not in results['pulled'] and parent not in results['unchanged']:
131
+ colour.write(f'[BOLD:Updating] [BLUE:{parent}]')
132
+
133
+ if not branch_pull(args, results, parent):
134
+ return
135
+
136
+ if branch not in results['pulled']:
137
+ if git.iscommit(branch, remote_only=True):
138
+ colour.write(f'[BOLD:Updating] [BLUE:{branch}]')
139
+
140
+ branch_pull(args, results, branch)
141
+ else:
142
+ results['no-tracking'].add(branch)
143
+
144
+ if git.rebase_required(branch, parent):
145
+ colour.write(f'Rebasing [BLUE:{branch}] [BOLD:onto] [BLUE:{parent}]', indent=4)
146
+
147
+ git.checkout(branch)
148
+ output, status = git.rebase(parent)
149
+
150
+ if status:
151
+ colour.write(f'[RED:WARNING]: Unable to rebase [BLUE:{branch}] onto [BLUE:{parent}]', indent=4)
152
+
153
+ if args.verbose:
154
+ colour.write(output)
155
+
156
+ results['failed'].add(branch)
157
+
158
+ if args.stop:
159
+ raise UpdateFailure(branch)
160
+
161
+ git.abort_rebase()
162
+ return
163
+
164
+ results['rebased'].add(branch)
165
+ else:
166
+ colour.write(f'[BLUE:{branch}] is already up-to-date on parent branch [BLUE:{parent}]', indent=4)
167
+
168
+ results['unchanged'].add(branch)
169
+
170
+ if args.cleanup:
171
+ if args.dry_run:
172
+ colour.write(f'[GREEN:Dry-run: Checking to see if {branch} and {parent} are the same - deleting {branch} if they are]', indent=4)
173
+
174
+ elif git.diff_status(branch, parent):
175
+ git.checkout(parent)
176
+ git.delete_branch(branch, force=True)
177
+
178
+ results['deleted'].add(branch)
179
+
180
+ colour.write(f'Deleted branch [BLUE:{branch}] as it is not different to its parent branch ([BLUE:{parent}])', indent=4)
181
+
182
+ ################################################################################
183
+
184
+ def branch_pull(args, results, branch, fail=True):
185
+ """Attempt to update a branch, logging any failure except no remote tracking branch
186
+ unless fail is False"""
187
+
188
+ colour.write(f'Pulling updates for the [BLUE:{branch}] branch', indent=4)
189
+
190
+ if not args.dry_run:
191
+ if branch not in results['pulled'] and branch not in results['unchanged']:
192
+ try:
193
+ git.checkout(branch)
194
+ output = git.pull()
195
+
196
+ colour.write(output, indent=4)
197
+
198
+ if output[0] == 'Already up-to-date.':
199
+ results['unchanged'].add(branch)
200
+
201
+ results['pulled'].add(branch)
202
+
203
+ except git.GitError as exc:
204
+ if exc.msg.startswith('There is no tracking information for the current branch.'):
205
+ colour.write(f'[RED:WARNING]: There is no tracking information for the [BLUE:{branch}] branch.', indent=4)
206
+ fail = False
207
+
208
+ elif exc.msg.startswith('Your configuration specifies to merge with the ref'):
209
+ colour.write('[RED:WARNING]: The upstream branch no longer exists', indent=4)
210
+ fail = False
211
+
212
+ elif 'no such ref was fetched' in exc.msg:
213
+ colour.write(f'[RED:WARNING]: {exc.msg}', indent=4)
214
+
215
+ else:
216
+ colour.write(f'[RED:ERROR]: Unable to merge upstream changes onto [BLUE:{branch}] branch.', indent=4)
217
+
218
+ if git.merging():
219
+ git.abort_merge()
220
+ elif git.rebasing():
221
+ git.abort_rebase()
222
+
223
+ if fail:
224
+ results['failed'].add(branch)
225
+
226
+ return False
227
+
228
+ return True
229
+
230
+ ################################################################################
231
+
232
+ def fixed_branch(branch):
233
+ """Return True if a branch is 'fixed' (master, develop, release, etc.)
234
+ and shouldn't be rebased automatically"""
235
+
236
+ return branch.startswith(('release/', 'hotfix/')) or \
237
+ branch in ('master', 'main', 'develop') or \
238
+ '/PoC-' in branch
239
+
240
+ ################################################################################
241
+
242
+ def report_branches(msg, branches):
243
+ """Report a list of branches with a message"""
244
+
245
+ colour.write(newline=True)
246
+ colour.write(msg)
247
+
248
+ for branch in branches:
249
+ colour.write(f'[BLUE:{branch}]', indent=4)
250
+
251
+ ################################################################################
252
+
253
+ def main():
254
+ """Entry point"""
255
+
256
+ # Handle the command line
257
+
258
+ args = parse_command_line()
259
+
260
+ # Enable logging if requested
261
+
262
+ if args.verbose:
263
+ logging.basicConfig(level=logging.DEBUG)
264
+
265
+ # Check we are in the right place
266
+
267
+ if not git.working_tree():
268
+ colour.error('Not in a git repo')
269
+
270
+ # Set the default ignore list if none specified and if not using the '-A' option
271
+
272
+ if args.ignore is None and not args.everything:
273
+ args.ignore = git.config_get('update', 'ignore')
274
+
275
+ args.ignore = args.ignore.split(',') if args.ignore else []
276
+
277
+ logging.info('Ignore list: %s', ', '.join(args.ignore))
278
+
279
+ # Make sure we've got no locally-modified files
280
+
281
+ status = git.status_info(ignored=True)
282
+
283
+ for entry in status:
284
+ if status[entry][1] == 'M':
285
+ colour.error('You have unstaged changes - cannot update.')
286
+
287
+ # Get the current branch
288
+
289
+ current_branch = git.branch()
290
+
291
+ if not current_branch:
292
+ colour.error('No branch currently checked out - cannot update.')
293
+
294
+ colour.write(f'[BOLD:Current branch:] [BLUE:{current_branch}]')
295
+
296
+ # Switch the current directory in case it vanishes when we switch branches
297
+
298
+ os.chdir(git.working_tree())
299
+
300
+ # Optionally pull or rebase everything - pull things first, then rebase
301
+ # the rest.
302
+
303
+ branches = git.branches() if args.all or args.everything else [current_branch]
304
+
305
+ logging.info('Updating %s', ', '.join(branches))
306
+
307
+ # Filter out branches that the user wants to ignore
308
+
309
+ if args.ignore:
310
+ for ignore in args.ignore:
311
+ for name in branches[:]:
312
+ if fnmatch.fnmatch(name, ignore) and name in branches:
313
+ branches.remove(name)
314
+
315
+ if not branches:
316
+ colour.error('No matching branches to update')
317
+
318
+ # List of stuff that's been done, to report in the summary
319
+
320
+ results = {'deleted': set(), 'pulled': set(), 'failed': set(), 'rebased': set(), 'unchanged': set(), 'no-tracking': set()}
321
+
322
+ to_rebase = set()
323
+
324
+ try:
325
+ for branch in branches:
326
+ if fixed_branch(branch):
327
+ branch_pull(args, results, branch)
328
+ else:
329
+ to_rebase.add(branch)
330
+
331
+ for branch in to_rebase:
332
+ branch_rebase(args, results, branch)
333
+
334
+ # Return to the original branch if it still exists or the master
335
+
336
+ all_branches = git.branches()
337
+
338
+ return_branch = current_branch if current_branch in all_branches \
339
+ else 'develop' if 'develop' in all_branches \
340
+ else 'main' if 'main' in all_branches \
341
+ else 'master' if 'master' in all_branches else None
342
+
343
+ if return_branch:
344
+ colour.write('')
345
+ colour.write(f'[BOLD]Checking out the [BLUE:{return_branch}] [BOLD:branch]')
346
+
347
+ if not args.dry_run:
348
+ git.checkout(return_branch)
349
+
350
+ except UpdateFailure as exc:
351
+ update_failed = exc.branchname
352
+
353
+ else:
354
+ update_failed = None
355
+
356
+ for entry in ('rebased', 'unchanged', 'pulled', 'failed', 'no-tracking'):
357
+ results[entry] -= results['deleted']
358
+
359
+ results['pulled'] -= results['unchanged']
360
+
361
+ if results['rebased']:
362
+ report_branches('[BOLD:The following branches have been rebased:]', results['rebased'])
363
+
364
+ if results['unchanged']:
365
+ report_branches('[BOLD:The following branches were already up-to-date:]', results['unchanged'])
366
+
367
+ if results['pulled']:
368
+ report_branches('[BOLD:The following branches have been updated:]', results['pulled'])
369
+
370
+ if results['deleted']:
371
+ report_branches('[BOLD:The following branches have been deleted:]', results['deleted'])
372
+
373
+ if results['failed']:
374
+ report_branches('[RED:WARNING:] [BOLD:The following branches failed to update:]', results['failed'])
375
+
376
+ if results['no-tracking']:
377
+ report_branches('[YELLOW:NOTE:] [BOLD:The following branches have been rebased, but no upstream branch exists]', results['no-tracking'])
378
+
379
+ if update_failed:
380
+ colour.write('')
381
+ colour.write(f'Halted during failed rebase of branch [BLUE:{update_failed}]')
382
+
383
+ ################################################################################
384
+
385
+ def git_update():
386
+ """Entry point"""
387
+
388
+ try:
389
+ main()
390
+ except KeyboardInterrupt:
391
+ sys.exit(1)
392
+ except BrokenPipeError:
393
+ sys.exit(2)
394
+
395
+ ################################################################################
396
+
397
+ if __name__ == '__main__':
398
+ git_update()
@@ -0,0 +1,72 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Output the top level directory of the git working tree or return
5
+ an error if we are not in a git working tree.
6
+
7
+ Copyright (C) 2017, 2018 John Skilleter
8
+
9
+ Licence: GPL v3 or later
10
+ """
11
+ ################################################################################
12
+
13
+ import sys
14
+ import argparse
15
+ import os
16
+
17
+ from skilleter_modules import git
18
+ from skilleter_modules import colour
19
+
20
+ ################################################################################
21
+
22
+ def main():
23
+ """ Main function """
24
+
25
+ # Command line parameters
26
+
27
+ parser = argparse.ArgumentParser(description='Report top-level directory of the current git working tree.')
28
+ parser.add_argument('--parent', '-p', action='store_true',
29
+ help='If we are already at the top of the working tree, check if the parent directory is in a working tree and output the top-level directory of that tree.')
30
+ parser.add_argument('--dir', '-d', action='store', default=None,
31
+ help='Find the location of the top-level directory in the working tree starting at the specified directory')
32
+
33
+ args = parser.parse_args()
34
+
35
+ try:
36
+ start_dir = os.path.abspath(args.dir or os.getcwd())
37
+ except FileNotFoundError:
38
+ sys.stderr.write('Unable to determine initial directory\n')
39
+ sys.exit(1)
40
+
41
+ # Search for a .git directory in the current or parent directories
42
+
43
+ working_tree = git.working_tree(start_dir)
44
+
45
+ # If we are in a working tree and also looking for the parent working
46
+ # tree, check if we are at the top of the current tree, and, if so,
47
+ # hop up a level and try again.
48
+
49
+ if args.parent and working_tree == start_dir:
50
+ working_tree = git.working_tree(os.path.join(working_tree, os.pardir))
51
+
52
+ if working_tree:
53
+ print(working_tree)
54
+
55
+ ################################################################################
56
+
57
+ def git_wt():
58
+ """Entry point"""
59
+
60
+ try:
61
+ main()
62
+ except KeyboardInterrupt:
63
+ sys.exit(1)
64
+ except BrokenPipeError:
65
+ sys.exit(2)
66
+ except git.GitError as exc:
67
+ colour.error(exc.msg, status=exc.status, prefix=True)
68
+
69
+ ################################################################################
70
+
71
+ if __name__ == '__main__':
72
+ git_wt()