skilleter-thingy 0.0.88__py3-none-any.whl → 0.0.90__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 +209 -437
- skilleter_thingy/thingy/git2.py +7 -3
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/METADATA +15 -28
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/RECORD +8 -8
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/LICENSE +0 -0
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/entry_points.txt +0 -0
- {skilleter_thingy-0.0.88.dist-info → skilleter_thingy-0.0.90.dist-info}/top_level.txt +0 -0
skilleter_thingy/multigit.py
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
import argparse
|
|
8
7
|
import fnmatch
|
|
9
8
|
import configparser
|
|
10
|
-
|
|
11
|
-
from
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
12
11
|
|
|
13
12
|
import thingy.git2 as git
|
|
14
13
|
import thingy.colour as colour
|
|
@@ -21,14 +20,14 @@ import thingy.colour as colour
|
|
|
21
20
|
# DONE: Use the configuration file
|
|
22
21
|
# DONE: init function
|
|
23
22
|
# TODO: -j option to run in parallel?
|
|
24
|
-
#
|
|
25
|
-
#
|
|
23
|
+
# NOPE: Pull/fetch - only output after running command and only if something updated
|
|
24
|
+
# DONE: Better error-handling - e.g. continue/abort option after failure in one repo
|
|
26
25
|
# TODO: Consistent colours in output
|
|
27
26
|
# TODO: Dry-run option
|
|
28
27
|
# 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
|
|
29
28
|
# TODO: If run in a subdirectory, only process repos in that tree (or have an option to do so)
|
|
30
29
|
# TODO: Is it going to be a problem if the same repo is checked out twice or more in the same workspace
|
|
31
|
-
#
|
|
30
|
+
# NOPE: Switch to tomlkit
|
|
32
31
|
# TODO: Verbose option
|
|
33
32
|
# TODO: When specifying list of repos, if repo name doesn't contain '/' prefix it with '*'?
|
|
34
33
|
|
|
@@ -43,14 +42,87 @@ DEFAULT_BRANCH = 'DEFAULT'
|
|
|
43
42
|
|
|
44
43
|
################################################################################
|
|
45
44
|
|
|
45
|
+
HELP_INFO = """usage: multigit [-h] [--dryrun] [--debug] [--verbose] [--quiet] [--config CONFIG] [--directory DIRECTORY] [--repos REPOS] [--modified] [--branched]
|
|
46
|
+
{+init, +config, +dir, GIT_COMMAND} ...
|
|
47
|
+
|
|
48
|
+
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
|
|
49
|
+
|
|
50
|
+
options:
|
|
51
|
+
-h, --help show this help message and exit
|
|
52
|
+
--dryrun, --dry-run, -D
|
|
53
|
+
Dry-run comands
|
|
54
|
+
--debug, -d Debug
|
|
55
|
+
--verbose, -v Verbosity to the maximum
|
|
56
|
+
--quiet, -q Minimal console output
|
|
57
|
+
--config CONFIG, -c CONFIG
|
|
58
|
+
The configuration file (defaults to multigit.toml)
|
|
59
|
+
--directory DIRECTORY, --dir DIRECTORY
|
|
60
|
+
The top-level directory of the multigit tree (defaults to the current directory)
|
|
61
|
+
--repos REPOS, -r REPOS
|
|
62
|
+
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)
|
|
63
|
+
--modified, -m Select repos that have local modifications
|
|
64
|
+
--branched, -b Select repos that do not have the default branch checked out
|
|
65
|
+
--continue, -C Continue if a git command returns an error (by default, executation terminates when a command fails)
|
|
66
|
+
|
|
67
|
+
Sub-commands:
|
|
68
|
+
{+init,+dir,+config,GIT_COMMAND}
|
|
69
|
+
+init Build or update the configuration file using the current branch in each repo as the default branch
|
|
70
|
+
+config Return the name and location of the configuration file
|
|
71
|
+
+dir Return the location of a working tree, given the repo name, or if no parameter specified, the root directory of the multigit tree
|
|
72
|
+
GIT_COMMAND Any git command, including options and parameters - this is then run in all specified working trees
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
################################################################################
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class Arguments():
|
|
80
|
+
"""Data class to contain command line options and parameters"""
|
|
81
|
+
|
|
82
|
+
dryrun: bool = False
|
|
83
|
+
debug: bool = False
|
|
84
|
+
quiet: bool = False
|
|
85
|
+
verbose: bool = False
|
|
86
|
+
configuration_file: str = DEFAULT_CONFIG_FILE
|
|
87
|
+
directory: str = '.'
|
|
88
|
+
repos: list[str] = field(default_factory=list)
|
|
89
|
+
modified: bool = False
|
|
90
|
+
branched: bool = False
|
|
91
|
+
command: str = None
|
|
92
|
+
error_continue: bool = False
|
|
93
|
+
parameters: list[str] = field(default_factory=list)
|
|
94
|
+
internal_command: bool = False
|
|
95
|
+
|
|
96
|
+
################################################################################
|
|
97
|
+
|
|
46
98
|
def error(msg, status=1):
|
|
47
99
|
"""Quit with an error"""
|
|
48
100
|
|
|
49
|
-
|
|
101
|
+
colour.write(f'[RED:ERROR:] {msg}\n', stream=sys.stderr)
|
|
50
102
|
sys.exit(status)
|
|
51
103
|
|
|
52
104
|
################################################################################
|
|
53
105
|
|
|
106
|
+
def find_configuration(args):
|
|
107
|
+
"""If the configuration file name has path elements, try and read it, otherwise
|
|
108
|
+
search up the directory tree looking for the configuration file.
|
|
109
|
+
Returns configuration file path or None if the configuration file
|
|
110
|
+
could not be found."""
|
|
111
|
+
|
|
112
|
+
if '/' in args.configuration_file:
|
|
113
|
+
config_file = args.configuration_file
|
|
114
|
+
else:
|
|
115
|
+
config_path = os.getcwd()
|
|
116
|
+
config_file = os.path.join(config_path, args.configuration_file)
|
|
117
|
+
|
|
118
|
+
while not os.path.isfile(config_file) and config_path != '/':
|
|
119
|
+
config_path = os.path.dirname(config_path)
|
|
120
|
+
config_file = os.path.join(config_path, args.configuration_file)
|
|
121
|
+
|
|
122
|
+
return config_file if os.path.isfile(config_file) else None
|
|
123
|
+
|
|
124
|
+
################################################################################
|
|
125
|
+
|
|
54
126
|
def show_progress(width, msg):
|
|
55
127
|
"""Show a single line progress message"""
|
|
56
128
|
|
|
@@ -76,7 +148,7 @@ def find_git_repos(args):
|
|
|
76
148
|
|
|
77
149
|
repos = set()
|
|
78
150
|
|
|
79
|
-
for root, dirs, _ in os.walk(args.
|
|
151
|
+
for root, dirs, _ in os.walk(os.path.dirname(args.configuration_file)):
|
|
80
152
|
if '.git' in dirs:
|
|
81
153
|
if root.startswith('./'):
|
|
82
154
|
root = root[2:]
|
|
@@ -147,21 +219,6 @@ def branch_name(name, default_branch):
|
|
|
147
219
|
|
|
148
220
|
################################################################################
|
|
149
221
|
|
|
150
|
-
def run_git_status(cmd, path, cont=False, redirect=True):
|
|
151
|
-
"""Run a git command and exit if it fails"""
|
|
152
|
-
|
|
153
|
-
output, status = git.git_run_status(cmd, path=path, redirect=redirect)
|
|
154
|
-
|
|
155
|
-
if output:
|
|
156
|
-
colour.write(f'[BOLD:{path}]')
|
|
157
|
-
colour.write()
|
|
158
|
-
colour.write(output, indent=4)
|
|
159
|
-
|
|
160
|
-
if status and not cont:
|
|
161
|
-
sys.exit(status)
|
|
162
|
-
|
|
163
|
-
################################################################################
|
|
164
|
-
|
|
165
222
|
def mg_init(args, config, console):
|
|
166
223
|
"""Create or update the configuration
|
|
167
224
|
By default, it scans the tree for git directories and adds or updates them
|
|
@@ -190,375 +247,160 @@ def mg_init(args, config, console):
|
|
|
190
247
|
|
|
191
248
|
################################################################################
|
|
192
249
|
|
|
193
|
-
def
|
|
194
|
-
"""
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
for repo in select_git_repos(args, config):
|
|
199
|
-
if not args.quiet:
|
|
200
|
-
show_progress(console.columns, repo.name)
|
|
201
|
-
|
|
202
|
-
status = git.status(path=repo.name)
|
|
203
|
-
branch = git.branch(path=repo.name)
|
|
204
|
-
|
|
205
|
-
if status or branch != repo['default branch']:
|
|
206
|
-
if branch == repo['default branch']:
|
|
207
|
-
colour.write(f'[BOLD:{repo.name}]')
|
|
208
|
-
else:
|
|
209
|
-
colour.write(f'[BOLD:{repo.name}] - branch: [BLUE:{branch}]')
|
|
210
|
-
|
|
211
|
-
staged = defaultdict(list)
|
|
212
|
-
unstaged = defaultdict(list)
|
|
213
|
-
untracked = []
|
|
214
|
-
|
|
215
|
-
for entry in status:
|
|
216
|
-
if entry[0] == '??':
|
|
217
|
-
untracked.append(entry[1])
|
|
218
|
-
elif entry[0][0] == 'M':
|
|
219
|
-
staged['Updated'].append(entry[1])
|
|
220
|
-
elif entry[0][0] == 'T':
|
|
221
|
-
staged['Type changed'].append(entry[1])
|
|
222
|
-
elif entry[0][0] == 'A':
|
|
223
|
-
staged['Added'].append(entry[1])
|
|
224
|
-
elif entry[0][0] == 'D':
|
|
225
|
-
staged['Deleted'].append(entry[1])
|
|
226
|
-
elif entry[0][0] == 'R':
|
|
227
|
-
staged['Renamed'].append(entry[1])
|
|
228
|
-
elif entry[0][0] == 'C':
|
|
229
|
-
staged['Copied'].append(entry[1])
|
|
230
|
-
elif entry[0][1] == 'M':
|
|
231
|
-
colour.write(f' WT Updated: [BLUE:{entry[1]}]')
|
|
232
|
-
elif entry[0][1] == 'T':
|
|
233
|
-
colour.write(f' WT Type changed: [BLUE:{entry[1]}]')
|
|
234
|
-
elif entry[0][1] == 'D':
|
|
235
|
-
unstaged['Deleted'].append(entry[1])
|
|
236
|
-
elif entry[0][1] == 'R':
|
|
237
|
-
colour.write(f' WT Renamed: [BLUE:{entry[1]}]')
|
|
238
|
-
elif entry[0][1] == 'C':
|
|
239
|
-
colour.write(f' WT Copied: [BLUE:{entry[1]}]')
|
|
240
|
-
else:
|
|
241
|
-
staged['Other'].append(f' {entry[0]}: [BLUE:{entry[1]}]')
|
|
242
|
-
|
|
243
|
-
if untracked:
|
|
244
|
-
colour.write()
|
|
245
|
-
colour.write('Untracked files:')
|
|
246
|
-
|
|
247
|
-
for git_object in untracked:
|
|
248
|
-
colour.write(f' [BLUE:{git_object}]')
|
|
249
|
-
|
|
250
|
-
if staged:
|
|
251
|
-
colour.write()
|
|
252
|
-
colour.write('Changes staged for commit:')
|
|
253
|
-
|
|
254
|
-
for item in staged:
|
|
255
|
-
for git_object in staged[item]:
|
|
256
|
-
colour.write(f' {item}: [BLUE:{git_object}]')
|
|
257
|
-
|
|
258
|
-
if unstaged:
|
|
259
|
-
colour.write()
|
|
260
|
-
colour.write('Changes not staged for commit:')
|
|
261
|
-
|
|
262
|
-
for item in unstaged:
|
|
263
|
-
for git_object in unstaged[item]:
|
|
264
|
-
colour.write(f' {item}: [BLUE:{git_object}]')
|
|
265
|
-
|
|
266
|
-
colour.write()
|
|
267
|
-
|
|
268
|
-
################################################################################
|
|
250
|
+
def mg_dir(args, config, console):
|
|
251
|
+
"""Return the location of a working tree, given the name, or the root directory
|
|
252
|
+
of the tree if not
|
|
253
|
+
Returns an error unless there is a unique match"""
|
|
269
254
|
|
|
270
|
-
|
|
271
|
-
"""Run git fetch everywhere"""
|
|
255
|
+
# DONE: Should return location relative to the current directory or as absolute path
|
|
272
256
|
|
|
257
|
+
_ = console
|
|
273
258
|
_ = config
|
|
274
259
|
|
|
275
|
-
for repo in select_git_repos(args, config):
|
|
276
|
-
if not args.quiet:
|
|
277
|
-
show_progress(console.columns, repo.name)
|
|
278
260
|
|
|
279
|
-
|
|
261
|
+
if len(args.parameters) > 1:
|
|
262
|
+
error('The +dir command takes no more than one parameter - the name of the working tree to search for')
|
|
263
|
+
elif args.parameters:
|
|
264
|
+
location = []
|
|
265
|
+
search_dir = args.parameters[0]
|
|
280
266
|
|
|
281
|
-
|
|
267
|
+
for repo in select_git_repos(args, config):
|
|
268
|
+
if fnmatch.fnmatch(repo['name'], search_dir):
|
|
269
|
+
location.append(repo.name)
|
|
282
270
|
|
|
283
|
-
if
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
colour.write(f' [BLUE:{item}]')
|
|
288
|
-
else:
|
|
289
|
-
colour.write(f' {item}')
|
|
271
|
+
if len(location) == 0:
|
|
272
|
+
error(f'No matches with [BLUE:{search_dir}]')
|
|
273
|
+
elif len(location) > 1:
|
|
274
|
+
error(f'Multiple matches with [BLUE:{search_dir}] - {" ".join(location)}')
|
|
290
275
|
|
|
291
|
-
|
|
276
|
+
colour.write(os.path.join(os.path.dirname(args.configuration_file), location[0]))
|
|
277
|
+
else:
|
|
278
|
+
colour.write(os.path.dirname(args.configuration_file))
|
|
292
279
|
|
|
293
280
|
################################################################################
|
|
294
281
|
|
|
295
|
-
def
|
|
296
|
-
"""
|
|
282
|
+
def mg_config(args, config, console):
|
|
283
|
+
"""Output the path to the configuration file"""
|
|
297
284
|
|
|
298
285
|
_ = config
|
|
286
|
+
_ = console
|
|
299
287
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
show_progress(console.columns, repo.name)
|
|
303
|
-
|
|
304
|
-
colour.write(f'Pulling updates for [BLUE:{repo.name}]')
|
|
305
|
-
|
|
306
|
-
try:
|
|
307
|
-
result = git.pull(path=repo.name)
|
|
308
|
-
except git.GitError as exc:
|
|
309
|
-
error(f'Error in {repo.name}: {exc}')
|
|
310
|
-
|
|
311
|
-
if result and result[0] != 'Already up-to-date.':
|
|
312
|
-
colour.write(f'[BOLD:{repo.name}]')
|
|
313
|
-
for item in result:
|
|
314
|
-
if item.startswith('Updating'):
|
|
315
|
-
colour.write(f' [BLUE:{item}]')
|
|
316
|
-
else:
|
|
317
|
-
colour.write(f' {item}')
|
|
318
|
-
|
|
319
|
-
colour.write()
|
|
320
|
-
|
|
321
|
-
################################################################################
|
|
322
|
-
|
|
323
|
-
def mg_push(args, config, console):
|
|
324
|
-
"""Run git push everywhere where the current branch isn't one of the defaults
|
|
325
|
-
and where the most recent commit was the current user and was on the branch
|
|
326
|
-
"""
|
|
327
|
-
|
|
328
|
-
# DONE: Add option for force-push?
|
|
329
|
-
# TODO: Add option for manual confirmation?
|
|
330
|
-
|
|
331
|
-
for repo in select_git_repos(args, config):
|
|
332
|
-
if not args.quiet:
|
|
333
|
-
show_progress(console.columns, repo.name)
|
|
334
|
-
|
|
335
|
-
branch = git.branch(path=repo.name)
|
|
336
|
-
|
|
337
|
-
if branch != repo['default branch']:
|
|
338
|
-
colour.write(f'Pushing changes to [BLUE:{branch}] in [BOLD:{repo.name}]')
|
|
339
|
-
|
|
340
|
-
result = git.push(path=repo.name, force_with_lease=args.force)
|
|
341
|
-
|
|
342
|
-
if result:
|
|
343
|
-
colour.write(result, indent=4)
|
|
344
|
-
|
|
345
|
-
colour.write()
|
|
346
|
-
|
|
347
|
-
################################################################################
|
|
348
|
-
|
|
349
|
-
def mg_checkout(args, config, console):
|
|
350
|
-
"""Run git checkout everywhere.
|
|
351
|
-
By default it just checks out the specified branch (or the default branch)
|
|
352
|
-
if the branch exists in the repo.
|
|
353
|
-
If the 'create' option is specified then branch is created"""
|
|
354
|
-
|
|
355
|
-
# TODO: Add --create handling
|
|
356
|
-
# TODO: Checkout remote branches
|
|
357
|
-
# TODO: only try checkout if branch exists
|
|
358
|
-
# TODO: option to fetch before checking out
|
|
359
|
-
|
|
360
|
-
for repo in select_git_repos(args, config):
|
|
361
|
-
if not args.quiet:
|
|
362
|
-
show_progress(console.columns, repo.name)
|
|
363
|
-
|
|
364
|
-
branch = branch_name(args.branch, repo['default branch'])
|
|
365
|
-
|
|
366
|
-
if git.branch(path=repo.name) != branch:
|
|
367
|
-
colour.write(f'Checking out [BLUE:{branch}] in [BOLD:{repo.name}]')
|
|
368
|
-
|
|
369
|
-
git.checkout(branch, create=args.create, path=repo.name)
|
|
370
|
-
|
|
371
|
-
################################################################################
|
|
372
|
-
|
|
373
|
-
def mg_commit(args, config, console):
|
|
374
|
-
"""For every repo that has a branch checked out and changes present,
|
|
375
|
-
commit those changes onto the branch"""
|
|
376
|
-
|
|
377
|
-
# DONE: Option to amend the commit if it is not the first one on the current branch
|
|
378
|
-
# DONE: Prevent commits if current branch is the default branch
|
|
379
|
-
# DONE: Option to specify wildcard for files to commit (default is all files)
|
|
380
|
-
|
|
381
|
-
for repo in select_git_repos(args, config):
|
|
382
|
-
if not args.quiet:
|
|
383
|
-
show_progress(console.columns, repo.name)
|
|
384
|
-
|
|
385
|
-
branch = git.branch(path=repo.name)
|
|
386
|
-
modified = git.status(path=repo.name)
|
|
387
|
-
|
|
388
|
-
if branch != repo['default branch'] and modified:
|
|
389
|
-
colour.write(f'Committing [BOLD:{len(modified)}] changes onto [BLUE:{branch}] branch in [BOLD:{repo.name}]')
|
|
390
|
-
|
|
391
|
-
git.commit(all=True, message=args.message, path=repo.name)
|
|
392
|
-
|
|
393
|
-
################################################################################
|
|
394
|
-
|
|
395
|
-
def mg_update(args, config, console):
|
|
396
|
-
"""For every repo, pull the default branch and if the current branch
|
|
397
|
-
is not the default branch, rebase it onto the default branch"""
|
|
398
|
-
|
|
399
|
-
# TODO: Option to pull current branch
|
|
400
|
-
# TODO: Use git-update
|
|
401
|
-
# TODO: Option to delete current branch before pulling (to get updates without conflicts)
|
|
402
|
-
# TODO: Option to stash changes on current branch before updating and unstash afterwards
|
|
403
|
-
|
|
404
|
-
for repo in select_git_repos(args, config):
|
|
405
|
-
if not args.quiet:
|
|
406
|
-
show_progress(console.columns, repo.name)
|
|
407
|
-
|
|
408
|
-
branch = git.branch(path=repo.name)
|
|
409
|
-
default_branch = repo['default branch']
|
|
410
|
-
|
|
411
|
-
colour.write(f'Updating branch [BLUE:{branch}] in [BOLD:{repo.name}]')
|
|
412
|
-
|
|
413
|
-
if branch != default_branch:
|
|
414
|
-
if not args.quiet:
|
|
415
|
-
colour.write(f'Checking out [BLUE:{default_branch}]', indent=4)
|
|
416
|
-
|
|
417
|
-
git.checkout(default_branch, path=repo.name)
|
|
418
|
-
|
|
419
|
-
if not args.quiet:
|
|
420
|
-
colour.write('Pulling updates from remote', indent=4)
|
|
421
|
-
|
|
422
|
-
git.pull(path=repo.name)
|
|
423
|
-
|
|
424
|
-
if branch != default_branch:
|
|
425
|
-
if not args.quiet:
|
|
426
|
-
colour.write(f'Checking out [BLUE:{branch}] and rebasing against [BLUE:{default_branch}]', indent=4)
|
|
288
|
+
if len(args.parameters):
|
|
289
|
+
error('The +config command does not take parameters')
|
|
427
290
|
|
|
428
|
-
|
|
429
|
-
result = git.rebase(default_branch, path=repo.name)
|
|
430
|
-
colour.write(result[0], indent=4)
|
|
291
|
+
colour.write(args.configuration_file)
|
|
431
292
|
|
|
432
293
|
################################################################################
|
|
433
294
|
|
|
434
|
-
def
|
|
435
|
-
"""
|
|
295
|
+
def run_git_command(args, config, console):
|
|
296
|
+
"""Run a command in each of the working trees, optionally continuing if
|
|
297
|
+
there's an error"""
|
|
436
298
|
|
|
437
299
|
_ = config
|
|
300
|
+
_ = console
|
|
438
301
|
|
|
439
302
|
for repo in select_git_repos(args, config):
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
result = git.clean(recurse=args.recurse, force=args.force, dry_run=args.dry_run,
|
|
444
|
-
quiet=args.quiet, exclude=args.exclude, ignore_rules=args.x,
|
|
445
|
-
remove_only_ignored=args.X, path=repo.name)
|
|
446
|
-
|
|
447
|
-
first_skip = True
|
|
448
|
-
|
|
449
|
-
if result:
|
|
450
|
-
colour.write(f'[BOLD:{repo.name}]')
|
|
303
|
+
repo_command = [args.command]
|
|
304
|
+
for cmd in args.parameters:
|
|
305
|
+
repo_command.append(branch_name(cmd, repo['default branch']))
|
|
451
306
|
|
|
452
|
-
|
|
453
|
-
skipping = item.startswith('Skipping repository ')
|
|
307
|
+
colour.write(f'\n[BOLD:{repo.name}]\n')
|
|
454
308
|
|
|
455
|
-
|
|
456
|
-
if first_skip:
|
|
457
|
-
colour.write('Skipping sub-repositories', indent=4)
|
|
458
|
-
first_skip = False
|
|
459
|
-
else:
|
|
460
|
-
colour.write(item.strip(), indent=4)
|
|
309
|
+
_, status = git.git_run_status(repo_command, path=repo.name, redirect=False)
|
|
461
310
|
|
|
462
|
-
|
|
311
|
+
if status and not args.error_continue:
|
|
312
|
+
sys.exit(status)
|
|
463
313
|
|
|
464
314
|
################################################################################
|
|
465
315
|
|
|
466
|
-
def
|
|
467
|
-
"""
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
# DONE: Should return location relative to the current directory or as absolute path
|
|
471
|
-
|
|
472
|
-
_ = console
|
|
473
|
-
_ = config
|
|
316
|
+
def parse_command_line():
|
|
317
|
+
"""Manually parse the command line as we want to be able to accept 'multigit <OPTIONS> <+MULTIGITCOMMAND | ANY_GIT_COMMAND_WITH_OPTIONS>
|
|
318
|
+
and I can't see a way to get ArgumentParser to accept arbitrary command+options"""
|
|
474
319
|
|
|
475
|
-
|
|
476
|
-
search_dir = args.dir[0]
|
|
320
|
+
args = Arguments()
|
|
477
321
|
|
|
478
|
-
|
|
479
|
-
if fnmatch.fnmatch(repo['name'], search_dir):
|
|
480
|
-
location.append(repo.name)
|
|
481
|
-
|
|
482
|
-
if len(location) == 0:
|
|
483
|
-
error(f'No matches with {search_dir}')
|
|
484
|
-
elif len(location) > 1:
|
|
485
|
-
error(f'Multiple matches with {search_dir}')
|
|
486
|
-
|
|
487
|
-
colour.write(os.path.join(os.path.dirname(args.config), location[0]))
|
|
322
|
+
# Expand arguments so that, for instance '-dv' is parsed as '-d -v'
|
|
488
323
|
|
|
489
|
-
|
|
324
|
+
argv = []
|
|
490
325
|
|
|
491
|
-
|
|
492
|
-
|
|
326
|
+
for arg in sys.argv:
|
|
327
|
+
if arg[0] != '-' or arg.startswith('--'):
|
|
328
|
+
argv.append(arg)
|
|
329
|
+
else:
|
|
330
|
+
for c in arg[1:]:
|
|
331
|
+
argv.append('-' + c)
|
|
493
332
|
|
|
494
|
-
|
|
495
|
-
_ = console
|
|
333
|
+
# Currently doesn't handle single letter options in concatenated form - e.g. -dv
|
|
496
334
|
|
|
497
|
-
|
|
335
|
+
i = 1
|
|
336
|
+
while i < len(argv):
|
|
337
|
+
param = argv[i]
|
|
498
338
|
|
|
499
|
-
|
|
339
|
+
if param in ('--dryrun', '--dry-run', '-D'):
|
|
340
|
+
args.dryrun = True
|
|
500
341
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
there's an error"""
|
|
342
|
+
elif param in ('--debug', '-d'):
|
|
343
|
+
args.debug = True
|
|
504
344
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if len(args.cmd) == 0:
|
|
508
|
-
error('No command specified')
|
|
509
|
-
elif len(args.cmd) == 1:
|
|
510
|
-
command = shlex.split(args.cmd[0])
|
|
511
|
-
else:
|
|
512
|
-
command = args.cmd
|
|
345
|
+
elif param in ('--verbose', '-v'):
|
|
346
|
+
args.verbose = True
|
|
513
347
|
|
|
348
|
+
elif param in ('--quiet', '-q'):
|
|
349
|
+
args.quiet = True
|
|
514
350
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
351
|
+
elif param in ('--config', '-c'):
|
|
352
|
+
try:
|
|
353
|
+
i += 1
|
|
354
|
+
args.configuration_file = argv[i]
|
|
355
|
+
except IndexError:
|
|
356
|
+
error('--config - missing configuration file parameter')
|
|
518
357
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
358
|
+
elif param in ('--repos', '-r'):
|
|
359
|
+
try:
|
|
360
|
+
i += 1
|
|
361
|
+
args.repos.append(argv[i])
|
|
362
|
+
except IndexError:
|
|
363
|
+
error('--repos - missing repo parameter')
|
|
522
364
|
|
|
523
|
-
|
|
365
|
+
elif param in ('--modified', '-m'):
|
|
366
|
+
args.modified = True
|
|
524
367
|
|
|
525
|
-
|
|
368
|
+
elif param in ('--branched', '-b'):
|
|
369
|
+
args.branched = True
|
|
526
370
|
|
|
527
|
-
|
|
528
|
-
|
|
371
|
+
elif param in ('--continue', '-C'):
|
|
372
|
+
args.error_continue = True
|
|
529
373
|
|
|
530
|
-
|
|
374
|
+
elif param in ('--help', '-h'):
|
|
375
|
+
print(HELP_INFO)
|
|
376
|
+
sys.exit(0)
|
|
531
377
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
378
|
+
elif param[0] == '-':
|
|
379
|
+
error(f'Invalid option: "{param}"')
|
|
380
|
+
else:
|
|
381
|
+
break
|
|
535
382
|
|
|
536
|
-
|
|
537
|
-
for p in args.parameters:
|
|
538
|
-
params += shlex.split(p.replace(DEFAULT_BRANCH, repo['default branch']))
|
|
383
|
+
i += 1
|
|
539
384
|
|
|
540
|
-
|
|
541
|
-
|
|
385
|
+
# After the options, we either have a multigit command (prefixed with '+') or a git command
|
|
386
|
+
# followed by parameter
|
|
542
387
|
|
|
543
|
-
|
|
388
|
+
try:
|
|
389
|
+
if argv[i][0] == '+':
|
|
390
|
+
args.command = argv[i][1:]
|
|
391
|
+
args.internal_command = True
|
|
392
|
+
else:
|
|
393
|
+
args.command = argv[i]
|
|
394
|
+
args.internal_command = False
|
|
544
395
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
search up the directory tree looking for the configuration file.
|
|
548
|
-
Returns configuration file path or None if the configuration file
|
|
549
|
-
could not be found."""
|
|
396
|
+
except IndexError:
|
|
397
|
+
error('Missing command')
|
|
550
398
|
|
|
551
|
-
|
|
552
|
-
config_file = args.config
|
|
553
|
-
else:
|
|
554
|
-
config_path = os.getcwd()
|
|
555
|
-
config_file = os.path.join(config_path, args.config)
|
|
399
|
+
args.parameters = argv[i+1:]
|
|
556
400
|
|
|
557
|
-
|
|
558
|
-
config_path = os.path.dirname(config_path)
|
|
559
|
-
config_file = os.path.join(config_path, args.config)
|
|
401
|
+
args.configuration_file = find_configuration(args)
|
|
560
402
|
|
|
561
|
-
return
|
|
403
|
+
return args
|
|
562
404
|
|
|
563
405
|
################################################################################
|
|
564
406
|
|
|
@@ -567,108 +409,26 @@ def main():
|
|
|
567
409
|
|
|
568
410
|
commands = {
|
|
569
411
|
'init': mg_init,
|
|
570
|
-
'status': mg_status,
|
|
571
|
-
'fetch': mg_fetch,
|
|
572
|
-
'pull': mg_pull,
|
|
573
|
-
'push': mg_push,
|
|
574
|
-
'checkout': mg_checkout,
|
|
575
|
-
'commit': mg_commit,
|
|
576
|
-
'update': mg_update,
|
|
577
|
-
'clean': mg_clean,
|
|
578
412
|
'dir': mg_dir,
|
|
579
413
|
'config': mg_config,
|
|
580
|
-
'run': mg_run,
|
|
581
|
-
'review': mg_review,
|
|
582
414
|
}
|
|
583
415
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
parser = argparse.ArgumentParser(description='Run git commands in multiple Git repos. DISCLAIMER: This is beta-quality software, with missing features and liable to fail with stack dump, but shouldn\'t eat your data')
|
|
587
|
-
|
|
588
|
-
parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
|
|
589
|
-
parser.add_argument('--debug', '-d', action='store_true', help='Debug')
|
|
590
|
-
parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
|
|
591
|
-
parser.add_argument('--quiet', '-q', action='store_true', help='Minimal console output')
|
|
592
|
-
parser.add_argument('--config', '-c', action='store', default=DEFAULT_CONFIG_FILE, help=f'The configuration file (defaults to {DEFAULT_CONFIG_FILE})')
|
|
593
|
-
parser.add_argument('--directory', '--dir', action='store', default='.', help='The top-level directory of the multigit tree (defaults to the current directory)')
|
|
594
|
-
parser.add_argument('--repos', '-r', action='append', default=None, help='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)')
|
|
595
|
-
parser.add_argument('--modified', '-m', action='store_true', help='Select repos that have local modifications')
|
|
596
|
-
parser.add_argument('--branched', '-b', action='store_true', help='Select repos that do not have the default branch checked out')
|
|
597
|
-
|
|
598
|
-
subparsers = parser.add_subparsers(dest='command')
|
|
416
|
+
args = parse_command_line()
|
|
599
417
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
parser_init = subparsers.add_parser('init', help='Build or update the configuration file using the current branch in each repo as the default branch')
|
|
603
|
-
|
|
604
|
-
parser_status = subparsers.add_parser('status', help='Report git status in every repo that has something to report')
|
|
605
|
-
parser_fetch = subparsers.add_parser('fetch', help='Run git fetch in every repo')
|
|
606
|
-
parser_pull = subparsers.add_parser('pull', help='Run git pull in every repo')
|
|
607
|
-
|
|
608
|
-
parser_push = subparsers.add_parser('push', help='Run git push in every repo where the current branch isn\'t the default and the most recent commit was by the current user')
|
|
609
|
-
parser_push.add_argument('--force', '-f', action='store_true', help='Use --force-push-with-least to update a remote branch if the local branch has been rebased')
|
|
610
|
-
|
|
611
|
-
parser_checkout = subparsers.add_parser('checkout', help='Checkout the specified branch')
|
|
612
|
-
parser_checkout.add_argument('--create', '-b', action='store_true', help='Create the specified branch and check it out')
|
|
613
|
-
parser_checkout.add_argument('branch', nargs='?', default=None, action='store', help='The branch name to check out (defaults to the default branch)')
|
|
614
|
-
|
|
615
|
-
parser_commit = subparsers.add_parser('commit', help='Commit changes')
|
|
616
|
-
parser_commit.add_argument('--message', '-m', action='store', default=None, help='The commit message')
|
|
617
|
-
|
|
618
|
-
parser_update = subparsers.add_parser('update', help='Pull the default branch and if the current branch isn\'t the default branch, rebase it onto the default branch')
|
|
619
|
-
|
|
620
|
-
parser_clean = subparsers.add_parser('clean', help='Remove untracked files from the working tree')
|
|
621
|
-
|
|
622
|
-
parser_clean.add_argument('--recurse', '-d', action='store_true', help='Recurse into subdirectories')
|
|
623
|
-
parser_clean.add_argument('--force', '-f', action='store_true', help='If the Git configuration variable clean.requireForce is not set to false, git clean will refuse to delete files or directories unless given -f or -i')
|
|
624
|
-
# TODO: parser_clean.add_argument('--interactive', '-i', action='store_true', help='Show what would be done and clean files interactively.')
|
|
625
|
-
parser_clean.add_argument('--dry-run', '-n', action='store_true', help='Don’t actually remove anything, just show what would be done.')
|
|
626
|
-
# TODO: parser_clean.add_argument('--quiet', '-q', , action='store_true', help='Be quiet, only report errors, but not the files that are successfully removed.')
|
|
627
|
-
parser_clean.add_argument('--exclude', '-e', action='store', help='Use the given exclude pattern in addition to the standard ignore rules.')
|
|
628
|
-
parser_clean.add_argument('-x', action='store_true', help='Don’t use the standard ignore rules, but still use the ignore rules given with -e options from the command line.')
|
|
629
|
-
parser_clean.add_argument('-X', action='store_true', help='Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files.')
|
|
630
|
-
|
|
631
|
-
parser_dir = subparsers.add_parser('dir', help='Return the location of a working tree, given the repo name')
|
|
632
|
-
parser_dir.add_argument('dir', nargs=1, action='store', help='The name of the working tree')
|
|
633
|
-
|
|
634
|
-
parser_config = subparsers.add_parser('config', help='Return the name and location of the configuration file')
|
|
635
|
-
|
|
636
|
-
parser_run = subparsers.add_parser('run', help='Run any git command in each of the working trees')
|
|
637
|
-
parser_run.add_argument('--cont', '-c', action='store_true', help='Continue if the command returns an error in any of the working trees')
|
|
638
|
-
parser_run.add_argument('cmd', nargs='*', action='store', help='The command to run (the command may need to be quoted)')
|
|
639
|
-
|
|
640
|
-
parser_review = subparsers.add_parser('review', help='Review the changes in a working tree')
|
|
641
|
-
parser_review.add_argument('parameters', nargs='*', action='store', help='Parameters passed to the "git review" command')
|
|
642
|
-
|
|
643
|
-
# Parse the command line
|
|
644
|
-
|
|
645
|
-
args = parser.parse_args()
|
|
646
|
-
|
|
647
|
-
# Basic error checking
|
|
648
|
-
|
|
649
|
-
if not args.command:
|
|
650
|
-
error('No command specified')
|
|
651
|
-
|
|
652
|
-
if args.command not in commands:
|
|
653
|
-
error(f'Unrecognized command "{args.command}"')
|
|
418
|
+
if args.internal_command and args.command not in commands:
|
|
419
|
+
error(f'Invalid command "{args.command}"')
|
|
654
420
|
|
|
655
421
|
# If the configuration file exists, read it
|
|
656
422
|
|
|
657
423
|
config = configparser.ConfigParser()
|
|
658
424
|
|
|
659
|
-
args.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
config.read(args.config)
|
|
663
|
-
os.chdir(os.path.dirname(args.config))
|
|
425
|
+
if not (args.internal_command and args.command == 'init'):
|
|
426
|
+
if not os.path.isfile(args.configuration_file):
|
|
427
|
+
error(f'Cannot read configuration file {args.configuration_file}')
|
|
664
428
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if args.modified or args.branched:
|
|
669
|
-
error('The "--modified" and "--branched" options cannot be used with the "init" subcommand')
|
|
670
|
-
elif not config:
|
|
671
|
-
error(f'Unable to location configuration file "{args.config}"')
|
|
429
|
+
if os.path.isfile(args.configuration_file):
|
|
430
|
+
config.read(args.configuration_file)
|
|
431
|
+
os.chdir(os.path.dirname(args.configuration_file))
|
|
672
432
|
|
|
673
433
|
# Get the console size
|
|
674
434
|
|
|
@@ -678,15 +438,27 @@ def main():
|
|
|
678
438
|
console = None
|
|
679
439
|
args.quiet = True
|
|
680
440
|
|
|
681
|
-
# Run
|
|
441
|
+
# Run an internal or external command-specific validation
|
|
442
|
+
|
|
443
|
+
if args.internal_command:
|
|
444
|
+
if args.command == 'init':
|
|
445
|
+
if args.modified or args.branched:
|
|
446
|
+
error('The "--modified" and "--branched" options cannot be used with the "init" subcommand')
|
|
447
|
+
elif not config:
|
|
448
|
+
error(f'Unable to location configuration file "{args.configuration_file}"')
|
|
449
|
+
|
|
450
|
+
# Run the subcommand
|
|
682
451
|
|
|
683
|
-
|
|
452
|
+
commands[args.command](args, config, console)
|
|
684
453
|
|
|
685
|
-
|
|
454
|
+
# Save the updated configuration file if it has changed (currently, only the init command will do this).
|
|
686
455
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
456
|
+
if config and args.command == 'init':
|
|
457
|
+
with open(args.configuration_file, 'w', encoding='utf8') as configfile:
|
|
458
|
+
config.write(configfile)
|
|
459
|
+
|
|
460
|
+
else:
|
|
461
|
+
run_git_command(args, config, console)
|
|
690
462
|
|
|
691
463
|
################################################################################
|
|
692
464
|
|
skilleter_thingy/thingy/git2.py
CHANGED
|
@@ -64,10 +64,12 @@ def git(cmd, stdout=None, stderr=None, path=None):
|
|
|
64
64
|
if path:
|
|
65
65
|
git_cmd += ['-C', path]
|
|
66
66
|
|
|
67
|
+
git_cmd += cmd if isinstance(cmd, list) else [cmd]
|
|
68
|
+
|
|
67
69
|
logging.debug('Running %s', ' '.join(git_cmd + cmd))
|
|
68
70
|
|
|
69
71
|
try:
|
|
70
|
-
return run.run(git_cmd
|
|
72
|
+
return run.run(git_cmd, stdout=stdout, stderr=stderr)
|
|
71
73
|
except run.RunError as exc:
|
|
72
74
|
raise GitError(exc.msg, exc.status)
|
|
73
75
|
|
|
@@ -85,15 +87,17 @@ def git_run_status(cmd, stdout=None, stderr=None, path=None, redirect=True):
|
|
|
85
87
|
if path:
|
|
86
88
|
git_cmd += ['-C', path]
|
|
87
89
|
|
|
90
|
+
git_cmd += cmd if isinstance(cmd, list) else [cmd]
|
|
91
|
+
|
|
88
92
|
if redirect:
|
|
89
|
-
result = subprocess.run(git_cmd
|
|
93
|
+
result = subprocess.run(git_cmd,
|
|
90
94
|
stdout=stdout or subprocess.PIPE,
|
|
91
95
|
stderr=stderr or subprocess.PIPE,
|
|
92
96
|
text=True, check=False,
|
|
93
97
|
errors='ignore',
|
|
94
98
|
universal_newlines=True)
|
|
95
99
|
else:
|
|
96
|
-
result = subprocess.run(git_cmd
|
|
100
|
+
result = subprocess.run(git_cmd)
|
|
97
101
|
|
|
98
102
|
return (result.stdout or result.stderr), result.returncode
|
|
99
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.90
|
|
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
|
|
@@ -43,6 +43,12 @@ This is intended for use in a situation where you have a collection of related g
|
|
|
43
43
|
|
|
44
44
|
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.
|
|
45
45
|
|
|
46
|
+
The multigit command line format is:
|
|
47
|
+
|
|
48
|
+
multigit OPTIONS COMMAND
|
|
49
|
+
|
|
50
|
+
Where COMMAND is an internal multigit command if it starts with a '+' and is a git command otherwise.
|
|
51
|
+
|
|
46
52
|
By default, when a multigit command, other than `init` is run, it runs a git command in each of the working trees. 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:
|
|
47
53
|
|
|
48
54
|
*--repos / -r* Allows a list of working trees to be specfied, either as the full or relative path, the name or a wildcard.
|
|
@@ -51,46 +57,27 @@ By default, when a multigit command, other than `init` is run, it runs a git com
|
|
|
51
57
|
|
|
52
58
|
*--branched / -b* Run only working trees where the current branch that is checked out is NOT the default branch
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
# TODO - Could just make the commands multigit +init/+dir/+config and then for anything else, just pass the whole of the command line to git once we've parsed the default branch?
|
|
56
|
-
|
|
57
|
-
Multigit supports a (growing) list of subcommands:
|
|
58
|
-
|
|
59
|
-
*init* - Create or update the configuration file
|
|
60
|
-
|
|
61
|
-
*status* - Run `git status` in each of the working trees.
|
|
62
|
-
|
|
63
|
-
*fetch* - Run `git fetch` in each of the working trees
|
|
64
|
-
|
|
65
|
-
*pull* - Run `git pull` in each of the working trees
|
|
66
|
-
|
|
67
|
-
*push* - Run `git push` in each of the working trees
|
|
68
|
-
|
|
69
|
-
*co*
|
|
70
|
-
|
|
71
|
-
*commit*
|
|
72
|
-
|
|
73
|
-
*update*
|
|
60
|
+
Multigit supports a small list of subcommands:
|
|
74
61
|
|
|
75
|
-
*
|
|
62
|
+
*+init* - Create or update the configuration file
|
|
76
63
|
|
|
77
|
-
|
|
64
|
+
*+dir* - Given the name of a working tree, prin the location within the multigit tree
|
|
78
65
|
|
|
79
|
-
|
|
66
|
+
*+config* - Print the name and location of the multigit configuration file.
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
Any command not prefixed with '+' is run in each of the working trees (filtered by the various multigit options) as a git command.
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
For example; `multigit -m commit -ab` would run `git commit -a` in each of the working trees that is branched and contains modified files.
|
|
84
71
|
|
|
85
72
|
# Miscellaneous Git Utilities
|
|
86
73
|
|
|
87
74
|
## ggit
|
|
88
75
|
|
|
89
|
-
Run a git command in all working trees under the current directory (note that this is not related to multigit
|
|
76
|
+
Run a git command in all working trees under the current directory (note that this is not related to multigit).
|
|
90
77
|
|
|
91
78
|
## ggrep
|
|
92
79
|
|
|
93
|
-
Run 'git grep' in all repos under the current directory (note that this is not related to multigit
|
|
80
|
+
Run 'git grep' in all repos under the current directory (note that this is not related to multigit).
|
|
94
81
|
|
|
95
82
|
## gitprompt
|
|
96
83
|
|
|
@@ -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=5voQtjJjDCVx4zjPwVRy620NpuLiwwFitzxjIsRGtxQ,4310
|
|
27
27
|
skilleter_thingy/moviemover.py,sha256=j_Xb9_jFdgpFBAXcF4tEqbnKH_FonlnUU39LiCK980k,4470
|
|
28
|
-
skilleter_thingy/multigit.py,sha256
|
|
28
|
+
skilleter_thingy/multigit.py,sha256=-UzmnCHXTq3Wlsem1zm4QsEp8wIB1HZnFvpTo3R3h_o,16575
|
|
29
29
|
skilleter_thingy/photodupe.py,sha256=l0hbzSLb2Vk2ceteg-x9fHXCEE1uUuFo84hz5rsZUPA,4184
|
|
30
30
|
skilleter_thingy/phototidier.py,sha256=5gSjlINUxf3ZQl3NG0o7CsWwODvTbokIMIafLFvn8Hc,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=
|
|
55
|
+
skilleter_thingy/thingy/git2.py,sha256=WfX85zWz-0h3FiP1ME5VJ-OUBVMX9Vr2JGo401jk9oc,37156
|
|
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.
|
|
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.90.dist-info/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
|
|
65
|
+
skilleter_thingy-0.0.90.dist-info/METADATA,sha256=JqhKf6MsYFBPaHNvtsPxEv0kbzzd9kVyWYdRmwDEVS8,8236
|
|
66
|
+
skilleter_thingy-0.0.90.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
67
|
+
skilleter_thingy-0.0.90.dist-info/entry_points.txt,sha256=u5ymS-KPljIGTnprV5yJsAjz7qgeT2BZ-Qo_Con_PFM,2145
|
|
68
|
+
skilleter_thingy-0.0.90.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
|
|
69
|
+
skilleter_thingy-0.0.90.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|