skilleter-thingy 0.0.71__tar.gz → 0.0.73__tar.gz
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-0.0.71/skilleter_thingy.egg-info → skilleter_thingy-0.0.73}/PKG-INFO +1 -2
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/pyproject.toml +2 -2
- skilleter_thingy-0.0.73/skilleter_thingy/multigit.py +345 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/git2.py +2 -2
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73/skilleter_thingy.egg-info}/PKG-INFO +1 -2
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/requires.txt +0 -1
- skilleter_thingy-0.0.71/skilleter_thingy/multigit.py +0 -244
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/LICENSE +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/README.md +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/setup.cfg +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/__init__.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/addpath.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/borger.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/box.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/console_colours.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/diskspacecheck.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/docker_purge.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/ffind.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/ggit.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/ggrep.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_br.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_ca.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_cleanup.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_co.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_common.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_hold.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_mr.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_parent.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_review.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_update.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/git_wt.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/gitcmp_helper.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/gitprompt.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/gl.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/gphotosync.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/linecount.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/moviemover.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/photodupe.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/phototidier.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/py_audit.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/readable.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/remdir.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/rmdupe.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/rpylint.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/splitpics.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/strreplace.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/sysmon.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/tfm.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/tfparse.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/__init__.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/colour.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/dc_curses.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/dc_defaults.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/dc_util.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/dircolors.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/docker.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/files.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/git.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/gitlab.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/path.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/popup.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/process.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/run.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/tfm_pane.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/tidy.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/venv_template.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/trimpath.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/venv_create.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/window_rename.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/xchmod.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/yamlcheck.py +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/SOURCES.txt +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/dependency_links.txt +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/entry_points.txt +0 -0
- {skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.73
|
|
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
|
|
@@ -18,7 +18,6 @@ Requires-Dist: pyaml
|
|
|
18
18
|
Requires-Dist: pygit2
|
|
19
19
|
Requires-Dist: python-dateutil
|
|
20
20
|
Requires-Dist: requests
|
|
21
|
-
Requires-Dist: tomlkit
|
|
22
21
|
|
|
23
22
|
# Thingy
|
|
24
23
|
|
|
@@ -7,7 +7,7 @@ name = "skilleter_thingy"
|
|
|
7
7
|
|
|
8
8
|
# Version must be incremented to install updated Thingy
|
|
9
9
|
|
|
10
|
-
version = "0.0.
|
|
10
|
+
version = "0.0.73"
|
|
11
11
|
|
|
12
12
|
authors = [
|
|
13
13
|
{name="John Skilleter", email="john@skilleter.org.uk"},
|
|
@@ -34,7 +34,7 @@ dependencies = [
|
|
|
34
34
|
"pygit2",
|
|
35
35
|
"python-dateutil",
|
|
36
36
|
"requests",
|
|
37
|
-
"tomlkit",
|
|
37
|
+
# "tomlkit",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""mg - MultiGit - utility for managing multiple Git repos in a hierarchical directory tree"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import argparse
|
|
8
|
+
import fnmatch
|
|
9
|
+
import configparser
|
|
10
|
+
|
|
11
|
+
# TODO: Switch to tomlkit
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
|
|
14
|
+
import thingy.git2 as git
|
|
15
|
+
import thingy.colour as colour
|
|
16
|
+
|
|
17
|
+
################################################################################
|
|
18
|
+
|
|
19
|
+
"""Configuration file format:
|
|
20
|
+
|
|
21
|
+
[default]
|
|
22
|
+
# Default settings
|
|
23
|
+
|
|
24
|
+
[repo_path]
|
|
25
|
+
name = path
|
|
26
|
+
default branch = name
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# TODO: [ ] -j option to run in parallel?
|
|
30
|
+
# TODO: [ ] init function
|
|
31
|
+
# TODO: [ ] Use the configuration file
|
|
32
|
+
# TODO: [ ] Don't use a fixed list of default branch names
|
|
33
|
+
# TODO: [ ] / Output name of each git repo as it is processed as command sits there seeming to do nothing otherwise.
|
|
34
|
+
# TODO: [ ] ? Pull/fetch - only output after running command and only if something updated
|
|
35
|
+
# TODO: [ ] Don't save the configuration on exit if it hasn't changed
|
|
36
|
+
################################################################################
|
|
37
|
+
|
|
38
|
+
DEFAULT_CONFIG_FILE = 'multigit.toml'
|
|
39
|
+
|
|
40
|
+
################################################################################
|
|
41
|
+
|
|
42
|
+
def error(msg, status=1):
|
|
43
|
+
"""Quit with an error"""
|
|
44
|
+
|
|
45
|
+
sys.stderr.write(f'{msg}\n')
|
|
46
|
+
sys.exit(status)
|
|
47
|
+
|
|
48
|
+
################################################################################
|
|
49
|
+
|
|
50
|
+
def show_progress(width, msg):
|
|
51
|
+
"""Show a single line progress message"""
|
|
52
|
+
|
|
53
|
+
name = msg[:width-1]
|
|
54
|
+
|
|
55
|
+
colour.write(f'{name}', newline=False)
|
|
56
|
+
|
|
57
|
+
if len(name) < width-1:
|
|
58
|
+
colour.write(' '*(width-len(name)), newline=False)
|
|
59
|
+
|
|
60
|
+
colour.write('\r', newline=False)
|
|
61
|
+
|
|
62
|
+
################################################################################
|
|
63
|
+
|
|
64
|
+
def find_git_repos(directory, wildcard):
|
|
65
|
+
"""Locate and return a list of '.git' directory parent directories in the
|
|
66
|
+
specified path.
|
|
67
|
+
If wildcard is not None then it is treated as a list of wildcards and
|
|
68
|
+
only repos matching at least one of the wildcards are returned."""
|
|
69
|
+
|
|
70
|
+
for root, dirs, _ in os.walk(directory):
|
|
71
|
+
if '.git' in dirs:
|
|
72
|
+
if root.startswith('./'):
|
|
73
|
+
root = root[2:]
|
|
74
|
+
|
|
75
|
+
if wildcard:
|
|
76
|
+
for card in wildcard:
|
|
77
|
+
if fnmatch.fnmatch(root, card):
|
|
78
|
+
yield root
|
|
79
|
+
break
|
|
80
|
+
else:
|
|
81
|
+
yield root
|
|
82
|
+
|
|
83
|
+
################################################################################
|
|
84
|
+
|
|
85
|
+
def mg_init(args, config, console):
|
|
86
|
+
"""Create or update the configuration
|
|
87
|
+
By default, it scans the tree for git directories and adds or updates them
|
|
88
|
+
in the configuration, using the current branch as the default branch. """
|
|
89
|
+
|
|
90
|
+
# Search for .git directories
|
|
91
|
+
|
|
92
|
+
for repo in find_git_repos(args.directory, args.repos):
|
|
93
|
+
if not args.quiet:
|
|
94
|
+
show_progress(console.columns, repo)
|
|
95
|
+
|
|
96
|
+
config[repo] = {'default branch': git.branch(path=repo)}
|
|
97
|
+
|
|
98
|
+
################################################################################
|
|
99
|
+
|
|
100
|
+
def mg_status(args, config, console):
|
|
101
|
+
"""Report Git status for any repo that has a non-empty status"""
|
|
102
|
+
|
|
103
|
+
for repo in find_git_repos(args.directory, args.repos):
|
|
104
|
+
if not args.quiet:
|
|
105
|
+
show_progress(console.columns, repo)
|
|
106
|
+
|
|
107
|
+
status = git.status(path=repo)
|
|
108
|
+
branch = git.branch(path=repo)
|
|
109
|
+
|
|
110
|
+
if status or branch != config[repo]['default branch']:
|
|
111
|
+
if branch == config[repo]['default branch']:
|
|
112
|
+
colour.write(f'[BOLD:{repo}]')
|
|
113
|
+
else:
|
|
114
|
+
colour.write(f'[BOLD:{repo}] - branch: [BLUE:{branch}]')
|
|
115
|
+
|
|
116
|
+
staged = defaultdict(list)
|
|
117
|
+
unstaged = defaultdict(list)
|
|
118
|
+
untracked = []
|
|
119
|
+
|
|
120
|
+
for entry in status:
|
|
121
|
+
if entry[0] == '??':
|
|
122
|
+
untracked.append(entry[1])
|
|
123
|
+
elif entry[0][0] == 'M':
|
|
124
|
+
staged['Updated'].append(entry[1])
|
|
125
|
+
elif entry[0][0] == 'T':
|
|
126
|
+
staged['Type changed'].append(entry[1])
|
|
127
|
+
elif entry[0][0] == 'A':
|
|
128
|
+
staged['Added'].append(entry[1])
|
|
129
|
+
elif entry[0][0] == 'D':
|
|
130
|
+
staged['Deleted'].append(entry[1])
|
|
131
|
+
elif entry[0][0] == 'R':
|
|
132
|
+
staged['Renamed'].append(entry[1])
|
|
133
|
+
elif entry[0][0] == 'C':
|
|
134
|
+
staged['Copied'].append(entry[1])
|
|
135
|
+
elif entry[0][1] == 'M':
|
|
136
|
+
colour.write(f' WT Updated: [BLUE:{entry[1]}]')
|
|
137
|
+
elif entry[0][1] == 'T':
|
|
138
|
+
colour.write(f' WT Type changed: [BLUE:{entry[1]}]')
|
|
139
|
+
elif entry[0][1] == 'D':
|
|
140
|
+
unstaged['Deleted'].append(entry[1])
|
|
141
|
+
elif entry[0][1] == 'R':
|
|
142
|
+
colour.write(f' WT Renamed: [BLUE:{entry[1]}]')
|
|
143
|
+
elif entry[0][1] == 'C':
|
|
144
|
+
colour.write(f' WT Copied: [BLUE:{entry[1]}]')
|
|
145
|
+
else:
|
|
146
|
+
staged['Other'].append(f' {entry[0]}: [BLUE:{entry[1]}]')
|
|
147
|
+
|
|
148
|
+
if untracked:
|
|
149
|
+
colour.write()
|
|
150
|
+
colour.write('Untracked files:')
|
|
151
|
+
|
|
152
|
+
for git_object in untracked:
|
|
153
|
+
colour.write(f' [BLUE:{git_object}]')
|
|
154
|
+
|
|
155
|
+
if staged:
|
|
156
|
+
colour.write()
|
|
157
|
+
colour.write('Changes staged for commit:')
|
|
158
|
+
|
|
159
|
+
for item in staged:
|
|
160
|
+
for git_object in staged[item]:
|
|
161
|
+
colour.write(f' {item}: [BLUE:{git_object}]')
|
|
162
|
+
|
|
163
|
+
if unstaged:
|
|
164
|
+
colour.write()
|
|
165
|
+
colour.write('Changes not staged for commit:')
|
|
166
|
+
|
|
167
|
+
for item in unstaged:
|
|
168
|
+
for git_object in unstaged[item]:
|
|
169
|
+
colour.write(f' {item}: [BLUE:{git_object}]')
|
|
170
|
+
|
|
171
|
+
colour.write()
|
|
172
|
+
|
|
173
|
+
################################################################################
|
|
174
|
+
|
|
175
|
+
def mg_fetch(args, config, console):
|
|
176
|
+
"""Run git fetch everywhere"""
|
|
177
|
+
|
|
178
|
+
for repo in find_git_repos(args.directory, args.repos):
|
|
179
|
+
if not args.quiet:
|
|
180
|
+
show_progress(console.columns, repo)
|
|
181
|
+
|
|
182
|
+
colour.write(f'Fetching updates for [BLUE:{repo}]')
|
|
183
|
+
|
|
184
|
+
result = git.fetch(path=repo)
|
|
185
|
+
|
|
186
|
+
if result:
|
|
187
|
+
colour.write(f'[BOLD:{repo}]')
|
|
188
|
+
for item in result:
|
|
189
|
+
if item.startswith('From '):
|
|
190
|
+
colour.write(f' [BLUE:{item}]')
|
|
191
|
+
else:
|
|
192
|
+
colour.write(f' {item}')
|
|
193
|
+
|
|
194
|
+
colour.write()
|
|
195
|
+
|
|
196
|
+
################################################################################
|
|
197
|
+
|
|
198
|
+
def mg_pull(args, config, console):
|
|
199
|
+
"""Run git pull everywhere"""
|
|
200
|
+
|
|
201
|
+
for repo in find_git_repos(args.directory, args.repos):
|
|
202
|
+
if not args.quiet:
|
|
203
|
+
show_progress(console.columns, repo)
|
|
204
|
+
|
|
205
|
+
colour.write(f'Pulling updates for [BLUE:{repo}]')
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
result = git.pull(path=repo)
|
|
209
|
+
except git.GitError as exc:
|
|
210
|
+
error(f'Error in {repo}: {exc}')
|
|
211
|
+
|
|
212
|
+
if result and result[0] != 'Already up-to-date.':
|
|
213
|
+
colour.write(f'[BOLD:{repo}]')
|
|
214
|
+
for item in result:
|
|
215
|
+
if item.startswith('Updating'):
|
|
216
|
+
colour.write(f' [BLUE:{item}]')
|
|
217
|
+
else:
|
|
218
|
+
colour.write(f' {item}')
|
|
219
|
+
|
|
220
|
+
colour.write()
|
|
221
|
+
|
|
222
|
+
################################################################################
|
|
223
|
+
|
|
224
|
+
def mg_push(args, config, console):
|
|
225
|
+
"""Run git push everywhere where the current branch isn't one of the defaults
|
|
226
|
+
and where the most recent commit was the current user and was on the branch
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
# TODO: Add option for force-push?
|
|
230
|
+
# TODO: Add option for manual confirmation?
|
|
231
|
+
|
|
232
|
+
################################################################################
|
|
233
|
+
|
|
234
|
+
def mg_checkout(args, config, console):
|
|
235
|
+
"""Run git checkout everywhere.
|
|
236
|
+
By default it just checks out the specified branch (or the default branch)
|
|
237
|
+
if the branch exists in the repo.
|
|
238
|
+
If the 'create' option is specified then branch is created"""
|
|
239
|
+
|
|
240
|
+
# TODO: Add --create handling
|
|
241
|
+
|
|
242
|
+
for repo in find_git_repos(args.directory, args.repos):
|
|
243
|
+
if not args.quiet:
|
|
244
|
+
show_progress(console.columns, repo)
|
|
245
|
+
|
|
246
|
+
branch = args.branch or config[repo]['default branch']
|
|
247
|
+
|
|
248
|
+
if git.branch(path=repo) != branch:
|
|
249
|
+
console.write(f'Checking out [BLUE:{branch}] in [BLUE:{repo}]')
|
|
250
|
+
|
|
251
|
+
git.checkout(branch, path=repo)
|
|
252
|
+
|
|
253
|
+
################################################################################
|
|
254
|
+
|
|
255
|
+
def main():
|
|
256
|
+
"""Main function"""
|
|
257
|
+
|
|
258
|
+
commands = {
|
|
259
|
+
'init': mg_init,
|
|
260
|
+
'status': mg_status,
|
|
261
|
+
'fetch': mg_fetch,
|
|
262
|
+
'pull': mg_pull,
|
|
263
|
+
'push': mg_push,
|
|
264
|
+
'checkout': mg_checkout,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# Parse args in the form COMMAND OPTIONS SUBCOMMAND SUBCOMMAND_OPTIONS PARAMETERS
|
|
268
|
+
|
|
269
|
+
parser = argparse.ArgumentParser(description='Gitlab commands')
|
|
270
|
+
|
|
271
|
+
parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
|
|
272
|
+
parser.add_argument('--debug', '-d', action='store_true', help='Debug')
|
|
273
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
|
|
274
|
+
parser.add_argument('--quiet', '-q', action='store_true', help='Minimal console output')
|
|
275
|
+
parser.add_argument('--config', '-c', action='store', default=DEFAULT_CONFIG_FILE, help=f'The configuration file (defaults to {DEFAULT_CONFIG_FILE})')
|
|
276
|
+
parser.add_argument('--directory', '--dir', action='store', default='.', help='The top-level directory of the multigit tree (defaults to the current directory)')
|
|
277
|
+
parser.add_argument('--repos', '-r', action='append', default=None, help='The list of repo names to work on (defaults to all repos and can contain shell wildcards)')
|
|
278
|
+
|
|
279
|
+
subparsers = parser.add_subparsers(dest='command')
|
|
280
|
+
|
|
281
|
+
# Subcommands - currently just init, status, fetch, pull, push, with more to come
|
|
282
|
+
|
|
283
|
+
parser_init = subparsers.add_parser('init', help='Build or update the configuration file using the current branch in each repo as the default branch')
|
|
284
|
+
|
|
285
|
+
parser_status = subparsers.add_parser('status', help='Report git status in every repo that has something to report')
|
|
286
|
+
parser_fetch = subparsers.add_parser('fetch', help='Run git fetch in every repo')
|
|
287
|
+
parser_pull = subparsers.add_parser('pull', help='Run git pull in every repo')
|
|
288
|
+
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')
|
|
289
|
+
|
|
290
|
+
parser_checkout = subparsers.add_parser('checkout', help='Checkout the specified branch')
|
|
291
|
+
parser_checkout.add_argument('--create', action='store_true', help='Create the specified branch and check it out')
|
|
292
|
+
parser_checkout.add_argument('branch', nargs='?', default=None, action='store', help='The branch name to check out (defaults to the default branch)')
|
|
293
|
+
|
|
294
|
+
# Parse the command line
|
|
295
|
+
|
|
296
|
+
args = parser.parse_args()
|
|
297
|
+
|
|
298
|
+
# Basic error checking
|
|
299
|
+
|
|
300
|
+
if not args.command:
|
|
301
|
+
error('No command specified')
|
|
302
|
+
|
|
303
|
+
# TODO: If the config file isn't in the current directory then search up the directory tree for it but run in the current directory
|
|
304
|
+
|
|
305
|
+
# If the configuration file exists, read it
|
|
306
|
+
|
|
307
|
+
config = configparser.ConfigParser()
|
|
308
|
+
|
|
309
|
+
if os.path.isfile(args.config):
|
|
310
|
+
config.read(args.config)
|
|
311
|
+
|
|
312
|
+
# Get the console size
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
console = os.get_terminal_size()
|
|
316
|
+
except OSError:
|
|
317
|
+
console = None
|
|
318
|
+
args.quiet = True
|
|
319
|
+
|
|
320
|
+
# Run the subcommand
|
|
321
|
+
|
|
322
|
+
commands[args.command](args, config, console)
|
|
323
|
+
|
|
324
|
+
# Save the updated configuration file
|
|
325
|
+
|
|
326
|
+
if config:
|
|
327
|
+
with open(args.config, 'w') as configfile:
|
|
328
|
+
config.write(configfile)
|
|
329
|
+
|
|
330
|
+
################################################################################
|
|
331
|
+
|
|
332
|
+
def multigit():
|
|
333
|
+
"""Entry point"""
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
main()
|
|
337
|
+
except KeyboardInterrupt:
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
except BrokenPipeError:
|
|
340
|
+
sys.exit(2)
|
|
341
|
+
|
|
342
|
+
################################################################################
|
|
343
|
+
|
|
344
|
+
if __name__ == '__main__':
|
|
345
|
+
multigit()
|
|
@@ -203,7 +203,7 @@ def pull(repo=None, all=False, path=None):
|
|
|
203
203
|
|
|
204
204
|
################################################################################
|
|
205
205
|
|
|
206
|
-
def checkout(branch, create=False):
|
|
206
|
+
def checkout(branch, create=False, path=None):
|
|
207
207
|
""" Checkout a branch (optionally creating it) """
|
|
208
208
|
|
|
209
209
|
cmd = ['checkout']
|
|
@@ -213,7 +213,7 @@ def checkout(branch, create=False):
|
|
|
213
213
|
|
|
214
214
|
cmd.append(branch)
|
|
215
215
|
|
|
216
|
-
return git(cmd)
|
|
216
|
+
return git(cmd, path=path)
|
|
217
217
|
|
|
218
218
|
################################################################################
|
|
219
219
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.73
|
|
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
|
|
@@ -18,7 +18,6 @@ Requires-Dist: pyaml
|
|
|
18
18
|
Requires-Dist: pygit2
|
|
19
19
|
Requires-Dist: python-dateutil
|
|
20
20
|
Requires-Dist: requests
|
|
21
|
-
Requires-Dist: tomlkit
|
|
22
21
|
|
|
23
22
|
# Thingy
|
|
24
23
|
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""mg - MultiGit - utility for managing multiple Git repos in a hierarchical directory tree"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
import argparse
|
|
8
|
-
|
|
9
|
-
import tomlkit
|
|
10
|
-
|
|
11
|
-
import thingy.git2 as git
|
|
12
|
-
import thingy.colour as colour
|
|
13
|
-
|
|
14
|
-
################################################################################
|
|
15
|
-
|
|
16
|
-
"""Configuration file format:
|
|
17
|
-
|
|
18
|
-
[default]
|
|
19
|
-
# Default settings
|
|
20
|
-
default branch = name
|
|
21
|
-
|
|
22
|
-
[repos]
|
|
23
|
-
name = path
|
|
24
|
-
default branch = name
|
|
25
|
-
|
|
26
|
-
[git-repo-location] # Either absolute or relative to the directory where the configuration file is found
|
|
27
|
-
# Repo-specific settings to override default section
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
# TODO: -j option to run in parallel
|
|
31
|
-
# TODO: init function
|
|
32
|
-
# TODO: Use the configuration file
|
|
33
|
-
# TODO: Don't use a fixed list of default branch names
|
|
34
|
-
# TODO: Output name of each git repo as it is processed as command sits there seeming to do nothing otherwise.
|
|
35
|
-
|
|
36
|
-
################################################################################
|
|
37
|
-
|
|
38
|
-
DEFAULT_CONFIG_FILE = 'multigit.toml'
|
|
39
|
-
|
|
40
|
-
DEFAULT_BRANCHES = ('main', 'scv-poc', 'master')
|
|
41
|
-
|
|
42
|
-
################################################################################
|
|
43
|
-
|
|
44
|
-
def error(msg, status=1):
|
|
45
|
-
"""Quit with an error"""
|
|
46
|
-
|
|
47
|
-
sys.stderr.write(f'{msg}\n')
|
|
48
|
-
sys.exit(status)
|
|
49
|
-
|
|
50
|
-
################################################################################
|
|
51
|
-
|
|
52
|
-
def show_progress(width, msg):
|
|
53
|
-
"""Show a single line progress message"""
|
|
54
|
-
|
|
55
|
-
name = msg[:width-1]
|
|
56
|
-
|
|
57
|
-
colour.write(f'{name}', newline=False)
|
|
58
|
-
|
|
59
|
-
if len(name) < width-1:
|
|
60
|
-
colour.write(' '*(width-len(name)), newline=False)
|
|
61
|
-
|
|
62
|
-
colour.write('\r', newline=False)
|
|
63
|
-
|
|
64
|
-
################################################################################
|
|
65
|
-
|
|
66
|
-
def find_git_repos(directory):
|
|
67
|
-
"""Locate and return a list of '.git' directory parent directories in the
|
|
68
|
-
specified path"""
|
|
69
|
-
|
|
70
|
-
git_repos = []
|
|
71
|
-
|
|
72
|
-
for root, dirs, _ in os.walk(directory):
|
|
73
|
-
if '.git' in dirs:
|
|
74
|
-
git_repos.append(root)
|
|
75
|
-
|
|
76
|
-
return git_repos
|
|
77
|
-
|
|
78
|
-
################################################################################
|
|
79
|
-
|
|
80
|
-
def mg_init(args, config, console):
|
|
81
|
-
"""Create or update the configuration"""
|
|
82
|
-
|
|
83
|
-
error('Not used - yet!')
|
|
84
|
-
|
|
85
|
-
if config:
|
|
86
|
-
print(f'Updating existing multigit configuration file - {args.config}')
|
|
87
|
-
error('Not supported yet')
|
|
88
|
-
else:
|
|
89
|
-
print(f'Creating new multigit configuration file - {args.config}')
|
|
90
|
-
|
|
91
|
-
# Search for .git directories
|
|
92
|
-
|
|
93
|
-
git_repos = find_git_repos(args.directory)
|
|
94
|
-
|
|
95
|
-
################################################################################
|
|
96
|
-
|
|
97
|
-
def mg_status(args, config, console):
|
|
98
|
-
"""Report Git status for any repo that has a non-empty status"""
|
|
99
|
-
|
|
100
|
-
for repo in find_git_repos(args.directory):
|
|
101
|
-
if not args.quiet:
|
|
102
|
-
show_progress(console.columns, repo)
|
|
103
|
-
|
|
104
|
-
status = git.status(path=repo)
|
|
105
|
-
branch = git.branch(path=repo)
|
|
106
|
-
|
|
107
|
-
if status or branch not in DEFAULT_BRANCHES:
|
|
108
|
-
if branch in DEFAULT_BRANCHES:
|
|
109
|
-
colour.write(f'[BOLD:{repo}]')
|
|
110
|
-
else:
|
|
111
|
-
colour.write(f'[BOLD:{repo}] - branch: [BLUE:{branch}]')
|
|
112
|
-
|
|
113
|
-
for entry in status:
|
|
114
|
-
if entry[0] == '??':
|
|
115
|
-
colour.write(f' Untracked: [BLUE:{entry[1]}]')
|
|
116
|
-
else:
|
|
117
|
-
colour.write(f' [BLUE:{entry}]')
|
|
118
|
-
|
|
119
|
-
colour.write()
|
|
120
|
-
|
|
121
|
-
################################################################################
|
|
122
|
-
|
|
123
|
-
def mg_fetch(args, config, console):
|
|
124
|
-
"""Run git fetch everywhere"""
|
|
125
|
-
|
|
126
|
-
for repo in find_git_repos(args.directory):
|
|
127
|
-
if not args.quiet:
|
|
128
|
-
show_progress(console.columns, repo)
|
|
129
|
-
|
|
130
|
-
result = git.fetch(path=repo)
|
|
131
|
-
|
|
132
|
-
if result:
|
|
133
|
-
colour.write(f'[BOLD:{repo}]')
|
|
134
|
-
for item in result:
|
|
135
|
-
if item.startswith('From '):
|
|
136
|
-
colour.write(f' [BLUE:{item}]')
|
|
137
|
-
else:
|
|
138
|
-
colour.write(f' {item}')
|
|
139
|
-
|
|
140
|
-
colour.write()
|
|
141
|
-
|
|
142
|
-
################################################################################
|
|
143
|
-
|
|
144
|
-
def mg_pull(args, config, console):
|
|
145
|
-
"""Run git pull everywhere"""
|
|
146
|
-
|
|
147
|
-
for repo in find_git_repos(args.directory):
|
|
148
|
-
if not args.quiet:
|
|
149
|
-
show_progress(console.columns, repo)
|
|
150
|
-
|
|
151
|
-
try:
|
|
152
|
-
result = git.pull(path=repo)
|
|
153
|
-
except git.GitError as exc:
|
|
154
|
-
error(f'Error in {repo}: {exc}')
|
|
155
|
-
|
|
156
|
-
if result and result[0] != 'Already up-to-date.':
|
|
157
|
-
colour.write(f'[BOLD:{repo}]')
|
|
158
|
-
for item in result:
|
|
159
|
-
if item.startswith('Updating'):
|
|
160
|
-
colour.write(f' [BLUE:{item}]')
|
|
161
|
-
else:
|
|
162
|
-
colour.write(f' {item}')
|
|
163
|
-
|
|
164
|
-
colour.write()
|
|
165
|
-
|
|
166
|
-
################################################################################
|
|
167
|
-
|
|
168
|
-
def mg_push(args, config, console):
|
|
169
|
-
"""Run git push everywhere where the current branch isn't one of the defaults
|
|
170
|
-
and where the most recent commit was the current user and was on the branch
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
# TODO: Add option for force-push?
|
|
174
|
-
# TODO: Add option for manual confirmation?
|
|
175
|
-
|
|
176
|
-
pass
|
|
177
|
-
|
|
178
|
-
################################################################################
|
|
179
|
-
|
|
180
|
-
def main():
|
|
181
|
-
"""Main function"""
|
|
182
|
-
|
|
183
|
-
commands = {
|
|
184
|
-
'init': mg_init,
|
|
185
|
-
'status': mg_status,
|
|
186
|
-
'fetch': mg_fetch,
|
|
187
|
-
'pull': mg_pull,
|
|
188
|
-
'push': mg_push,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
# Parse args in the form COMMAND OPTIONS SUBCOMMAND SUBCOMMAND_OPTIONS PARAMETERS
|
|
192
|
-
|
|
193
|
-
parser = argparse.ArgumentParser(description='Gitlab commands')
|
|
194
|
-
|
|
195
|
-
parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
|
|
196
|
-
parser.add_argument('--debug', '-d', action='store_true', help='Debug')
|
|
197
|
-
parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
|
|
198
|
-
parser.add_argument('--quiet', '-q', action='store_true', help='Minimal console output')
|
|
199
|
-
parser.add_argument('--config', '-c', action='store', default=DEFAULT_CONFIG_FILE, help=f'The configuration file (defaults to {DEFAULT_CONFIG_FILE})')
|
|
200
|
-
parser.add_argument('--directory', '--dir', action='store', default='.', help='The top-level directory of the multigit tree (defaults to the current directory)')
|
|
201
|
-
|
|
202
|
-
subparsers = parser.add_subparsers(dest='command')
|
|
203
|
-
|
|
204
|
-
# Subcommands - currently just init, status, fetch, pull, push, with more to come
|
|
205
|
-
|
|
206
|
-
parser_init = subparsers.add_parser('init', help='')
|
|
207
|
-
|
|
208
|
-
parser_status = subparsers.add_parser('status', help='Report git status in every repo that has one')
|
|
209
|
-
parser_fetch = subparsers.add_parser('fetch', help='Run git fetch in every repo')
|
|
210
|
-
parser_pull = subparsers.add_parser('pull', help='Run git pull in every repo')
|
|
211
|
-
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')
|
|
212
|
-
|
|
213
|
-
# Parse the command line
|
|
214
|
-
|
|
215
|
-
args = parser.parse_args()
|
|
216
|
-
|
|
217
|
-
# If the configuration file exists, read it
|
|
218
|
-
|
|
219
|
-
config = tomlkit.loads(args.config) if os.path.isfile(args.config) else None
|
|
220
|
-
|
|
221
|
-
# Get the console size
|
|
222
|
-
|
|
223
|
-
console = os.get_terminal_size()
|
|
224
|
-
|
|
225
|
-
# Run the subcommand
|
|
226
|
-
|
|
227
|
-
commands[args.command](args, config, console)
|
|
228
|
-
|
|
229
|
-
################################################################################
|
|
230
|
-
|
|
231
|
-
def multigit():
|
|
232
|
-
"""Entry point"""
|
|
233
|
-
|
|
234
|
-
try:
|
|
235
|
-
main()
|
|
236
|
-
except KeyboardInterrupt:
|
|
237
|
-
sys.exit(1)
|
|
238
|
-
except BrokenPipeError:
|
|
239
|
-
sys.exit(2)
|
|
240
|
-
|
|
241
|
-
################################################################################
|
|
242
|
-
|
|
243
|
-
if __name__ == '__main__':
|
|
244
|
-
mg()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy/thingy/venv_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{skilleter_thingy-0.0.71 → skilleter_thingy-0.0.73}/skilleter_thingy.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|