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

Files changed (68) hide show
  1. skilleter_thingy/__init__.py +6 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/borger.py +269 -0
  4. skilleter_thingy/console_colours.py +63 -0
  5. skilleter_thingy/diskspacecheck.py +67 -0
  6. skilleter_thingy/docker_purge.py +113 -0
  7. skilleter_thingy/ffind.py +536 -0
  8. skilleter_thingy/ggit.py +90 -0
  9. skilleter_thingy/ggrep.py +154 -0
  10. skilleter_thingy/git_br.py +180 -0
  11. skilleter_thingy/git_ca.py +142 -0
  12. skilleter_thingy/git_cleanup.py +287 -0
  13. skilleter_thingy/git_co.py +220 -0
  14. skilleter_thingy/git_common.py +61 -0
  15. skilleter_thingy/git_hold.py +154 -0
  16. skilleter_thingy/git_mr.py +92 -0
  17. skilleter_thingy/git_parent.py +77 -0
  18. skilleter_thingy/git_review.py +1428 -0
  19. skilleter_thingy/git_update.py +385 -0
  20. skilleter_thingy/git_wt.py +96 -0
  21. skilleter_thingy/gitcmp_helper.py +322 -0
  22. skilleter_thingy/gitprompt.py +274 -0
  23. skilleter_thingy/gl.py +174 -0
  24. skilleter_thingy/gphotosync.py +610 -0
  25. skilleter_thingy/linecount.py +155 -0
  26. skilleter_thingy/moviemover.py +133 -0
  27. skilleter_thingy/photodupe.py +136 -0
  28. skilleter_thingy/phototidier.py +248 -0
  29. skilleter_thingy/py_audit.py +131 -0
  30. skilleter_thingy/readable.py +270 -0
  31. skilleter_thingy/remdir.py +126 -0
  32. skilleter_thingy/rmdupe.py +550 -0
  33. skilleter_thingy/rpylint.py +91 -0
  34. skilleter_thingy/splitpics.py +99 -0
  35. skilleter_thingy/strreplace.py +82 -0
  36. skilleter_thingy/sysmon.py +435 -0
  37. skilleter_thingy/tfm.py +920 -0
  38. skilleter_thingy/tfparse.py +101 -0
  39. skilleter_thingy/thingy/__init__.py +6 -0
  40. skilleter_thingy/thingy/colour.py +213 -0
  41. skilleter_thingy/thingy/dc_curses.py +278 -0
  42. skilleter_thingy/thingy/dc_defaults.py +221 -0
  43. skilleter_thingy/thingy/dc_util.py +50 -0
  44. skilleter_thingy/thingy/dircolors.py +308 -0
  45. skilleter_thingy/thingy/docker.py +95 -0
  46. skilleter_thingy/thingy/files.py +142 -0
  47. skilleter_thingy/thingy/git.py +1371 -0
  48. skilleter_thingy/thingy/git2.py +1307 -0
  49. skilleter_thingy/thingy/gitlab.py +193 -0
  50. skilleter_thingy/thingy/logger.py +112 -0
  51. skilleter_thingy/thingy/path.py +156 -0
  52. skilleter_thingy/thingy/popup.py +87 -0
  53. skilleter_thingy/thingy/process.py +112 -0
  54. skilleter_thingy/thingy/run.py +334 -0
  55. skilleter_thingy/thingy/tfm_pane.py +595 -0
  56. skilleter_thingy/thingy/tidy.py +160 -0
  57. skilleter_thingy/trimpath.py +84 -0
  58. skilleter_thingy/window_rename.py +92 -0
  59. skilleter_thingy/xchmod.py +125 -0
  60. skilleter_thingy/yamlcheck.py +89 -0
  61. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/METADATA +5 -1
  62. skilleter_thingy-0.0.42.dist-info/RECORD +66 -0
  63. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/entry_points.txt +1 -0
  64. skilleter_thingy-0.0.42.dist-info/top_level.txt +1 -0
  65. skilleter_thingy-0.0.40.dist-info/RECORD +0 -6
  66. skilleter_thingy-0.0.40.dist-info/top_level.txt +0 -1
  67. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/LICENSE +0 -0
  68. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/WHEEL +0 -0
