skilleter-thingy 0.3.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skilleter_thingy/__init__.py +0 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/ffind.py +535 -0
- skilleter_thingy/ggit.py +88 -0
- skilleter_thingy/ggrep.py +155 -0
- skilleter_thingy/git_br.py +186 -0
- skilleter_thingy/git_ca.py +147 -0
- skilleter_thingy/git_cleanup.py +297 -0
- skilleter_thingy/git_co.py +227 -0
- skilleter_thingy/git_common.py +68 -0
- skilleter_thingy/git_hold.py +162 -0
- skilleter_thingy/git_parent.py +84 -0
- skilleter_thingy/git_retag.py +67 -0
- skilleter_thingy/git_review.py +1450 -0
- skilleter_thingy/git_update.py +398 -0
- skilleter_thingy/git_wt.py +72 -0
- skilleter_thingy/gitcmp_helper.py +328 -0
- skilleter_thingy/gitprompt.py +293 -0
- skilleter_thingy/linecount.py +154 -0
- skilleter_thingy/multigit.py +915 -0
- skilleter_thingy/py_audit.py +133 -0
- skilleter_thingy/remdir.py +127 -0
- skilleter_thingy/rpylint.py +98 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/test.py +34 -0
- skilleter_thingy/tfm.py +948 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/trimpath.py +82 -0
- skilleter_thingy/venv_create.py +47 -0
- skilleter_thingy/venv_template.py +47 -0
- skilleter_thingy/xchmod.py +124 -0
- skilleter_thingy/yamlcheck.py +89 -0
- skilleter_thingy-0.3.14.dist-info/METADATA +606 -0
- skilleter_thingy-0.3.14.dist-info/RECORD +39 -0
- skilleter_thingy-0.3.14.dist-info/WHEEL +5 -0
- skilleter_thingy-0.3.14.dist-info/entry_points.txt +31 -0
- skilleter_thingy-0.3.14.dist-info/licenses/LICENSE +619 -0
- skilleter_thingy-0.3.14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Thingy 'git-cleanup' command - list or delete branches that have been merged.
|
|
5
|
+
|
|
6
|
+
Author: John Skilleter
|
|
7
|
+
|
|
8
|
+
Licence: GPL v3 or later
|
|
9
|
+
"""
|
|
10
|
+
################################################################################
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import argparse
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from skilleter_modules import git
|
|
18
|
+
from skilleter_modules import colour
|
|
19
|
+
|
|
20
|
+
################################################################################
|
|
21
|
+
# Constants
|
|
22
|
+
|
|
23
|
+
# Branches that we will never delete
|
|
24
|
+
|
|
25
|
+
PROTECTED_BRANCHES = ['develop', 'master', 'main', 'release', 'hotfix']
|
|
26
|
+
|
|
27
|
+
################################################################################
|
|
28
|
+
|
|
29
|
+
def parse_command_line():
|
|
30
|
+
""" Parse the command line, returning the arguments """
|
|
31
|
+
|
|
32
|
+
parser = argparse.ArgumentParser(
|
|
33
|
+
description='List or delete branches that have been merged.\nWhen deleting branches, also delete tracking branches that are not longer on the remote.')
|
|
34
|
+
|
|
35
|
+
parser.add_argument('--delete', '-d', action='store_true', dest='delete', help='Delete all branches that have been merged')
|
|
36
|
+
parser.add_argument('--master', '-m', '--main', dest='master',
|
|
37
|
+
help='Specify the master branch (Attempts to read this from GitLab or defaults to "develop" if present or "master" or "main" otherwise')
|
|
38
|
+
parser.add_argument('--force', '-f', action='store_true', dest='force', help='Allow protected branches (e.g. master) to be removed')
|
|
39
|
+
parser.add_argument('--unmerged', '-u', action='store_true', dest='list_unmerged', help='List branches that have NOT been merged')
|
|
40
|
+
parser.add_argument('--yes', '-y', action='store_true', dest='force', help='Assume "yes" in response to any prompts (e.g. to delete branches)')
|
|
41
|
+
parser.add_argument('--debug', action='store_true', help='Enable debug output')
|
|
42
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
43
|
+
help='Run the command in the specified directory')
|
|
44
|
+
|
|
45
|
+
parser.add_argument('branches', nargs='*', help='List of branches to check (default is all branches)')
|
|
46
|
+
|
|
47
|
+
return parser.parse_args()
|
|
48
|
+
|
|
49
|
+
################################################################################
|
|
50
|
+
|
|
51
|
+
def validate_options(args, all_branches):
|
|
52
|
+
""" Check that the command line options make sense """
|
|
53
|
+
|
|
54
|
+
# If the master branch has not been specified try to get it from GitLab and then default to either 'develop', 'main', or 'master'
|
|
55
|
+
|
|
56
|
+
if not args.master:
|
|
57
|
+
args.master = git.default_branch()
|
|
58
|
+
|
|
59
|
+
if not args.master:
|
|
60
|
+
if 'develop' in all_branches:
|
|
61
|
+
args.master = 'develop'
|
|
62
|
+
elif 'main' in all_branches:
|
|
63
|
+
args.master = 'main'
|
|
64
|
+
elif 'master' in all_branches:
|
|
65
|
+
args.master = 'master'
|
|
66
|
+
else:
|
|
67
|
+
colour.error('You must specify a master branch as the repo contains no obvious master branch')
|
|
68
|
+
|
|
69
|
+
# Check that the master branch actually exists
|
|
70
|
+
|
|
71
|
+
if args.master not in all_branches:
|
|
72
|
+
colour.error('The "%s" branch does not exist in the repo' % args.master)
|
|
73
|
+
|
|
74
|
+
# Check that the user isn't trying to remove a branch that is normally sacrosanct
|
|
75
|
+
|
|
76
|
+
if not args.force and args.branches:
|
|
77
|
+
for branch in all_branches:
|
|
78
|
+
if branch in PROTECTED_BRANCHES:
|
|
79
|
+
colour.error('You must use the "--force" option to delete protected branches (%s)' % ', '.join(PROTECTED_BRANCHES))
|
|
80
|
+
|
|
81
|
+
# If no list of branches to check has been specified, use all the branches
|
|
82
|
+
|
|
83
|
+
if not args.branches:
|
|
84
|
+
args.branches = all_branches
|
|
85
|
+
|
|
86
|
+
################################################################################
|
|
87
|
+
|
|
88
|
+
def main():
|
|
89
|
+
""" Entry point """
|
|
90
|
+
|
|
91
|
+
# Handle the command line
|
|
92
|
+
|
|
93
|
+
args = parse_command_line()
|
|
94
|
+
|
|
95
|
+
# Enable logging if requested
|
|
96
|
+
|
|
97
|
+
if args.debug:
|
|
98
|
+
logging.basicConfig(level=logging.INFO)
|
|
99
|
+
|
|
100
|
+
# Change directory, if specified
|
|
101
|
+
|
|
102
|
+
if args.path:
|
|
103
|
+
os.chdir(args.path[0])
|
|
104
|
+
|
|
105
|
+
# Get the list of all local branches
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
all_branches = git.branches()
|
|
109
|
+
except git.GitError as exc:
|
|
110
|
+
colour.error(exc.msg, status=exc.status)
|
|
111
|
+
|
|
112
|
+
logging.info('Branches=%s', all_branches)
|
|
113
|
+
|
|
114
|
+
# Check that the command line options are sensible, including the list of branches (if any)
|
|
115
|
+
|
|
116
|
+
validate_options(args, all_branches)
|
|
117
|
+
|
|
118
|
+
# Checkout and update the master branch then switch back
|
|
119
|
+
|
|
120
|
+
logging.info('Checking out %s branch', args.master)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
git.checkout(args.master)
|
|
124
|
+
|
|
125
|
+
logging.info('Running git pull')
|
|
126
|
+
|
|
127
|
+
git.pull()
|
|
128
|
+
|
|
129
|
+
logging.info('Checking out previous branch')
|
|
130
|
+
|
|
131
|
+
git.checkout('-')
|
|
132
|
+
|
|
133
|
+
except git.GitError as exc:
|
|
134
|
+
colour.error(exc.msg, status=exc.status)
|
|
135
|
+
|
|
136
|
+
# 'reported' is True when we've reported something so we can put a blank line before the
|
|
137
|
+
# next item (if there is one).
|
|
138
|
+
|
|
139
|
+
reported = False
|
|
140
|
+
|
|
141
|
+
# List of branches that we will delete (if we aren't just listing possibilities)
|
|
142
|
+
|
|
143
|
+
logging.info('Determining whether any branches can be deleted (not master branch, not protected and no outstanding commits)')
|
|
144
|
+
|
|
145
|
+
branches_to_delete = []
|
|
146
|
+
|
|
147
|
+
# Iterate through the branches, ignoring protected branches and the current master
|
|
148
|
+
|
|
149
|
+
for branch in args.branches:
|
|
150
|
+
if branch not in PROTECTED_BRANCHES and branch not in args.master:
|
|
151
|
+
|
|
152
|
+
# Has the branch got commits that haven't been merged to the master branch?
|
|
153
|
+
|
|
154
|
+
logging.info('Checking for unmerged commits on %s (against %s)', branch, args.master)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
unmerged = git.git(['log', '--no-merges', '--oneline', branch, '^%s' % args.master, '--'])
|
|
158
|
+
except git.GitError as exc:
|
|
159
|
+
sys.stderr.write('%s\n' % exc.msg)
|
|
160
|
+
sys.exit(exc.status)
|
|
161
|
+
|
|
162
|
+
# Either mark merged branches to be deleted or list unmerged or merged ones
|
|
163
|
+
|
|
164
|
+
if args.delete:
|
|
165
|
+
# Mark the branch as deleteable if the branch doesn't have unmerged commits
|
|
166
|
+
# and it either isn't protected or we're forcing
|
|
167
|
+
|
|
168
|
+
if not unmerged and (args.force or branch not in PROTECTED_BRANCHES):
|
|
169
|
+
logging.info('Branch %s can be deleted', branch)
|
|
170
|
+
|
|
171
|
+
branches_to_delete.append(branch)
|
|
172
|
+
|
|
173
|
+
elif args.list_unmerged:
|
|
174
|
+
if unmerged:
|
|
175
|
+
# if the branch has commits that are not on the master branch then list it as unmerged
|
|
176
|
+
|
|
177
|
+
if reported:
|
|
178
|
+
print()
|
|
179
|
+
else:
|
|
180
|
+
colour.write('Branches that have not been merged to [BLUE:%s]:' % args.master)
|
|
181
|
+
|
|
182
|
+
colour.write(' [BLUE:%s]: [BOLD:%d] unmerged commits' % (branch, len(unmerged)))
|
|
183
|
+
|
|
184
|
+
for commit in unmerged:
|
|
185
|
+
print(' %s' % commit)
|
|
186
|
+
|
|
187
|
+
reported = True
|
|
188
|
+
|
|
189
|
+
elif not unmerged:
|
|
190
|
+
# If the branch hasn't got unique commits then it has been merged (or is empty)
|
|
191
|
+
|
|
192
|
+
if not reported:
|
|
193
|
+
colour.write('Branches that have %sbeen merged to [BLUE:%s]' % ('not ' if args.list_unmerged else '', args.master))
|
|
194
|
+
|
|
195
|
+
colour.write(' [BLUE:%s]' % branch)
|
|
196
|
+
|
|
197
|
+
reported = True
|
|
198
|
+
|
|
199
|
+
# If we have branches to delete then delete them
|
|
200
|
+
|
|
201
|
+
if args.delete:
|
|
202
|
+
if branches_to_delete:
|
|
203
|
+
|
|
204
|
+
logging.info('Deleting branch(es): %s', branches_to_delete)
|
|
205
|
+
|
|
206
|
+
if not args.force:
|
|
207
|
+
colour.write('The following branches have already been merged to the [BLUE:%s] branch and can be deleted:' % args.master)
|
|
208
|
+
for branch in branches_to_delete:
|
|
209
|
+
colour.write(' [BLUE:%s]' % branch)
|
|
210
|
+
|
|
211
|
+
print()
|
|
212
|
+
confirm = input('Are you sure that you want to delete these branches? ')
|
|
213
|
+
|
|
214
|
+
if confirm.lower() not in ('y', 'yes'):
|
|
215
|
+
colour.error('**aborted**')
|
|
216
|
+
|
|
217
|
+
print()
|
|
218
|
+
|
|
219
|
+
# Delete the branches, switching to the master branch before attempting to delete the current one
|
|
220
|
+
|
|
221
|
+
for branch in branches_to_delete:
|
|
222
|
+
if branch == git.branch():
|
|
223
|
+
colour.write('Switching to [BLUE:%s] branch before deleting current branch.' % args.master)
|
|
224
|
+
git.checkout(args.master)
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
logging.info('Deleting %s', branch)
|
|
228
|
+
|
|
229
|
+
git.delete_branch(branch, force=True)
|
|
230
|
+
except git.GitError as exc:
|
|
231
|
+
colour.error(str(exc), status=exc.status)
|
|
232
|
+
|
|
233
|
+
colour.write('Deleted [BLUE:%s]' % branch)
|
|
234
|
+
else:
|
|
235
|
+
colour.write('There are no branches that have been merged to the [BLUE:%s] branch.' % args.master)
|
|
236
|
+
|
|
237
|
+
# Finally run remote pruning (note that we don't have an option to
|
|
238
|
+
# list branches that *can't* be pruned (yet))
|
|
239
|
+
|
|
240
|
+
reported = False
|
|
241
|
+
prunable = False
|
|
242
|
+
|
|
243
|
+
# Look for prunable branches and report them
|
|
244
|
+
|
|
245
|
+
logging.info('Looking for remote tracking branches that can be pruned')
|
|
246
|
+
|
|
247
|
+
for remote in git.remotes():
|
|
248
|
+
for prune in git.remote_prune(remote, dry_run=True):
|
|
249
|
+
if not reported:
|
|
250
|
+
print()
|
|
251
|
+
if args.force:
|
|
252
|
+
print('Deleting remote tracking branches:')
|
|
253
|
+
else:
|
|
254
|
+
print('Remote tracking branches that can be deleted:')
|
|
255
|
+
reported = True
|
|
256
|
+
|
|
257
|
+
colour.write(' [BLUE:%s]' % prune)
|
|
258
|
+
prunable = True
|
|
259
|
+
|
|
260
|
+
# If we are deleting things and have things to delete then delete things
|
|
261
|
+
|
|
262
|
+
if args.delete and prunable:
|
|
263
|
+
if not args.force:
|
|
264
|
+
print()
|
|
265
|
+
confirm = input('Are you sure that you want to prune these branches? ')
|
|
266
|
+
|
|
267
|
+
if confirm.lower() not in ('y', 'yes'):
|
|
268
|
+
colour.error('**aborted**')
|
|
269
|
+
|
|
270
|
+
print()
|
|
271
|
+
|
|
272
|
+
for remote in git.remotes():
|
|
273
|
+
logging.info('Pruning remote branches from %s', remote)
|
|
274
|
+
|
|
275
|
+
pruned = git.remote_prune(remote)
|
|
276
|
+
|
|
277
|
+
for branch in pruned:
|
|
278
|
+
colour.write('Deleted [BLUE:%s]' % branch)
|
|
279
|
+
|
|
280
|
+
################################################################################
|
|
281
|
+
|
|
282
|
+
def git_cleanup():
|
|
283
|
+
"""Entry point"""
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
main()
|
|
287
|
+
except KeyboardInterrupt:
|
|
288
|
+
sys.exit(1)
|
|
289
|
+
except BrokenPipeError:
|
|
290
|
+
sys.exit(2)
|
|
291
|
+
except git.GitError as exc:
|
|
292
|
+
colour.error(exc.msg, status=exc.status, prefix=True)
|
|
293
|
+
|
|
294
|
+
################################################################################
|
|
295
|
+
|
|
296
|
+
if __name__ == '__main__':
|
|
297
|
+
git_cleanup()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Enhanced version of 'git checkout'
|
|
5
|
+
|
|
6
|
+
Currently only supports the '-b' option in addition to the default
|
|
7
|
+
behaviour (may be extended to other options later, but otherwise, just
|
|
8
|
+
use the 'git checkout' command as normal.
|
|
9
|
+
|
|
10
|
+
Differs from standard checkout in that if the branch name specified is not
|
|
11
|
+
an exact match for an existing local or remote branch it will look for
|
|
12
|
+
branches where the specified name is a substring (e.g. '12345' will match
|
|
13
|
+
'feature/fix-1234567') and, if there is a unique match, it will check that
|
|
14
|
+
out. If there are multiple matches it will just list them.
|
|
15
|
+
|
|
16
|
+
Note - partial matching ONLY works for branch names - tag names only
|
|
17
|
+
do full matching and commits only match against the start of the SHA1
|
|
18
|
+
|
|
19
|
+
TODO: Should prioritise branch names over SHA1 - for instance git co 69772
|
|
20
|
+
"""
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import logging
|
|
25
|
+
import sys
|
|
26
|
+
import argparse
|
|
27
|
+
|
|
28
|
+
from skilleter_modules import git
|
|
29
|
+
from skilleter_modules import colour
|
|
30
|
+
|
|
31
|
+
assert sys.version_info.major >= 3 and sys.version_info.minor >= 6
|
|
32
|
+
|
|
33
|
+
################################################################################
|
|
34
|
+
|
|
35
|
+
DESCRIPTION = \
|
|
36
|
+
"""
|
|
37
|
+
Enhanced version of 'git checkout'
|
|
38
|
+
|
|
39
|
+
Differs from standard checkout in that if the branch name specified is
|
|
40
|
+
not an exact match for an existing branch it will look for branches
|
|
41
|
+
where the specified name is a substring (e.g. '12345' will match
|
|
42
|
+
'feature/fix-1234567')
|
|
43
|
+
|
|
44
|
+
If there is a single match, it will check that out.
|
|
45
|
+
|
|
46
|
+
If there are multiple matches it will just list them.
|
|
47
|
+
|
|
48
|
+
If no local branches match, it will match against remote branches.
|
|
49
|
+
|
|
50
|
+
If no matching branches exist will will try commit IDs or tags.
|
|
51
|
+
|
|
52
|
+
Currently only supports the '-b' option in addition to the default
|
|
53
|
+
behaviour (may be extended to other options later, but otherwise, just
|
|
54
|
+
use the 'git checkout' command as normal).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
################################################################################
|
|
58
|
+
|
|
59
|
+
def parse_arguments():
|
|
60
|
+
""" Parse and return command line arguments """
|
|
61
|
+
|
|
62
|
+
parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter, epilog='Note that the "owner" of a branch is deemed to be the person who made the most-recent commit on that branch')
|
|
63
|
+
parser.add_argument('--branch', '-b', action='store_true', help='Create the specified branch')
|
|
64
|
+
parser.add_argument('--update', '-u', action='store_true', help='If a remote branch exists, and the branch isn\'t owned by the current user, delete any local branch and check out the remote version')
|
|
65
|
+
parser.add_argument('--rebase', '-r', action='store_true', help='Rebase the branch onto its parent after checking it out')
|
|
66
|
+
parser.add_argument('--force', '-f', action='store_true',
|
|
67
|
+
help='When using the update option, recreate the local branch even if it is owned by the current user')
|
|
68
|
+
parser.add_argument('--exact', '-e', action='store_true', help='Do not use branch name matching - check out the branch as specified (if it exists)')
|
|
69
|
+
parser.add_argument('--debug', action='store_true', help='Enable debug output')
|
|
70
|
+
parser.add_argument('branchname', nargs=1, type=str,
|
|
71
|
+
help='The branch name (or a partial name that matches uniquely against a local branch, remote branch, commit ID or tag)')
|
|
72
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
73
|
+
help='Run the command in the specified directory')
|
|
74
|
+
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
|
|
77
|
+
# Enable logging if requested
|
|
78
|
+
|
|
79
|
+
if args.debug:
|
|
80
|
+
logging.basicConfig(level=logging.INFO)
|
|
81
|
+
|
|
82
|
+
if args.path:
|
|
83
|
+
os.chdir(args.path[0])
|
|
84
|
+
|
|
85
|
+
if args.force and not args.update:
|
|
86
|
+
colour.error('The --force option only works with the --update option')
|
|
87
|
+
|
|
88
|
+
return args
|
|
89
|
+
|
|
90
|
+
################################################################################
|
|
91
|
+
|
|
92
|
+
def checkout_matching_branch(args, branchname):
|
|
93
|
+
""" Look for a commit matching the specified name and check it out if it is
|
|
94
|
+
an exact match or there is only one partial match.
|
|
95
|
+
If there are multiple branches that match, just list them
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# If we are doing an update, then make sure we have all the remote info up-to-date
|
|
99
|
+
|
|
100
|
+
if args.update:
|
|
101
|
+
colour.write('Fetching updates from remote server(s)')
|
|
102
|
+
git.fetch(all=True)
|
|
103
|
+
|
|
104
|
+
# Get the list of matching commits.
|
|
105
|
+
# * If an exact match to the branch, tag or SHA branch exists use it without checking partial matches
|
|
106
|
+
# * Otherwise, unless --exact was specified, check for a partial match
|
|
107
|
+
|
|
108
|
+
if git.iscommit(branchname, remote=True):
|
|
109
|
+
commits = [branchname]
|
|
110
|
+
|
|
111
|
+
logging.info('Exact match found for %s', branchname)
|
|
112
|
+
elif not args.exact:
|
|
113
|
+
commits = git.matching_branch(branchname)
|
|
114
|
+
|
|
115
|
+
if not commits:
|
|
116
|
+
commits = git.matching_commit(branchname)
|
|
117
|
+
|
|
118
|
+
logging.info('Commits matching %s = %s', branchname, commits)
|
|
119
|
+
|
|
120
|
+
# Can't do anything with multiple/no matching commits/branches
|
|
121
|
+
|
|
122
|
+
if not commits:
|
|
123
|
+
colour.error(f'[BOLD]No branches or commits matching [NORMAL][BLUE]{branchname}[NORMAL]')
|
|
124
|
+
elif len(commits) > 1:
|
|
125
|
+
colour.write(f'[RED:ERROR]: [BOLD]Multiple matches for [NORMAL][BLUE]{branchname}[NORMAL]:')
|
|
126
|
+
for item in commits:
|
|
127
|
+
colour.write(f' {item}')
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
# If we have one match, then we can do stuff
|
|
131
|
+
|
|
132
|
+
logging.info('Only one matching commit: %s', commits[0])
|
|
133
|
+
|
|
134
|
+
commit = commits[0]
|
|
135
|
+
|
|
136
|
+
if args.update:
|
|
137
|
+
# TODO: Should check all remotes if more than one
|
|
138
|
+
|
|
139
|
+
remote = git.remote_names()[0]
|
|
140
|
+
|
|
141
|
+
if commit.startswith(f'{remote}/'):
|
|
142
|
+
remote_branch = commit
|
|
143
|
+
else:
|
|
144
|
+
remote_branch = f'remotes/{remote}/{commit}'
|
|
145
|
+
|
|
146
|
+
logging.info('Remote branch: %s', remote_branch)
|
|
147
|
+
|
|
148
|
+
# If the remote branch exists, then update, delete the local branch and re-create it
|
|
149
|
+
|
|
150
|
+
if git.isbranch(remote_branch):
|
|
151
|
+
logging.info('Remote branch exists')
|
|
152
|
+
|
|
153
|
+
default_branch = git.default_branch()
|
|
154
|
+
|
|
155
|
+
colour.write(f'Updating the [BLUE:{default_branch}] branch')
|
|
156
|
+
|
|
157
|
+
git.checkout(default_branch)
|
|
158
|
+
git.merge(f'{remote}/{default_branch}')
|
|
159
|
+
|
|
160
|
+
# If the local branch exists, delete it
|
|
161
|
+
|
|
162
|
+
# TODO: Should prompt rather than using force
|
|
163
|
+
|
|
164
|
+
if git.isbranch(commit):
|
|
165
|
+
logging.info('Local branch %s exists', commit)
|
|
166
|
+
|
|
167
|
+
# Don't overwrite our own branches, just to be on the safe side
|
|
168
|
+
|
|
169
|
+
if not args.force:
|
|
170
|
+
author = git.author(commit)
|
|
171
|
+
if author == git.config_get('user', 'name'):
|
|
172
|
+
colour.write(f'ERROR: Most recent commit on {commit} is {author} - Use the --force option to force-update your own branch!')
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
colour.write('Removing existing [BLUE:{commit}] branch')
|
|
176
|
+
git.delete_branch(commit, force=True)
|
|
177
|
+
else:
|
|
178
|
+
colour.write(f'No corresponding remote branch [BLUE:{remote_branch}] exists')
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
# Check out the commit and report the name (branch, tag, or if nowt else, commit ID)
|
|
182
|
+
|
|
183
|
+
logging.info('Checking out %s', commit)
|
|
184
|
+
|
|
185
|
+
git.checkout(commit)
|
|
186
|
+
colour.write('[BOLD]Checked out [NORMAL][BLUE]%s[NORMAL]' % (git.branch() or git.tag() or git.current_commit()))
|
|
187
|
+
|
|
188
|
+
if args.rebase:
|
|
189
|
+
colour.write('Rebasing branch against its parent')
|
|
190
|
+
|
|
191
|
+
output = git.update()
|
|
192
|
+
|
|
193
|
+
for text in output:
|
|
194
|
+
print(text)
|
|
195
|
+
|
|
196
|
+
################################################################################
|
|
197
|
+
|
|
198
|
+
def main():
|
|
199
|
+
""" Main function - parse the command line and create or attempt to checkout
|
|
200
|
+
the specified branch """
|
|
201
|
+
|
|
202
|
+
args = parse_arguments()
|
|
203
|
+
|
|
204
|
+
if args.branch:
|
|
205
|
+
git.checkout(args.branchname[0], create=True)
|
|
206
|
+
else:
|
|
207
|
+
checkout_matching_branch(args, args.branchname[0])
|
|
208
|
+
|
|
209
|
+
################################################################################
|
|
210
|
+
|
|
211
|
+
def git_co():
|
|
212
|
+
"""Entry point"""
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
main()
|
|
216
|
+
|
|
217
|
+
except KeyboardInterrupt:
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
except BrokenPipeError:
|
|
220
|
+
sys.exit(2)
|
|
221
|
+
except git.GitError as exc:
|
|
222
|
+
colour.error(exc.msg, status=exc.status, prefix=True)
|
|
223
|
+
|
|
224
|
+
################################################################################
|
|
225
|
+
|
|
226
|
+
if __name__ == '__main__':
|
|
227
|
+
git_co()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Report the oldest commit in common in the history of two commits
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
################################################################################
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
from skilleter_modules import colour
|
|
14
|
+
from skilleter_modules import git
|
|
15
|
+
|
|
16
|
+
################################################################################
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
""" Main function """
|
|
20
|
+
|
|
21
|
+
parser = argparse.ArgumentParser(description='Find the most recent common ancestor for two commits')
|
|
22
|
+
|
|
23
|
+
parser.add_argument('--short', '-s', action='store_true', help='Just output the ancestor commit ID')
|
|
24
|
+
parser.add_argument('--long', '-l', action='store_true', help='Output the log entry for the commit')
|
|
25
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
26
|
+
help='Run the command in the specified directory')
|
|
27
|
+
parser.add_argument('commit1', nargs='?', default='HEAD', help='First commit (default=HEAD)')
|
|
28
|
+
parser.add_argument('commit2', nargs='?', default='master', help='Second commit (default=master)')
|
|
29
|
+
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
|
|
32
|
+
if args.long and args.short:
|
|
33
|
+
colour.error('The [BLUE:--long] and [BLUE:--short] options cannot be used together', prefix=True)
|
|
34
|
+
|
|
35
|
+
# Change directory, if specified
|
|
36
|
+
|
|
37
|
+
if args.path:
|
|
38
|
+
os.chdir(args.path[0])
|
|
39
|
+
|
|
40
|
+
ancestor = git.find_common_ancestor(args.commit1, args.commit2)
|
|
41
|
+
|
|
42
|
+
if args.short:
|
|
43
|
+
print(ancestor)
|
|
44
|
+
elif args.long:
|
|
45
|
+
print('\n'.join(git.log(ancestor)))
|
|
46
|
+
else:
|
|
47
|
+
colour.write(f'Last common commit between [BLUE:{args.commit1}] and [BLUE:{args.commit2}] is [BLUE:{ancestor}]')
|
|
48
|
+
|
|
49
|
+
################################################################################
|
|
50
|
+
# Entry point
|
|
51
|
+
|
|
52
|
+
def git_common():
|
|
53
|
+
"""Entry point"""
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
main()
|
|
57
|
+
|
|
58
|
+
except KeyboardInterrupt:
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
except BrokenPipeError:
|
|
61
|
+
sys.exit(2)
|
|
62
|
+
except git.GitError as exc:
|
|
63
|
+
colour.error(exc.msg, status=exc.status, prefix=True)
|
|
64
|
+
|
|
65
|
+
################################################################################
|
|
66
|
+
|
|
67
|
+
if __name__ == '__main__':
|
|
68
|
+
git_common()
|