skilleter-thingy 0.0.87__py3-none-any.whl → 0.0.89__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 +196 -429
- skilleter_thingy/thingy/git2.py +7 -3
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.dist-info}/METADATA +44 -8
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.dist-info}/RECORD +8 -8
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.dist-info}/LICENSE +0 -0
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.dist-info}/entry_points.txt +0 -0
- {skilleter_thingy-0.0.87.dist-info → skilleter_thingy-0.0.89.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,6 +42,59 @@ 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
|
|
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
|
|
|
@@ -51,6 +103,26 @@ def error(msg, status=1):
|
|
|
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,279 +247,6 @@ def mg_init(args, config, console):
|
|
|
190
247
|
|
|
191
248
|
################################################################################
|
|
192
249
|
|
|
193
|
-
def mg_status(args, config, console):
|
|
194
|
-
"""Report Git status for any repo that has a non-empty status"""
|
|
195
|
-
|
|
196
|
-
# TODO: More user-friendly output
|
|
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
|
-
################################################################################
|
|
269
|
-
|
|
270
|
-
def mg_fetch(args, config, console):
|
|
271
|
-
"""Run git fetch everywhere"""
|
|
272
|
-
|
|
273
|
-
_ = config
|
|
274
|
-
|
|
275
|
-
for repo in select_git_repos(args, config):
|
|
276
|
-
if not args.quiet:
|
|
277
|
-
show_progress(console.columns, repo.name)
|
|
278
|
-
|
|
279
|
-
colour.write(f'Fetching updates for [BLUE:{repo.name}]')
|
|
280
|
-
|
|
281
|
-
result = git.fetch(path=repo.name)
|
|
282
|
-
|
|
283
|
-
if result:
|
|
284
|
-
colour.write(f'[BOLD:{repo.name}]')
|
|
285
|
-
for item in result:
|
|
286
|
-
if item.startswith('From '):
|
|
287
|
-
colour.write(f' [BLUE:{item}]')
|
|
288
|
-
else:
|
|
289
|
-
colour.write(f' {item}')
|
|
290
|
-
|
|
291
|
-
colour.write()
|
|
292
|
-
|
|
293
|
-
################################################################################
|
|
294
|
-
|
|
295
|
-
def mg_pull(args, config, console):
|
|
296
|
-
"""Run git pull everywhere"""
|
|
297
|
-
|
|
298
|
-
_ = config
|
|
299
|
-
|
|
300
|
-
for repo in select_git_repos(args, config):
|
|
301
|
-
if not args.quiet:
|
|
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)
|
|
427
|
-
|
|
428
|
-
git.checkout(branch, path=repo.name)
|
|
429
|
-
result = git.rebase(default_branch, path=repo.name)
|
|
430
|
-
colour.write(result[0], indent=4)
|
|
431
|
-
|
|
432
|
-
################################################################################
|
|
433
|
-
|
|
434
|
-
def mg_clean(args, config, console):
|
|
435
|
-
"""Clean the repos"""
|
|
436
|
-
|
|
437
|
-
_ = config
|
|
438
|
-
|
|
439
|
-
for repo in select_git_repos(args, config):
|
|
440
|
-
if not args.quiet:
|
|
441
|
-
show_progress(console.columns, repo.name)
|
|
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}]')
|
|
451
|
-
|
|
452
|
-
for item in result:
|
|
453
|
-
skipping = item.startswith('Skipping repository ')
|
|
454
|
-
|
|
455
|
-
if skipping and not args.verbose:
|
|
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)
|
|
461
|
-
|
|
462
|
-
colour.write()
|
|
463
|
-
|
|
464
|
-
################################################################################
|
|
465
|
-
|
|
466
250
|
def mg_dir(args, config, console):
|
|
467
251
|
"""Return the location of a working tree, given the name. Returns an
|
|
468
252
|
error unless there is a unique match"""
|
|
@@ -472,11 +256,13 @@ def mg_dir(args, config, console):
|
|
|
472
256
|
_ = console
|
|
473
257
|
_ = config
|
|
474
258
|
|
|
259
|
+
if len(args.parameters) != 1:
|
|
260
|
+
error('The +dir command takes one parameter - the name of the working tree to search for')
|
|
261
|
+
|
|
475
262
|
location = []
|
|
476
|
-
search_dir = args.dir[0]
|
|
477
263
|
|
|
478
264
|
for repo in select_git_repos(args, config):
|
|
479
|
-
if fnmatch.fnmatch(repo['name'],
|
|
265
|
+
if fnmatch.fnmatch(repo['name'], args.parameters[0]):
|
|
480
266
|
location.append(repo.name)
|
|
481
267
|
|
|
482
268
|
if len(location) == 0:
|
|
@@ -484,7 +270,7 @@ def mg_dir(args, config, console):
|
|
|
484
270
|
elif len(location) > 1:
|
|
485
271
|
error(f'Multiple matches with {search_dir}')
|
|
486
272
|
|
|
487
|
-
colour.write(os.path.join(os.path.dirname(args.
|
|
273
|
+
colour.write(os.path.join(os.path.dirname(args.configuration_file), location[0]))
|
|
488
274
|
|
|
489
275
|
################################################################################
|
|
490
276
|
|
|
@@ -494,181 +280,150 @@ def mg_config(args, config, console):
|
|
|
494
280
|
_ = config
|
|
495
281
|
_ = console
|
|
496
282
|
|
|
497
|
-
|
|
283
|
+
if len(args.parameters):
|
|
284
|
+
error('The +config command does not take parameters')
|
|
285
|
+
|
|
286
|
+
colour.write(args.configuration_file)
|
|
498
287
|
|
|
499
288
|
################################################################################
|
|
500
289
|
|
|
501
|
-
def
|
|
290
|
+
def run_git_command(args, config, console):
|
|
502
291
|
"""Run a command in each of the working trees, optionally continuing if
|
|
503
292
|
there's an error"""
|
|
504
293
|
|
|
505
294
|
_ = config
|
|
506
|
-
|
|
507
|
-
if len(args.cmd) == 0:
|
|
508
|
-
error('No command specified')
|
|
509
|
-
elif len(args.cmd) == 1:
|
|
510
|
-
command = shlex.split(args.cmd)
|
|
511
|
-
else:
|
|
512
|
-
command = args.cmd
|
|
513
|
-
|
|
295
|
+
_ = console
|
|
514
296
|
|
|
515
297
|
for repo in select_git_repos(args, config):
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
repo_command = []
|
|
520
|
-
for cmd in command:
|
|
298
|
+
repo_command = [args.command]
|
|
299
|
+
for cmd in args.parameters:
|
|
521
300
|
repo_command.append(branch_name(cmd, repo['default branch']))
|
|
522
301
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
################################################################################
|
|
526
|
-
|
|
527
|
-
def mg_review(args, config, console):
|
|
528
|
-
"""Run the git review command"""
|
|
529
|
-
|
|
530
|
-
# TODO: Better parsing to replace DEFAULT with default branch only where appropriate
|
|
531
|
-
|
|
532
|
-
for repo in select_git_repos(args, config):
|
|
533
|
-
if not args.quiet:
|
|
534
|
-
show_progress(console.columns, repo.name)
|
|
302
|
+
colour.write(f'\n[BOLD:{repo.name}]\n')
|
|
535
303
|
|
|
536
|
-
|
|
537
|
-
for p in args.parameters:
|
|
538
|
-
params += shlex.split(p.replace(DEFAULT_BRANCH, repo['default branch']))
|
|
304
|
+
_, status = git.git_run_status(repo_command, path=repo.name, redirect=False)
|
|
539
305
|
|
|
540
|
-
|
|
541
|
-
|
|
306
|
+
if status and not args.error_continue:
|
|
307
|
+
sys.exit(status)
|
|
542
308
|
|
|
543
309
|
################################################################################
|
|
544
310
|
|
|
545
|
-
def
|
|
546
|
-
"""
|
|
547
|
-
|
|
548
|
-
Returns configuration file path or None if the configuration file
|
|
549
|
-
could not be found."""
|
|
311
|
+
def parse_command_line():
|
|
312
|
+
"""Manually parse the command line as we want to be able to accept 'multigit <OPTIONS> <+MULTIGITCOMMAND | ANY_GIT_COMMAND_WITH_OPTIONS>
|
|
313
|
+
and I can't see a way to get ArgumentParser to accept arbitrary command+options"""
|
|
550
314
|
|
|
551
|
-
|
|
552
|
-
config_file = args.config
|
|
553
|
-
else:
|
|
554
|
-
config_path = os.getcwd()
|
|
555
|
-
config_file = os.path.join(config_path, args.config)
|
|
315
|
+
args = Arguments()
|
|
556
316
|
|
|
557
|
-
|
|
558
|
-
config_path = os.path.dirname(config_path)
|
|
559
|
-
config_file = os.path.join(config_path, args.config)
|
|
317
|
+
# Expand arguments so that, for instance '-dv' is parsed as '-d -v'
|
|
560
318
|
|
|
561
|
-
|
|
319
|
+
argv = []
|
|
562
320
|
|
|
563
|
-
|
|
321
|
+
for arg in sys.argv:
|
|
322
|
+
if arg[0] != '-' or arg.startswith('--'):
|
|
323
|
+
argv.append(arg)
|
|
324
|
+
else:
|
|
325
|
+
for c in arg[1:]:
|
|
326
|
+
argv.append('-' + c)
|
|
564
327
|
|
|
565
|
-
|
|
566
|
-
"""Main function"""
|
|
328
|
+
# Currently doesn't handle single letter options in concatenated form - e.g. -dv
|
|
567
329
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
'dir': mg_dir,
|
|
579
|
-
'config': mg_config,
|
|
580
|
-
'run': mg_run,
|
|
581
|
-
'review': mg_review,
|
|
582
|
-
}
|
|
330
|
+
i = 1
|
|
331
|
+
while i < len(argv):
|
|
332
|
+
param = argv[i]
|
|
583
333
|
|
|
584
|
-
|
|
334
|
+
if param in ('--dryrun', '--dry-run', '-D'):
|
|
335
|
+
args.dryrun = True
|
|
585
336
|
|
|
586
|
-
|
|
337
|
+
elif param in ('--debug', '-d'):
|
|
338
|
+
args.debug = True
|
|
587
339
|
|
|
588
|
-
|
|
589
|
-
|
|
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')
|
|
340
|
+
elif param in ('--verbose', '-v'):
|
|
341
|
+
args.verbose = True
|
|
597
342
|
|
|
598
|
-
|
|
343
|
+
elif param in ('--quiet', '-q'):
|
|
344
|
+
args.quiet = True
|
|
599
345
|
|
|
600
|
-
|
|
346
|
+
elif param in ('--config', '-c'):
|
|
347
|
+
try:
|
|
348
|
+
i += 1
|
|
349
|
+
args.configuration_file = argv[i]
|
|
350
|
+
except IndexError:
|
|
351
|
+
error('--config - missing configuration file parameter')
|
|
601
352
|
|
|
602
|
-
|
|
353
|
+
elif param in ('--repos', '-r'):
|
|
354
|
+
try:
|
|
355
|
+
i += 1
|
|
356
|
+
args.repos.append(argv[i])
|
|
357
|
+
except IndexError:
|
|
358
|
+
error('--repos - missing repo parameter')
|
|
603
359
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
parser_pull = subparsers.add_parser('pull', help='Run git pull in every repo')
|
|
360
|
+
elif param in ('--modified', '-m'):
|
|
361
|
+
args.modified = True
|
|
607
362
|
|
|
608
|
-
|
|
609
|
-
|
|
363
|
+
elif param in ('--branched', '-b'):
|
|
364
|
+
args.branched = True
|
|
610
365
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
parser_checkout.add_argument('branch', nargs='?', default=None, action='store', help='The branch name to check out (defaults to the default branch)')
|
|
366
|
+
elif param in ('--continue', '-C'):
|
|
367
|
+
args.error_continue = True
|
|
614
368
|
|
|
615
|
-
|
|
616
|
-
|
|
369
|
+
elif param in ('--help', '-h'):
|
|
370
|
+
print(HELP_INFO)
|
|
371
|
+
sys.exit(0)
|
|
617
372
|
|
|
618
|
-
|
|
373
|
+
elif param[0] == '-':
|
|
374
|
+
error(f'Invalid option: "{param}"')
|
|
375
|
+
else:
|
|
376
|
+
break
|
|
619
377
|
|
|
620
|
-
|
|
378
|
+
i += 1
|
|
621
379
|
|
|
622
|
-
|
|
623
|
-
|
|
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.')
|
|
380
|
+
# After the options, we either have a multigit command (prefixed with '+') or a git command
|
|
381
|
+
# followed by parameter
|
|
630
382
|
|
|
631
|
-
|
|
632
|
-
|
|
383
|
+
try:
|
|
384
|
+
if argv[i][0] == '+':
|
|
385
|
+
args.command = argv[i][1:]
|
|
386
|
+
args.internal_command = True
|
|
387
|
+
else:
|
|
388
|
+
args.command = argv[i]
|
|
389
|
+
args.internal_command = False
|
|
633
390
|
|
|
634
|
-
|
|
391
|
+
except IndexError:
|
|
392
|
+
error('Missing command')
|
|
635
393
|
|
|
636
|
-
|
|
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)')
|
|
394
|
+
args.parameters = argv[i+1:]
|
|
639
395
|
|
|
640
|
-
|
|
641
|
-
parser_review.add_argument('parameters', nargs='*', action='store', help='Parameters passed to the "git review" command')
|
|
396
|
+
args.configuration_file = find_configuration(args)
|
|
642
397
|
|
|
643
|
-
|
|
398
|
+
return args
|
|
644
399
|
|
|
645
|
-
|
|
400
|
+
################################################################################
|
|
646
401
|
|
|
647
|
-
|
|
402
|
+
def main():
|
|
403
|
+
"""Main function"""
|
|
648
404
|
|
|
649
|
-
|
|
650
|
-
|
|
405
|
+
commands = {
|
|
406
|
+
'init': mg_init,
|
|
407
|
+
'dir': mg_dir,
|
|
408
|
+
'config': mg_config,
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
args = parse_command_line()
|
|
651
412
|
|
|
652
|
-
if args.command not in commands:
|
|
653
|
-
error(f'
|
|
413
|
+
if args.internal_command and args.command not in commands:
|
|
414
|
+
error(f'Invalid command "{args.command}"')
|
|
654
415
|
|
|
655
416
|
# If the configuration file exists, read it
|
|
656
417
|
|
|
657
418
|
config = configparser.ConfigParser()
|
|
658
419
|
|
|
659
|
-
args.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
config.read(args.config)
|
|
663
|
-
os.chdir(os.path.dirname(args.config))
|
|
420
|
+
if not (args.internal_command and args.command == 'init'):
|
|
421
|
+
if not os.path.isfile(args.configuration_file):
|
|
422
|
+
error(f'Cannot read configuration file {args.configuration_file}')
|
|
664
423
|
|
|
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}"')
|
|
424
|
+
if os.path.isfile(args.configuration_file):
|
|
425
|
+
config.read(args.configuration_file)
|
|
426
|
+
os.chdir(os.path.dirname(args.configuration_file))
|
|
672
427
|
|
|
673
428
|
# Get the console size
|
|
674
429
|
|
|
@@ -678,15 +433,27 @@ def main():
|
|
|
678
433
|
console = None
|
|
679
434
|
args.quiet = True
|
|
680
435
|
|
|
681
|
-
# Run
|
|
436
|
+
# Run an internal or external command-specific validation
|
|
437
|
+
|
|
438
|
+
if args.internal_command:
|
|
439
|
+
if args.command == 'init':
|
|
440
|
+
if args.modified or args.branched:
|
|
441
|
+
error('The "--modified" and "--branched" options cannot be used with the "init" subcommand')
|
|
442
|
+
elif not config:
|
|
443
|
+
error(f'Unable to location configuration file "{args.configuration_file}"')
|
|
444
|
+
|
|
445
|
+
# Run the subcommand
|
|
682
446
|
|
|
683
|
-
|
|
447
|
+
commands[args.command](args, config, console)
|
|
684
448
|
|
|
685
|
-
|
|
449
|
+
# Save the updated configuration file if it has changed (currently, only the init command will do this).
|
|
686
450
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
451
|
+
if config and args.command == 'init':
|
|
452
|
+
with open(args.configuration_file, 'w', encoding='utf8') as configfile:
|
|
453
|
+
config.write(configfile)
|
|
454
|
+
|
|
455
|
+
else:
|
|
456
|
+
run_git_command(args, config, console)
|
|
690
457
|
|
|
691
458
|
################################################################################
|
|
692
459
|
|
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.89
|
|
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
|
|
@@ -33,20 +33,60 @@ The following commands are documented in detail in the help output that can be d
|
|
|
33
33
|
|
|
34
34
|
This README just contains a summary of the functionality of each command.
|
|
35
35
|
|
|
36
|
-
# Git
|
|
36
|
+
# Git Repo Management
|
|
37
|
+
|
|
38
|
+
## multigit
|
|
39
|
+
|
|
40
|
+
Manage a collection of related git working trees.
|
|
41
|
+
|
|
42
|
+
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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:
|
|
53
|
+
|
|
54
|
+
*--repos / -r* Allows a list of working trees to be specfied, either as the full or relative path, the name or a wildcard.
|
|
55
|
+
|
|
56
|
+
*--modified / -m* Run only in working trees containing locally modified files
|
|
57
|
+
|
|
58
|
+
*--branched / -b* Run only working trees where the current branch that is checked out is NOT the default branch
|
|
59
|
+
|
|
60
|
+
Multigit supports a small list of subcommands:
|
|
61
|
+
|
|
62
|
+
*+init* - Create or update the configuration file
|
|
63
|
+
|
|
64
|
+
*+dir* - Given the name of a working tree, prin the location within the multigit tree
|
|
65
|
+
|
|
66
|
+
*+config* - Print the name and location of the multigit configuration file.
|
|
67
|
+
|
|
68
|
+
Any command not prefixed with '+' is run in each of the working trees (filtered by the various multigit options) as a git command.
|
|
69
|
+
|
|
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.
|
|
71
|
+
|
|
72
|
+
# Miscellaneous Git Utilities
|
|
37
73
|
|
|
38
74
|
## ggit
|
|
39
75
|
|
|
40
|
-
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).
|
|
41
77
|
|
|
42
78
|
## ggrep
|
|
43
79
|
|
|
44
|
-
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).
|
|
45
81
|
|
|
46
82
|
## gitprompt
|
|
47
83
|
|
|
48
84
|
Output a string containing colour-coded shell nesting level, current directory and git working tree status (used in the shell prompt).
|
|
49
85
|
|
|
86
|
+
# Git Extensions
|
|
87
|
+
|
|
88
|
+
Due to the way that the git command works, these can be run as they were additional git subcommands
|
|
89
|
+
|
|
50
90
|
## git ca
|
|
51
91
|
|
|
52
92
|
Improved version of 'git commit --amend'. Updates files that are already in the commit and, optionally, adds and commits additional files.
|
|
@@ -75,10 +115,6 @@ Output the top level directory of the git working tree or return an error if we
|
|
|
75
115
|
|
|
76
116
|
Console-based git change review tool.
|
|
77
117
|
|
|
78
|
-
## multigit
|
|
79
|
-
|
|
80
|
-
Manage a collection of related git repoitories.
|
|
81
|
-
|
|
82
118
|
## GitLab Commands
|
|
83
119
|
|
|
84
120
|
### git mr
|
|
@@ -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=hAMF5glefQ82_tMs-zGXj-5FGkXpytQZO5hjqjqA95M,16212
|
|
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.89.dist-info/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
|
|
65
|
+
skilleter_thingy-0.0.89.dist-info/METADATA,sha256=m_ybn_cE_YK_cnxhqEmXKxyPm32Kx5cYWuRPwcqURUA,8236
|
|
66
|
+
skilleter_thingy-0.0.89.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
67
|
+
skilleter_thingy-0.0.89.dist-info/entry_points.txt,sha256=u5ymS-KPljIGTnprV5yJsAjz7qgeT2BZ-Qo_Con_PFM,2145
|
|
68
|
+
skilleter_thingy-0.0.89.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
|
|
69
|
+
skilleter_thingy-0.0.89.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|