@@ -0,0 +1,322 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Script invoked via the 'git cmp' alias, which sets GIT_EXTERNAL_DIFF so that
5
+ this is called instead of using git diff.
6
+
7
+ Copyright (C) 2017-18 John Skilleter
8
+
9
+ Parameters passed by git are:
10
+
11
+ For a normal diff between two versions of a file in Git:
12
+
13
+ N MEANING EXAMPLE
14
+ 1 file path tgy_git.py
15
+
16
+ 2 tmp old file /tmp/OjfLZ8_tgy_git.py
17
+ 3 old SHA1 027422b8b6e945227b27abf4161ad38c5b6e9ff9
18
+ 4 old perm 100644
19
+
20
+ 5 tmp new file tgy_git.py
21
+ 6 new SHA1 0000000000000000000000000000000000000000
22
+ 7 new perm 100644
23
+
24
+ If the path is unmerged, only parameter #1 is passed
25
+
26
+ Also sets:
27
+
28
+ GIT_DIFF_PATH_COUNTER - incremented for each file compared
29
+ GIT_DIFF_PATH_TOTAL - total number of paths to be compared
30
+
31
+ If GIT_SUBDIR is set in the environment it indicates the relative
32
+ path of the current directory from the top-level directory of the
33
+ working tree.
34
+ """
35
+ ################################################################################
36
+
37
+ ################################################################################
38
+ # Imports
39
+
40
+ import sys
41
+ import os
42
+ import argparse
43
+ import filecmp
44
+ import re
45
+
46
+ import thingy.logger as logger
47
+ import thingy.colour as colour
48
+ import thingy.run as run
49
+ import thingy.files as files
50
+ import thingy.git as git
51
+ import thingy.dircolors as dircolors
52
+
53
+ ################################################################################
54
+ # Constants
55
+
56
+ # A file must be at least this size to be considered binary - if it is smaller
57
+ # we give it the benefit of the doubt.
58
+
59
+ MIN_BINARY_SIZE = 8
60
+
61
+ ################################################################################
62
+
63
+ def report_permissions(perm):
64
+ """ Convert an octal value in a string to a description of file permissions
65
+ e.g. given '644' it will return 'rw-r--r--' """
66
+
67
+ mask_chars = ('r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x')
68
+
69
+ # Convert the permissions from an octal string to an integer
70
+
71
+ permissions = int(perm, 8)
72
+
73
+ # Start at the topmost bit and work downwards adding the mask character
74
+ # for bits that are set and '-' for ones that aren't.
75
+
76
+ mask = 1 << (len(mask_chars) - 1)
77
+
78
+ permtext = []
79
+
80
+ for mask_char in mask_chars:
81
+ permtext.append(mask_char if permissions & mask else '-')
82
+ mask >>= 1
83
+
84
+ return '%s' % ''.join(permtext)
85
+
86
+ ################################################################################
87
+
88
+ def main():
89
+ """ Main function - does everything """
90
+
91
+ # Set up logging
92
+
93
+ log = logger.init('gitcmp')
94
+
95
+ # Allow the log level to be configured in git config, as well as via the
96
+ # GITCMP_DEBUG
97
+
98
+ txt_debug = git.config_get('cmp', 'debug')
99
+ env_debug = os.getenv('GITCMP_DEBUG', '0')
100
+
101
+ if txt_debug.lower() in ('true', '1') or env_debug.lower() in ('true', '1'):
102
+ log.setLevel(logger.DEBUG)
103
+
104
+ # Parse the command line
105
+
106
+ parser = argparse.ArgumentParser(description='Invoked via the "git cmp" alias. Works as an enhanced version of "git difftool"')
107
+
108
+ parser.add_argument('file_path', nargs='?', help='File name and path')
109
+
110
+ parser.add_argument('old_file', nargs='?', help='Name of temporary copy of old version')
111
+ parser.add_argument('old_sha1', nargs='?', help='SHA1 of the old version')
112
+ parser.add_argument('old_perm', nargs='?', help='Permissions for the old version')
113
+
114
+ parser.add_argument('new_file', nargs='?', help='Name of temporary copy of the new version')
115
+ parser.add_argument('new_sha1', nargs='?', help='SHA1 of the new version')
116
+ parser.add_argument('new_perm', nargs='?', help='Permissions for the new version')
117
+
118
+ parser.add_argument('new_name', nargs='?', help='New name (if file has been renamed)')
119
+ parser.add_argument('rename', nargs='?', help='Description of rename')
120
+
121
+ args = parser.parse_args()
122
+
123
+ # Get configuration from the environment
124
+
125
+ path_count = int(os.getenv('GIT_DIFF_PATH_COUNTER', '0'))
126
+ path_total = int(os.getenv('GIT_DIFF_PATH_TOTAL', '0'))
127
+ diff_binaries = int(os.getenv('GIT_DIFF_BINARIES', '0'))
128
+ skip_deleted = int(os.getenv('GIT_IGNORE_DELETED', '0'))
129
+
130
+ # Debug output
131
+
132
+ log.info('Parameters to gitcmp-helper:')
133
+ log.info('1: path: %s', args.file_path)
134
+ log.info('2: old file: %s', args.old_file)
135
+ log.info('3: old sha1: %s', args.old_sha1)
136
+ log.info('4: old perm: %s', args.old_perm)
137
+ log.info('5: new file: %s', args.new_file)
138
+ log.info('6: new sha1: %s', args.new_sha1)
139
+ log.info('7: new perm: %s', args.new_perm)
140
+ log.info('8: new name: %s', args.new_name)
141
+ log.info('9: rename : %s', args.rename)
142
+ log.info('path count: %d/%d', path_count, path_total)
143
+
144
+ # Sanity checks
145
+
146
+ if args.file_path is None:
147
+ sys.stderr.write('At least one parameter must be specified\n')
148
+ sys.exit(1)
149
+
150
+ # Check and handle for the simple case of an unmerged file
151
+
152
+ if args.old_file is None:
153
+ colour.write('[CYAN:%s] is not merged' % args.file_path)
154
+ sys.exit(0)
155
+
156
+ # Make sure that we have all the expected parameters
157
+
158
+ if args.new_perm is None:
159
+ sys.stderr.write('Either 1 or 7 parameters must be specifed\n')
160
+ sys.exit(1)
161
+
162
+ # Make sure we can access the temporary files supplied
163
+
164
+ if not os.access(args.old_file, os.R_OK):
165
+ sys.stderr.write('Unable to read temporary old file: %s\n' % args.old_file)
166
+ sys.exit(2)
167
+
168
+ if not os.access(args.new_file, os.R_OK):
169
+ sys.stderr.write('Unable to read temporary new file: %s\n' % args.new_file)
170
+ sys.exit(2)
171
+
172
+ dc = dircolors.Dircolors()
173
+
174
+ # Determine the best way of reporting the path to the file
175
+
176
+ working_tree_path = os.getcwd()
177
+ current_path = os.path.join(working_tree_path, os.getenv('GIT_SUBDIR', ''))
178
+
179
+ current_file_path = os.path.relpath(args.file_path if args.new_name is None else args.new_name, current_path)
180
+
181
+ log.info('file path: %s', current_file_path)
182
+
183
+ # Heading printed first
184
+
185
+ heading = ['[BOLD]Changes in [NORMAL]%s' % dc.format(current_file_path)]
186
+
187
+ # If file was renamed, append the old name and the degree of similarity
188
+
189
+ if args.new_name:
190
+ similarity = re.sub(r'(similarity index) (.*)', r'\1 [CYAN:\2]', args.rename.split('\n')[0])
191
+
192
+ heading.append('(rename from %s with %s)' % (dc.format(os.path.relpath(args.file_path, current_path)), similarity))
193
+
194
+ # If processing more than one file, append he index and total number of files
195
+
196
+ if path_total > 0:
197
+ heading.append('(%d/%d)' % (path_count, path_total))
198
+
199
+ # Check for newly created/deleted files (other version will be '/dev/null')
200
+
201
+ created_file = (args.old_file == '/dev/null')
202
+ deleted_file = (args.new_file == '/dev/null')
203
+
204
+ if created_file:
205
+ heading.append('(new file)')
206
+
207
+ if deleted_file:
208
+ heading.append('(deleted file)')
209
+
210
+ colour.write(' '.join(heading))
211
+
212
+ # Report permission(s) / permissions changes
213
+
214
+ permissions_changed = not (created_file or deleted_file) and args.old_perm != args.new_perm
215
+
216
+ if deleted_file:
217
+ colour.write(' Old permissions: [CYAN:%s]' % report_permissions(args.old_perm))
218
+ elif created_file:
219
+ colour.write(' New permissions: [CYAN:%s]' % report_permissions(args.new_perm))
220
+ elif permissions_changed:
221
+ colour.write(' Changed permissions: [CYAN:%s] -> [CYAN:%s]' % (report_permissions(args.old_perm), report_permissions(args.new_perm)))
222
+ else:
223
+ colour.write(' Permissions: [CYAN:%s]' % report_permissions(args.new_perm))
224
+
225
+ # Report size changes
226
+
227
+ old_size = os.stat(args.old_file).st_size
228
+ new_size = os.stat(args.new_file).st_size
229
+
230
+ formatted_old_size = files.format_size(old_size, always_suffix=True)
231
+ formatted_new_size = files.format_size(new_size, always_suffix=True)
232
+
233
+ if created_file:
234
+ colour.write(' New size: [CYAN:%s]' % formatted_new_size)
235
+ elif deleted_file:
236
+ colour.write(' Original size: [CYAN:%s]' % formatted_old_size)
237
+ elif new_size == old_size:
238
+ colour.write(' Size: [CYAN]%s[NORMAL] (no change)' % formatted_new_size)
239
+ else:
240
+ formatted_delta_size = files.format_size(abs(new_size - old_size), always_suffix=True)
241
+
242
+ delta = '%s %s' % (formatted_delta_size, 'larger' if new_size > old_size else 'smaller')
243
+
244
+ if formatted_old_size == formatted_new_size:
245
+ colour.write(' Size: [CYAN:%s] (%s)' % (formatted_new_size, delta))
246
+ else:
247
+ colour.write(' Size: [CYAN:%s] -> [CYAN:%s] (%s)' %
248
+ (formatted_old_size, formatted_new_size, delta))
249
+
250
+ # Report file type
251
+
252
+ if created_file:
253
+ old_type = None
254
+ else:
255
+ old_type = files.file_type(args.old_file)
256
+
257
+ if deleted_file:
258
+ new_type = None
259
+ else:
260
+ new_type = files.file_type(args.new_file)
261
+
262
+ if created_file:
263
+ colour.write(' File type: [CYAN:%s]' % new_type)
264
+ elif deleted_file:
265
+ colour.write(' Original file type: [CYAN:%s]' % old_type)
266
+ elif old_type != new_type:
267
+ colour.write(' File type: [CYAN:%s] (previously [CYAN:%s)]' % (new_type, old_type))
268
+ else:
269
+ colour.write(' File type: [CYAN:%s]' % new_type)
270
+
271
+ # Report permissions and type
272
+
273
+ if filecmp.cmp(args.old_file, args.new_file, shallow=False):
274
+ # If the file is unchanged, just report the permissions change (if any)
275
+
276
+ if permissions_changed:
277
+ colour.write(' Revisions are identical with changes to permissions')
278
+ else:
279
+ colour.write(' Revisions are identical')
280
+ else:
281
+ # Check if the file is/was a binary
282
+
283
+ old_binary = not created_file and old_size > MIN_BINARY_SIZE and files.is_binary_file(args.old_file)
284
+ new_binary = not deleted_file and new_size > MIN_BINARY_SIZE and files.is_binary_file(args.new_file)
285
+
286
+ # If both versions are binary and we're not risking diffing binaries, report it
287
+ # otherwise, issue a warning if one version is binary then do the diff
288
+
289
+ if (old_binary or new_binary) and not diff_binaries:
290
+ colour.write(' Cannot diff binary files')
291
+ else:
292
+ difftool = git.config_get('cmp', 'difftool', defaultvalue='diffuse')
293
+
294
+ if old_binary or new_binary:
295
+ colour.write(' [BOLD:WARNING]: One or both files may be binaries')
296
+
297
+ if not deleted_file or not skip_deleted:
298
+ try:
299
+ run.run([difftool, args.old_file, args.new_file])
300
+ except run.RunError as exc:
301
+ print('Diff failed: %s' % exc.msg)
302
+
303
+ # Separate reports with a blank line
304
+
305
+ print('')
306
+
307
+ ################################################################################
308
+
309
+ def gitcmp_helper():
310
+ """Entry point"""
311
+
312
+ try:
313
+ main()
314
+ except KeyboardInterrupt:
315
+ sys.exit(1)
316
+ except BrokenPipeError:
317
+ sys.exit(2)
318
+
319
+ ################################################################################
320
+
321
+ if __name__ == '__main__':
322
+ gitcmp_helper()
@@ -0,0 +1,274 @@
1
+ #! /usr/bin/env python3
2
+
3
+ """ Thingy gitprompt command
4
+
5
+ Copyright (C) 2017 John Skilleter
6
+
7
+ Used to create the portion of the shell prompt that optionally shows
8
+ the current git repo name and branch and to output a colour code indicating
9
+ the status of the current working tree.
10
+
11
+ Normally used in the shell setup scripts (e.g. ~/.bashrc) as:
12
+
13
+ export PS1=$(gitprompt OPTIONS)
14
+
15
+ Command line options:
16
+
17
+ '--colour'
18
+
19
+ Output a background colour code indicating the status of the
20
+ current tree, rather than the repo name and branch.
21
+
22
+ Colours used are:
23
+
24
+ Green - Clean repo, no local changes
25
+ Cyan - Clean repo with untracked file(s)
26
+ Yellow - Uncommitted local changes (added, copied or renamed files)
27
+ Red - Local changes that have not been added (files modified or deleted)
28
+ Magenta - Unmerged files
29
+
30
+ Other options are set via the Git configuration:
31
+
32
+ prompt.prefix: 0 - No prefix
33
+ 1 - Single letter indications of git status (untracked, modified, etc)
34
+ 2 - One word indications (untracked, modified, etc)
35
+
36
+ TODO: Limit the total prompt length more 'intelligently', rather than just bits of it.
37
+ TODO: Indicate whether current directory is writeable and/or put current owner in prompt if no the current user
38
+ """
39
+
40
+ ################################################################################
41
+
42
+ # Try and reduce the scope for an auto-repeating ^C to screw up the shell prompt
43
+
44
+ import signal
45
+
46
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
47
+
48
+ import os
49
+ import sys
50
+ import argparse
51
+
52
+ import thingy.git as git
53
+ import thingy.colour as colour
54
+
55
+ ################################################################################
56
+ # Constants
57
+
58
+ # Prefix text used when showing git status in the prompt - first entry is the
59
+ # abbreviated form and the second is the verbose.
60
+
61
+ STATUS_PREFIX = \
62
+ {
63
+ 'untracked': ('u', 'untracked'),
64
+ 'added': ('A', 'added'),
65
+ 'modified': ('M', 'modified'),
66
+ 'unmerged': ('U', 'unmerged'),
67
+ 'deleted': ('D', 'deleted'),
68
+ 'copied': ('C', 'copied'),
69
+ 'renamed': ('R', 'renamed'),
70
+ 'stash': ('S', 'stashed')
71
+ }
72
+
73
+ MAX_BRANCH_NAME_LENGTH = int(os.environ.get('COLUMNS', 96)) / 3
74
+
75
+ ################################################################################
76
+
77
+ def write_colour_prompt(gitstate: dict):
78
+ """ Output the colour for the prompt, according to the current state of the
79
+ working tree. """
80
+
81
+ colour.write('[NORMAL]', newline=False)
82
+
83
+ if gitstate['modified'] or gitstate['deleted']:
84
+ colour.write('[REVERSE][RED]', newline=False)
85
+
86
+ elif gitstate['added'] or gitstate['copied'] or gitstate['renamed']:
87
+ colour.write('[REVERSE][YELLOW]', newline=False)
88
+
89
+ elif gitstate['unmerged']:
90
+ colour.write('[REVERSE][MAGENTA]', newline=False)
91
+
92
+ elif gitstate['untracked']:
93
+ colour.write('[REVERSE][CYAN]', newline=False)
94
+
95
+ elif gitstate['merging'] or gitstate['bisecting'] or gitstate['rebasing']:
96
+ colour.write('[REVERSE][BLACK]', newline=False)
97
+
98
+ else:
99
+ colour.write('[REVERSE][GREEN]', newline=False)
100
+
101
+ ################################################################################
102
+
103
+ def write_prompt_prefix(gitstate: dict):
104
+ """ Build a prompt prefix containing the type and number
105
+ of changes in the repo. """
106
+
107
+ prefix = []
108
+
109
+ # Get the status configuration from gitconfig and restrict it to the
110
+ # range 0..2
111
+
112
+ try:
113
+ status_prefix = int(git.config_get('prompt', 'prefix', defaultvalue='0'))
114
+ except ValueError:
115
+ status_prefix = 0
116
+ else:
117
+ status_prefix = max(min(status_prefix, 2), 0)
118
+
119
+ # Only output the status information if the prefix is non-zero
120
+
121
+ if status_prefix > 0:
122
+ i = status_prefix - 1
123
+
124
+ stashed = len(git.stash())
125
+
126
+ if stashed:
127
+ prefix.append('%s:%d' % (STATUS_PREFIX['stash'][i], stashed))
128
+
129
+ if gitstate['untracked']:
130
+ prefix.append('%s:%d' % (STATUS_PREFIX['untracked'][i], gitstate['untracked']))
131
+
132
+ if gitstate['added']:
133
+ prefix.append('%s:%d' % (STATUS_PREFIX['added'][i], gitstate['added']))
134
+
135
+ if gitstate['modified']:
136
+ prefix.append('%s:%d' % (STATUS_PREFIX['modified'][i], gitstate['modified']))
137
+
138
+ if gitstate['unmerged']:
139
+ prefix.append('%s:%d' % (STATUS_PREFIX['unmerged'][i], gitstate['unmerged']))
140
+
141
+ if gitstate['deleted']:
142
+ prefix.append('%s:%d' % (STATUS_PREFIX['deleted'][i], gitstate['deleted']))
143
+
144
+ if gitstate['copied']:
145
+ prefix.append('%s:%d' % (STATUS_PREFIX['copied'][i], gitstate['copied']))
146
+
147
+ if gitstate['renamed']:
148
+ prefix.append('%s:%d' % (STATUS_PREFIX['renamed'][i], gitstate['renamed']))
149
+
150
+ # Get the current branch, tag or commit
151
+
152
+ branch = git.branch() or git.tag() or git.current_commit(short=True)
153
+
154
+ # TODO: More intelligent branch name pruning - currently just trims it if longer than limit
155
+ # TODO: Keep branch name up to minimum characters long - use more components if still shorter
156
+
157
+ if len(branch) > MAX_BRANCH_NAME_LENGTH:
158
+ truncated_name = None
159
+
160
+ for sep in (' ', '-', '_', '/'):
161
+ shortname = sep.join(branch.split(sep)[0:2])
162
+ if (truncated_name and len(truncated_name) > len(shortname)) or not truncated_name:
163
+ truncated_name = shortname
164
+
165
+ if truncated_name:
166
+ branch = '%s...' % truncated_name
167
+
168
+ if gitstate['rebasing']:
169
+ prefix.append('(rebasing)')
170
+ elif gitstate['bisecting']:
171
+ prefix.append('(bisecting)')
172
+ elif gitstate['merging']:
173
+ prefix.append('(merging)')
174
+
175
+ if prefix:
176
+ sys.stdout.write(' {%s}' % ' '.join(prefix))
177
+
178
+ project = git.project(short=True)
179
+
180
+ if project:
181
+ sys.stdout.write(f' {project}: {branch} ')
182
+ else:
183
+ sys.stdout.write(f' {branch} ')
184
+
185
+ ################################################################################
186
+
187
+ def git_status(colour_output: str, prompt_output: str):
188
+ """ Catalogue the current state of the working tree then call the function
189
+ to either output a suitable colour or the prompt text or both """
190
+
191
+ # Get the working tree, just return if there's an error
192
+
193
+ try:
194
+ working_tree = git.working_tree()
195
+ except git.GitError:
196
+ return
197
+
198
+ # Return if we are not in a working tree
199
+
200
+ if not working_tree:
201
+ return
202
+
203
+ # gitstate contains counters for numbers of modified (etc.) elements in the tree and flags to indicate
204
+ # whether we're currently in a merge/rebase/bisect state.
205
+
206
+ gitstate = {'modified': 0, 'added': 0, 'untracked': 0, 'unmerged': 0, 'deleted': 0, 'renamed': 0, 'copied': 0}
207
+
208
+ # Set flags if we are currently merging/rebasing/bisecting and get the current status
209
+
210
+ try:
211
+ gitstate['merging'] = git.merging()
212
+ gitstate['rebasing'] = git.rebasing()
213
+ gitstate['bisecting'] = git.bisecting()
214
+
215
+ status = git.status()
216
+ except git.GitError as exc:
217
+ # Major failure of gitness - report the error and quit
218
+
219
+ if colour_output:
220
+ colour.write('[WHITE][BRED]', newline=False)
221
+ else:
222
+ colour.write(' ERROR: %s ' % exc.msg.split('\n')[0], newline=False)
223
+
224
+ return
225
+
226
+ # Count the number of files in each state
227
+
228
+ for st in status:
229
+ gitstate['untracked'] += '?' in st[0]
230
+ gitstate['added'] += 'A' in st[0]
231
+ gitstate['modified'] += 'M' in st[0]
232
+ gitstate['unmerged'] += 'U' in st[0]
233
+ gitstate['deleted'] += 'D' in st[0]
234
+ gitstate['copied'] += 'C' in st[0]
235
+ gitstate['renamed'] += 'R' in st[0]
236
+
237
+ # Set the output colour or output the prompt prefix
238
+
239
+ if colour_output:
240
+ write_colour_prompt(gitstate)
241
+
242
+ if prompt_output or not colour_output:
243
+ write_prompt_prefix(gitstate)
244
+
245
+ ################################################################################
246
+
247
+ def main():
248
+ """ Parse the command line and output colour or status """
249
+
250
+ parser = argparse.ArgumentParser(description='Report current branch and, optionally, git repo name to be embedded in shell prompt')
251
+ parser.add_argument('--colour', action='store_true', help='Output colour code indicating working tree status')
252
+ parser.add_argument('--prompt', action='store_true', help='Output the prompt (default if --colour not specified)')
253
+
254
+ args = parser.parse_args()
255
+
256
+ git_status(args.colour, args.prompt)
257
+
258
+ ################################################################################
259
+
260
+ def gitprompt():
261
+ """Entry point"""
262
+
263
+ try:
264
+ main()
265
+
266
+ except KeyboardInterrupt:
267
+ sys.exit(1)
268
+ except BrokenPipeError:
269
+ sys.exit(2)
270
+
271
+ ################################################################################
272
+
273
+ if __name__ == '__main__':
274
+ gitprompt()