skilleter-thingy 0.0.22__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/__init__.py +0 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/aws.py +548 -0
- skilleter_thingy/borger.py +269 -0
- skilleter_thingy/colour.py +213 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/dc_curses.py +278 -0
- skilleter_thingy/dc_defaults.py +221 -0
- skilleter_thingy/dc_util.py +50 -0
- skilleter_thingy/dircolors.py +308 -0
- skilleter_thingy/diskspacecheck.py +67 -0
- skilleter_thingy/docker.py +95 -0
- skilleter_thingy/docker_purge.py +113 -0
- skilleter_thingy/ffind.py +536 -0
- skilleter_thingy/files.py +142 -0
- skilleter_thingy/ggit.py +90 -0
- skilleter_thingy/ggrep.py +154 -0
- skilleter_thingy/git.py +1368 -0
- skilleter_thingy/git2.py +1307 -0
- skilleter_thingy/git_br.py +180 -0
- skilleter_thingy/git_ca.py +142 -0
- skilleter_thingy/git_cleanup.py +287 -0
- skilleter_thingy/git_co.py +220 -0
- skilleter_thingy/git_common.py +61 -0
- skilleter_thingy/git_hold.py +154 -0
- skilleter_thingy/git_mr.py +92 -0
- skilleter_thingy/git_parent.py +77 -0
- skilleter_thingy/git_review.py +1416 -0
- skilleter_thingy/git_update.py +385 -0
- skilleter_thingy/git_wt.py +96 -0
- skilleter_thingy/gitcmp_helper.py +322 -0
- skilleter_thingy/gitlab.py +193 -0
- skilleter_thingy/gitprompt.py +274 -0
- skilleter_thingy/gl.py +174 -0
- skilleter_thingy/gphotosync.py +610 -0
- skilleter_thingy/linecount.py +155 -0
- skilleter_thingy/logger.py +112 -0
- skilleter_thingy/moviemover.py +133 -0
- skilleter_thingy/path.py +156 -0
- skilleter_thingy/photodupe.py +110 -0
- skilleter_thingy/phototidier.py +248 -0
- skilleter_thingy/popup.py +87 -0
- skilleter_thingy/process.py +112 -0
- skilleter_thingy/py_audit.py +131 -0
- skilleter_thingy/readable.py +270 -0
- skilleter_thingy/remdir.py +126 -0
- skilleter_thingy/rmdupe.py +550 -0
- skilleter_thingy/rpylint.py +91 -0
- skilleter_thingy/run.py +334 -0
- skilleter_thingy/s3_sync.py +383 -0
- skilleter_thingy/splitpics.py +99 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/sysmon.py +435 -0
- skilleter_thingy/tfm.py +920 -0
- skilleter_thingy/tfm_pane.py +595 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/tidy.py +160 -0
- skilleter_thingy/trimpath.py +84 -0
- skilleter_thingy/window_rename.py +92 -0
- skilleter_thingy/xchmod.py +125 -0
- skilleter_thingy/yamlcheck.py +89 -0
- skilleter_thingy-0.0.22.dist-info/LICENSE +619 -0
- skilleter_thingy-0.0.22.dist-info/METADATA +22 -0
- skilleter_thingy-0.0.22.dist-info/RECORD +67 -0
- skilleter_thingy-0.0.22.dist-info/WHEEL +5 -0
- skilleter_thingy-0.0.22.dist-info/entry_points.txt +43 -0
- skilleter_thingy-0.0.22.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Script invoked via the 'git cmp' alias, which sets GIT_EXTERNAL_DIFF so that
|
|
5
|
+
this is called instead of using git diff.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2017-18 John Skilleter
|
|
8
|
+
|
|
9
|
+
Parameters passed by git are:
|
|
10
|
+
|
|
11
|
+
For a normal diff between two versions of a file in Git:
|
|
12
|
+
|
|
13
|
+
N MEANING EXAMPLE
|
|
14
|
+
1 file path tgy_git.py
|
|
15
|
+
|
|
16
|
+
2 tmp old file /tmp/OjfLZ8_tgy_git.py
|
|
17
|
+
3 old SHA1 027422b8b6e945227b27abf4161ad38c5b6e9ff9
|
|
18
|
+
4 old perm 100644
|
|
19
|
+
|
|
20
|
+
5 tmp new file tgy_git.py
|
|
21
|
+
6 new SHA1 0000000000000000000000000000000000000000
|
|
22
|
+
7 new perm 100644
|
|
23
|
+
|
|
24
|
+
If the path is unmerged, only parameter #1 is passed
|
|
25
|
+
|
|
26
|
+
Also sets:
|
|
27
|
+
|
|
28
|
+
GIT_DIFF_PATH_COUNTER - incremented for each file compared
|
|
29
|
+
GIT_DIFF_PATH_TOTAL - total number of paths to be compared
|
|
30
|
+
|
|
31
|
+
If GIT_SUBDIR is set in the environment it indicates the relative
|
|
32
|
+
path of the current directory from the top-level directory of the
|
|
33
|
+
working tree.
|
|
34
|
+
"""
|
|
35
|
+
################################################################################
|
|
36
|
+
|
|
37
|
+
################################################################################
|
|
38
|
+
# Imports
|
|
39
|
+
|
|
40
|
+
import sys
|
|
41
|
+
import os
|
|
42
|
+
import argparse
|
|
43
|
+
import filecmp
|
|
44
|
+
import re
|
|
45
|
+
|
|
46
|
+
from skilleter_thingy import logger
|
|
47
|
+
from skilleter_thingy import colour
|
|
48
|
+
from skilleter_thingy import run
|
|
49
|
+
from skilleter_thingy import files
|
|
50
|
+
from skilleter_thingy import git
|
|
51
|
+
from skilleter_thingy import dircolors
|
|
52
|
+
|
|
53
|
+
################################################################################
|
|
54
|
+
# Constants
|
|
55
|
+
|
|
56
|
+
# A file must be at least this size to be considered binary - if it is smaller
|
|
57
|
+
# we give it the benefit of the doubt.
|
|
58
|
+
|
|
59
|
+
MIN_BINARY_SIZE = 8
|
|
60
|
+
|
|
61
|
+
################################################################################
|
|
62
|
+
|
|
63
|
+
def report_permissions(perm):
|
|
64
|
+
""" Convert an octal value in a string to a description of file permissions
|
|
65
|
+
e.g. given '644' it will return 'rw-r--r--' """
|
|
66
|
+
|
|
67
|
+
mask_chars = ('r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x')
|
|
68
|
+
|
|
69
|
+
# Convert the permissions from an octal string to an integer
|
|
70
|
+
|
|
71
|
+
permissions = int(perm, 8)
|
|
72
|
+
|
|
73
|
+
# Start at the topmost bit and work downwards adding the mask character
|
|
74
|
+
# for bits that are set and '-' for ones that aren't.
|
|
75
|
+
|
|
76
|
+
mask = 1 << (len(mask_chars) - 1)
|
|
77
|
+
|
|
78
|
+
permtext = []
|
|
79
|
+
|
|
80
|
+
for mask_char in mask_chars:
|
|
81
|
+
permtext.append(mask_char if permissions & mask else '-')
|
|
82
|
+
mask >>= 1
|
|
83
|
+
|
|
84
|
+
return '%s' % ''.join(permtext)
|
|
85
|
+
|
|
86
|
+
################################################################################
|
|
87
|
+
|
|
88
|
+
def main():
|
|
89
|
+
""" Main function - does everything """
|
|
90
|
+
|
|
91
|
+
# Set up logging
|
|
92
|
+
|
|
93
|
+
log = logger.init('gitcmp')
|
|
94
|
+
|
|
95
|
+
# Allow the log level to be configured in git config, as well as via the
|
|
96
|
+
# GITCMP_DEBUG
|
|
97
|
+
|
|
98
|
+
txt_debug = git.config_get('cmp', 'debug')
|
|
99
|
+
env_debug = os.getenv('GITCMP_DEBUG', '0')
|
|
100
|
+
|
|
101
|
+
if txt_debug.lower() in ('true', '1') or env_debug.lower() in ('true', '1'):
|
|
102
|
+
log.setLevel(logger.DEBUG)
|
|
103
|
+
|
|
104
|
+
# Parse the command line
|
|
105
|
+
|
|
106
|
+
parser = argparse.ArgumentParser(description='Invoked via the "git cmp" alias. Works as an enhanced version of "git difftool"')
|
|
107
|
+
|
|
108
|
+
parser.add_argument('file_path', nargs='?', help='File name and path')
|
|
109
|
+
|
|
110
|
+
parser.add_argument('old_file', nargs='?', help='Name of temporary copy of old version')
|
|
111
|
+
parser.add_argument('old_sha1', nargs='?', help='SHA1 of the old version')
|
|
112
|
+
parser.add_argument('old_perm', nargs='?', help='Permissions for the old version')
|
|
113
|
+
|
|
114
|
+
parser.add_argument('new_file', nargs='?', help='Name of temporary copy of the new version')
|
|
115
|
+
parser.add_argument('new_sha1', nargs='?', help='SHA1 of the new version')
|
|
116
|
+
parser.add_argument('new_perm', nargs='?', help='Permissions for the new version')
|
|
117
|
+
|
|
118
|
+
parser.add_argument('new_name', nargs='?', help='New name (if file has been renamed)')
|
|
119
|
+
parser.add_argument('rename', nargs='?', help='Description of rename')
|
|
120
|
+
|
|
121
|
+
args = parser.parse_args()
|
|
122
|
+
|
|
123
|
+
# Get configuration from the environment
|
|
124
|
+
|
|
125
|
+
path_count = int(os.getenv('GIT_DIFF_PATH_COUNTER', '0'))
|
|
126
|
+
path_total = int(os.getenv('GIT_DIFF_PATH_TOTAL', '0'))
|
|
127
|
+
diff_binaries = int(os.getenv('GIT_DIFF_BINARIES', '0'))
|
|
128
|
+
skip_deleted = int(os.getenv('GIT_IGNORE_DELETED', '0'))
|
|
129
|
+
|
|
130
|
+
# Debug output
|
|
131
|
+
|
|
132
|
+
log.info('Parameters to gitcmp-helper:')
|
|
133
|
+
log.info('1: path: %s', args.file_path)
|
|
134
|
+
log.info('2: old file: %s', args.old_file)
|
|
135
|
+
log.info('3: old sha1: %s', args.old_sha1)
|
|
136
|
+
log.info('4: old perm: %s', args.old_perm)
|
|
137
|
+
log.info('5: new file: %s', args.new_file)
|
|
138
|
+
log.info('6: new sha1: %s', args.new_sha1)
|
|
139
|
+
log.info('7: new perm: %s', args.new_perm)
|
|
140
|
+
log.info('8: new name: %s', args.new_name)
|
|
141
|
+
log.info('9: rename : %s', args.rename)
|
|
142
|
+
log.info('path count: %d/%d', path_count, path_total)
|
|
143
|
+
|
|
144
|
+
# Sanity checks
|
|
145
|
+
|
|
146
|
+
if args.file_path is None:
|
|
147
|
+
sys.stderr.write('At least one parameter must be specified\n')
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
|
|
150
|
+
# Check and handle for the simple case of an unmerged file
|
|
151
|
+
|
|
152
|
+
if args.old_file is None:
|
|
153
|
+
colour.write('[CYAN:%s] is not merged' % args.file_path)
|
|
154
|
+
sys.exit(0)
|
|
155
|
+
|
|
156
|
+
# Make sure that we have all the expected parameters
|
|
157
|
+
|
|
158
|
+
if args.new_perm is None:
|
|
159
|
+
sys.stderr.write('Either 1 or 7 parameters must be specifed\n')
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
|
|
162
|
+
# Make sure we can access the temporary files supplied
|
|
163
|
+
|
|
164
|
+
if not os.access(args.old_file, os.R_OK):
|
|
165
|
+
sys.stderr.write('Unable to read temporary old file: %s\n' % args.old_file)
|
|
166
|
+
sys.exit(2)
|
|
167
|
+
|
|
168
|
+
if not os.access(args.new_file, os.R_OK):
|
|
169
|
+
sys.stderr.write('Unable to read temporary new file: %s\n' % args.new_file)
|
|
170
|
+
sys.exit(2)
|
|
171
|
+
|
|
172
|
+
dc = dircolors.Dircolors()
|
|
173
|
+
|
|
174
|
+
# Determine the best way of reporting the path to the file
|
|
175
|
+
|
|
176
|
+
working_tree_path = os.getcwd()
|
|
177
|
+
current_path = os.path.join(working_tree_path, os.getenv('GIT_SUBDIR', ''))
|
|
178
|
+
|
|
179
|
+
current_file_path = os.path.relpath(args.file_path if args.new_name is None else args.new_name, current_path)
|
|
180
|
+
|
|
181
|
+
log.info('file path: %s', current_file_path)
|
|
182
|
+
|
|
183
|
+
# Heading printed first
|
|
184
|
+
|
|
185
|
+
heading = ['[BOLD]Changes in [NORMAL]%s' % dc.format(current_file_path)]
|
|
186
|
+
|
|
187
|
+
# If file was renamed, append the old name and the degree of similarity
|
|
188
|
+
|
|
189
|
+
if args.new_name:
|
|
190
|
+
similarity = re.sub(r'(similarity index) (.*)', r'\1 [CYAN:\2]', args.rename.split('\n')[0])
|
|
191
|
+
|
|
192
|
+
heading.append('(rename from %s with %s)' % (dc.format(os.path.relpath(args.file_path, current_path)), similarity))
|
|
193
|
+
|
|
194
|
+
# If processing more than one file, append he index and total number of files
|
|
195
|
+
|
|
196
|
+
if path_total > 0:
|
|
197
|
+
heading.append('(%d/%d)' % (path_count, path_total))
|
|
198
|
+
|
|
199
|
+
# Check for newly created/deleted files (other version will be '/dev/null')
|
|
200
|
+
|
|
201
|
+
created_file = (args.old_file == '/dev/null')
|
|
202
|
+
deleted_file = (args.new_file == '/dev/null')
|
|
203
|
+
|
|
204
|
+
if created_file:
|
|
205
|
+
heading.append('(new file)')
|
|
206
|
+
|
|
207
|
+
if deleted_file:
|
|
208
|
+
heading.append('(deleted file)')
|
|
209
|
+
|
|
210
|
+
colour.write(' '.join(heading))
|
|
211
|
+
|
|
212
|
+
# Report permission(s) / permissions changes
|
|
213
|
+
|
|
214
|
+
permissions_changed = not (created_file or deleted_file) and args.old_perm != args.new_perm
|
|
215
|
+
|
|
216
|
+
if deleted_file:
|
|
217
|
+
colour.write(' Old permissions: [CYAN:%s]' % report_permissions(args.old_perm))
|
|
218
|
+
elif created_file:
|
|
219
|
+
colour.write(' New permissions: [CYAN:%s]' % report_permissions(args.new_perm))
|
|
220
|
+
elif permissions_changed:
|
|
221
|
+
colour.write(' Changed permissions: [CYAN:%s] -> [CYAN:%s]' % (report_permissions(args.old_perm), report_permissions(args.new_perm)))
|
|
222
|
+
else:
|
|
223
|
+
colour.write(' Permissions: [CYAN:%s]' % report_permissions(args.new_perm))
|
|
224
|
+
|
|
225
|
+
# Report size changes
|
|
226
|
+
|
|
227
|
+
old_size = os.stat(args.old_file).st_size
|
|
228
|
+
new_size = os.stat(args.new_file).st_size
|
|
229
|
+
|
|
230
|
+
formatted_old_size = files.format_size(old_size, always_suffix=True)
|
|
231
|
+
formatted_new_size = files.format_size(new_size, always_suffix=True)
|
|
232
|
+
|
|
233
|
+
if created_file:
|
|
234
|
+
colour.write(' New size: [CYAN:%s]' % formatted_new_size)
|
|
235
|
+
elif deleted_file:
|
|
236
|
+
colour.write(' Original size: [CYAN:%s]' % formatted_old_size)
|
|
237
|
+
elif new_size == old_size:
|
|
238
|
+
colour.write(' Size: [CYAN]%s[NORMAL] (no change)' % formatted_new_size)
|
|
239
|
+
else:
|
|
240
|
+
formatted_delta_size = files.format_size(abs(new_size - old_size), always_suffix=True)
|
|
241
|
+
|
|
242
|
+
delta = '%s %s' % (formatted_delta_size, 'larger' if new_size > old_size else 'smaller')
|
|
243
|
+
|
|
244
|
+
if formatted_old_size == formatted_new_size:
|
|
245
|
+
colour.write(' Size: [CYAN:%s] (%s)' % (formatted_new_size, delta))
|
|
246
|
+
else:
|
|
247
|
+
colour.write(' Size: [CYAN:%s] -> [CYAN:%s] (%s)' %
|
|
248
|
+
(formatted_old_size, formatted_new_size, delta))
|
|
249
|
+
|
|
250
|
+
# Report file type
|
|
251
|
+
|
|
252
|
+
if created_file:
|
|
253
|
+
old_type = None
|
|
254
|
+
else:
|
|
255
|
+
old_type = files.file_type(args.old_file)
|
|
256
|
+
|
|
257
|
+
if deleted_file:
|
|
258
|
+
new_type = None
|
|
259
|
+
else:
|
|
260
|
+
new_type = files.file_type(args.new_file)
|
|
261
|
+
|
|
262
|
+
if created_file:
|
|
263
|
+
colour.write(' File type: [CYAN:%s]' % new_type)
|
|
264
|
+
elif deleted_file:
|
|
265
|
+
colour.write(' Original file type: [CYAN:%s]' % old_type)
|
|
266
|
+
elif old_type != new_type:
|
|
267
|
+
colour.write(' File type: [CYAN:%s] (previously [CYAN:%s)]' % (new_type, old_type))
|
|
268
|
+
else:
|
|
269
|
+
colour.write(' File type: [CYAN:%s]' % new_type)
|
|
270
|
+
|
|
271
|
+
# Report permissions and type
|
|
272
|
+
|
|
273
|
+
if filecmp.cmp(args.old_file, args.new_file, shallow=False):
|
|
274
|
+
# If the file is unchanged, just report the permissions change (if any)
|
|
275
|
+
|
|
276
|
+
if permissions_changed:
|
|
277
|
+
colour.write(' Revisions are identical with changes to permissions')
|
|
278
|
+
else:
|
|
279
|
+
colour.write(' Revisions are identical')
|
|
280
|
+
else:
|
|
281
|
+
# Check if the file is/was a binary
|
|
282
|
+
|
|
283
|
+
old_binary = not created_file and old_size > MIN_BINARY_SIZE and files.is_binary_file(args.old_file)
|
|
284
|
+
new_binary = not deleted_file and new_size > MIN_BINARY_SIZE and files.is_binary_file(args.new_file)
|
|
285
|
+
|
|
286
|
+
# If both versions are binary and we're not risking diffing binaries, report it
|
|
287
|
+
# otherwise, issue a warning if one version is binary then do the diff
|
|
288
|
+
|
|
289
|
+
if (old_binary or new_binary) and not diff_binaries:
|
|
290
|
+
colour.write(' Cannot diff binary files')
|
|
291
|
+
else:
|
|
292
|
+
difftool = git.config_get('cmp', 'difftool', defaultvalue='diffuse')
|
|
293
|
+
|
|
294
|
+
if old_binary or new_binary:
|
|
295
|
+
colour.write(' [BOLD:WARNING]: One or both files may be binaries')
|
|
296
|
+
|
|
297
|
+
if not deleted_file or not skip_deleted:
|
|
298
|
+
try:
|
|
299
|
+
run.run([difftool, args.old_file, args.new_file])
|
|
300
|
+
except run.RunError as exc:
|
|
301
|
+
print('Diff failed: %s' % exc.msg)
|
|
302
|
+
|
|
303
|
+
# Separate reports with a blank line
|
|
304
|
+
|
|
305
|
+
print('')
|
|
306
|
+
|
|
307
|
+
################################################################################
|
|
308
|
+
|
|
309
|
+
def gitcmp_helper():
|
|
310
|
+
"""Entry point"""
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
main()
|
|
314
|
+
except KeyboardInterrupt:
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
except BrokenPipeError:
|
|
317
|
+
sys.exit(2)
|
|
318
|
+
|
|
319
|
+
################################################################################
|
|
320
|
+
|
|
321
|
+
if __name__ == '__main__':
|
|
322
|
+
gitcmp_helper()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" GitLab module - implemented using the REST API as some features are not
|
|
5
|
+
available (or don't work) in the official Python module
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2017-20 John Skilleter
|
|
8
|
+
|
|
9
|
+
Licence: GPL v3 or later
|
|
10
|
+
|
|
11
|
+
Note: There are two types of function for returning data from GitLab;
|
|
12
|
+
the paged functions and the non-paged ones - the paged ones return a page
|
|
13
|
+
(normally 20 items) of data and need to be called repeated until no data is
|
|
14
|
+
left whereas the non-paged ones query all the data and concatenate it
|
|
15
|
+
together.
|
|
16
|
+
|
|
17
|
+
The paged functions expect a full request string with the URL, as returned
|
|
18
|
+
by the request_string() member. The non-paged ones call request_string()
|
|
19
|
+
to add the URL & API prefix.
|
|
20
|
+
"""
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import os
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import requests
|
|
28
|
+
except ModuleNotFoundError:
|
|
29
|
+
sys.stderr.write('This code requires the Python "requests" module which should be installed via Pip\n')
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
################################################################################
|
|
33
|
+
|
|
34
|
+
class GitLabError(Exception):
|
|
35
|
+
""" Gitlab exceptions """
|
|
36
|
+
|
|
37
|
+
def __init__(self, response):
|
|
38
|
+
""" Save the error code and text """
|
|
39
|
+
|
|
40
|
+
self.status = response.status_code
|
|
41
|
+
self.message = response.reason
|
|
42
|
+
|
|
43
|
+
def __str__(self):
|
|
44
|
+
""" Return a string version of the exception """
|
|
45
|
+
|
|
46
|
+
return '%s: %s' % (self.status, self.message)
|
|
47
|
+
|
|
48
|
+
################################################################################
|
|
49
|
+
|
|
50
|
+
class GitLab:
|
|
51
|
+
""" Class for GitLab access """
|
|
52
|
+
|
|
53
|
+
def __init__(self, gitlab, token=None):
|
|
54
|
+
""" Initialisation """
|
|
55
|
+
|
|
56
|
+
# Save the GitLab URL
|
|
57
|
+
|
|
58
|
+
self.gitlab = gitlab
|
|
59
|
+
|
|
60
|
+
# If we have a private token use it, otherwise try and get it from
|
|
61
|
+
# the environmnet
|
|
62
|
+
|
|
63
|
+
self.token = token if token else os.getenv('GITLAB_TOKEN', None)
|
|
64
|
+
|
|
65
|
+
# Create the default header for requests
|
|
66
|
+
|
|
67
|
+
self.header = {'Private-Token': self.token}
|
|
68
|
+
|
|
69
|
+
################################################################################
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def encode_project(name):
|
|
73
|
+
""" Encode a project name in the form request by GitLab requests """
|
|
74
|
+
|
|
75
|
+
return name.replace('/', '%2F')
|
|
76
|
+
|
|
77
|
+
################################################################################
|
|
78
|
+
|
|
79
|
+
def request_string(self, request):
|
|
80
|
+
""" Add the URL/API header onto a request string """
|
|
81
|
+
|
|
82
|
+
return '%s/api/v4/%s' % (self.gitlab, request)
|
|
83
|
+
|
|
84
|
+
################################################################################
|
|
85
|
+
|
|
86
|
+
def request(self, request, parameters=None):
|
|
87
|
+
""" Send a request to GitLab - handles pagination and returns all the
|
|
88
|
+
results concatenated together """
|
|
89
|
+
|
|
90
|
+
if parameters:
|
|
91
|
+
request = '%s?%s' % (request, '&'.join(parameters))
|
|
92
|
+
|
|
93
|
+
gl_request = self.request_string(request)
|
|
94
|
+
|
|
95
|
+
# Keep requesting data until there's no 'next' link in the response
|
|
96
|
+
|
|
97
|
+
while True:
|
|
98
|
+
response = requests.get(gl_request, headers=self.header)
|
|
99
|
+
|
|
100
|
+
if not response:
|
|
101
|
+
raise GitLabError(response)
|
|
102
|
+
|
|
103
|
+
yield response.json()
|
|
104
|
+
|
|
105
|
+
if 'next' not in response.links:
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
gl_request = response.links['next']['url']
|
|
109
|
+
|
|
110
|
+
################################################################################
|
|
111
|
+
|
|
112
|
+
def paged_request(self, request):
|
|
113
|
+
""" Send a request to GitLab - returns all the results concatenated together
|
|
114
|
+
and returns a page of results along with the request for the next page of
|
|
115
|
+
results (if any).
|
|
116
|
+
|
|
117
|
+
Note that the request parameter is the full request string as returned by
|
|
118
|
+
request_string(). """
|
|
119
|
+
|
|
120
|
+
response = requests.get(request, headers=self.header)
|
|
121
|
+
|
|
122
|
+
result = response.json()
|
|
123
|
+
|
|
124
|
+
if not response:
|
|
125
|
+
raise GitLabError(response)
|
|
126
|
+
|
|
127
|
+
request = response.links['next']['url'] if 'next' in response.links else None
|
|
128
|
+
|
|
129
|
+
return result, request
|
|
130
|
+
|
|
131
|
+
################################################################################
|
|
132
|
+
|
|
133
|
+
def projects(self):
|
|
134
|
+
""" Return a list of projects """
|
|
135
|
+
|
|
136
|
+
return self.request('projects')
|
|
137
|
+
|
|
138
|
+
################################################################################
|
|
139
|
+
|
|
140
|
+
def branches(self, repo):
|
|
141
|
+
""" Return the list of branches in a repo """
|
|
142
|
+
|
|
143
|
+
for batch in self.request('projects/%s/repository/branches' % self.encode_project(repo)):
|
|
144
|
+
for branch in batch:
|
|
145
|
+
yield branch
|
|
146
|
+
|
|
147
|
+
################################################################################
|
|
148
|
+
|
|
149
|
+
def merge_requests(self, **kwargs):
|
|
150
|
+
""" Return a list of merge requests filtered according to the parameters """
|
|
151
|
+
|
|
152
|
+
request = 'merge_requests'
|
|
153
|
+
|
|
154
|
+
parameters = []
|
|
155
|
+
|
|
156
|
+
for data in kwargs:
|
|
157
|
+
parameters.append('%s=%s' % (data, kwargs[data]))
|
|
158
|
+
|
|
159
|
+
for result in self.request(request, parameters):
|
|
160
|
+
for r in result:
|
|
161
|
+
yield r
|
|
162
|
+
|
|
163
|
+
################################################################################
|
|
164
|
+
|
|
165
|
+
def default_branch(self, repo):
|
|
166
|
+
""" Query gitlab to retreive the default branch for the repo """
|
|
167
|
+
|
|
168
|
+
# Look for the default branch
|
|
169
|
+
|
|
170
|
+
for branch in self.branches(repo):
|
|
171
|
+
if branch['default']:
|
|
172
|
+
return branch['name']
|
|
173
|
+
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
################################################################################
|
|
177
|
+
|
|
178
|
+
def isbranch(self, repo, branchname):
|
|
179
|
+
""" Return True if the branch exists in the repo """
|
|
180
|
+
|
|
181
|
+
request = self.request_string('projects/%s/repository/branches' % self.encode_project(repo))
|
|
182
|
+
|
|
183
|
+
while True:
|
|
184
|
+
branches, request = self.paged_request(request)
|
|
185
|
+
|
|
186
|
+
for branch in branches:
|
|
187
|
+
if branch['name'] == branchname:
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
if not request:
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
return False
|