blob: 409bca31d2965381d6c3a9dfb6320f88bbf367c6 [file] [log] [blame]
Michael Bestas3952f6c2016-08-26 01:12:08 +03001#!/usr/bin/env python
2#
3# Copyright (C) 2013-15 The CyanogenMod Project
Dan Pasanen0fdc0852016-12-27 10:32:16 -06004# (C) 2017 The LineageOS Project
Michael Bestas3952f6c2016-08-26 01:12:08 +03005#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19#
20# Run repopick.py -h for a description of this utility.
21#
22
23from __future__ import print_function
24
25import sys
26import json
27import os
28import subprocess
29import re
30import argparse
31import textwrap
Gabriele M5b610ae2018-03-31 14:26:59 +020032from functools import cmp_to_key
Michael Bestas3952f6c2016-08-26 01:12:08 +030033from xml.etree import ElementTree
34
35try:
Dan Pasanen1cdd3802017-01-23 15:08:52 -060036 import requests
Michael Bestas3952f6c2016-08-26 01:12:08 +030037except ImportError:
Dan Pasanen1cdd3802017-01-23 15:08:52 -060038 try:
39 # For python3
40 import urllib.error
41 import urllib.request
42 except ImportError:
43 # For python2
44 import imp
45 import urllib2
46 urllib = imp.new_module('urllib')
47 urllib.error = urllib2
48 urllib.request = urllib2
Michael Bestas3952f6c2016-08-26 01:12:08 +030049
50
Luca Weiss5ee35ea2018-11-25 14:07:12 +010051# cmp() is not available in Python 3, define it manually
52# See https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
53def cmp(a, b):
54 return (a > b) - (a < b)
55
56
Michael Bestas3952f6c2016-08-26 01:12:08 +030057# Verifies whether pathA is a subdirectory (or the same) as pathB
58def is_subdir(a, b):
59 a = os.path.realpath(a) + '/'
60 b = os.path.realpath(b) + '/'
61 return b == a[:len(b)]
62
63
64def fetch_query_via_ssh(remote_url, query):
65 """Given a remote_url and a query, return the list of changes that fit it
66 This function is slightly messy - the ssh api does not return data in the same structure as the HTTP REST API
67 We have to get the data, then transform it to match what we're expecting from the HTTP RESET API"""
68 if remote_url.count(':') == 2:
69 (uri, userhost, port) = remote_url.split(':')
70 userhost = userhost[2:]
71 elif remote_url.count(':') == 1:
72 (uri, userhost) = remote_url.split(':')
73 userhost = userhost[2:]
74 port = 29418
75 else:
76 raise Exception('Malformed URI: Expecting ssh://[user@]host[:port]')
77
78
79 out = subprocess.check_output(['ssh', '-x', '-p{0}'.format(port), userhost, 'gerrit', 'query', '--format=JSON --patch-sets --current-patch-set', query])
80 if not hasattr(out, 'encode'):
81 out = out.decode()
82 reviews = []
83 for line in out.split('\n'):
84 try:
85 data = json.loads(line)
86 # make our data look like the http rest api data
87 review = {
88 'branch': data['branch'],
89 'change_id': data['id'],
90 'current_revision': data['currentPatchSet']['revision'],
91 'number': int(data['number']),
92 'revisions': {patch_set['revision']: {
Gabriele M0fcc1222018-04-10 18:35:12 +020093 '_number': int(patch_set['number']),
Michael Bestas3952f6c2016-08-26 01:12:08 +030094 'fetch': {
95 'ssh': {
96 'ref': patch_set['ref'],
97 'url': 'ssh://{0}:{1}/{2}'.format(userhost, port, data['project'])
98 }
Gabriele M0fcc1222018-04-10 18:35:12 +020099 },
100 'commit': {
101 'parents': [{ 'commit': parent } for parent in patch_set['parents']]
102 },
Michael Bestas3952f6c2016-08-26 01:12:08 +0300103 } for patch_set in data['patchSets']},
104 'subject': data['subject'],
105 'project': data['project'],
106 'status': data['status']
107 }
108 reviews.append(review)
109 except:
110 pass
111 args.quiet or print('Found {0} reviews'.format(len(reviews)))
112 return reviews
113
114
115def fetch_query_via_http(remote_url, query):
Dan Pasanen1cdd3802017-01-23 15:08:52 -0600116 if "requests" in sys.modules:
117 auth = None
118 if os.path.isfile(os.getenv("HOME") + "/.gerritrc"):
119 f = open(os.getenv("HOME") + "/.gerritrc", "r")
120 for line in f:
121 parts = line.rstrip().split("|")
122 if parts[0] in remote_url:
123 auth = requests.auth.HTTPBasicAuth(username=parts[1], password=parts[2])
124 statusCode = '-1'
125 if auth:
Gabriele M5b610ae2018-03-31 14:26:59 +0200126 url = '{0}/a/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS&o=ALL_COMMITS'.format(remote_url, query)
Dan Pasanen1cdd3802017-01-23 15:08:52 -0600127 data = requests.get(url, auth=auth)
128 statusCode = str(data.status_code)
129 if statusCode != '200':
130 #They didn't get good authorization or data, Let's try the old way
Gabriele M5b610ae2018-03-31 14:26:59 +0200131 url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS&o=ALL_COMMITS'.format(remote_url, query)
Dan Pasanen1cdd3802017-01-23 15:08:52 -0600132 data = requests.get(url)
133 reviews = json.loads(data.text[5:])
134 else:
135 """Given a query, fetch the change numbers via http"""
Gabriele M5b610ae2018-03-31 14:26:59 +0200136 url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS&o=ALL_COMMITS'.format(remote_url, query)
Dan Pasanen1cdd3802017-01-23 15:08:52 -0600137 data = urllib.request.urlopen(url).read().decode('utf-8')
138 reviews = json.loads(data[5:])
Michael Bestas3952f6c2016-08-26 01:12:08 +0300139
140 for review in reviews:
141 review['number'] = review.pop('_number')
142
143 return reviews
144
145
146def fetch_query(remote_url, query):
147 """Wrapper for fetch_query_via_proto functions"""
148 if remote_url[0:3] == 'ssh':
149 return fetch_query_via_ssh(remote_url, query)
150 elif remote_url[0:4] == 'http':
151 return fetch_query_via_http(remote_url, query.replace(' ', '+'))
152 else:
153 raise Exception('Gerrit URL should be in the form http[s]://hostname/ or ssh://[user@]host[:port]')
154
155if __name__ == '__main__':
Dan Pasanen0fdc0852016-12-27 10:32:16 -0600156 # Default to LineageOS Gerrit
rohanfa44f372020-03-14 23:40:54 -0400157 default_gerrit = 'https://review.blissroms.com'
Michael Bestas3952f6c2016-08-26 01:12:08 +0300158
159 parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\
160 repopick.py is a utility to simplify the process of cherry picking
Dan Pasanen0fdc0852016-12-27 10:32:16 -0600161 patches from LineageOS's Gerrit instance (or any gerrit instance of your choosing)
Michael Bestas3952f6c2016-08-26 01:12:08 +0300162
163 Given a list of change numbers, repopick will cd into the project path
164 and cherry pick the latest patch available.
165
166 With the --start-branch argument, the user can specify that a branch
167 should be created before cherry picking. This is useful for
168 cherry-picking many patches into a common branch which can be easily
169 abandoned later (good for testing other's changes.)
170
171 The --abandon-first argument, when used in conjunction with the
172 --start-branch option, will cause repopick to abandon the specified
173 branch in all repos first before performing any cherry picks.'''))
174 parser.add_argument('change_number', nargs='*', help='change number to cherry pick. Use {change number}/{patchset number} to get a specific revision.')
175 parser.add_argument('-i', '--ignore-missing', action='store_true', help='do not error out if a patch applies to a missing directory')
176 parser.add_argument('-s', '--start-branch', nargs=1, help='start the specified branch before cherry picking')
Harry Youd1c9c5a32017-07-18 18:52:42 +0100177 parser.add_argument('-r', '--reset', action='store_true', help='reset to initial state (abort cherry-pick) if there is a conflict')
Michael Bestas3952f6c2016-08-26 01:12:08 +0300178 parser.add_argument('-a', '--abandon-first', action='store_true', help='before cherry picking, abandon the branch specified in --start-branch')
179 parser.add_argument('-b', '--auto-branch', action='store_true', help='shortcut to "--start-branch auto --abandon-first --ignore-missing"')
180 parser.add_argument('-q', '--quiet', action='store_true', help='print as little as possible')
181 parser.add_argument('-v', '--verbose', action='store_true', help='print extra information to aid in debug')
182 parser.add_argument('-f', '--force', action='store_true', help='force cherry pick even if change is closed')
183 parser.add_argument('-p', '--pull', action='store_true', help='execute pull instead of cherry-pick')
184 parser.add_argument('-P', '--path', help='use the specified path for the change')
Giuseppe Maggiobcf8e892019-03-18 17:24:06 +0100185 parser.add_argument('-t', '--topic', nargs='*', help='pick all commits from the specified topics')
Michael Bestas3952f6c2016-08-26 01:12:08 +0300186 parser.add_argument('-Q', '--query', help='pick all commits using the specified query')
187 parser.add_argument('-g', '--gerrit', default=default_gerrit, help='Gerrit Instance to use. Form proto://[user@]host[:port]')
188 parser.add_argument('-e', '--exclude', nargs=1, help='exclude a list of commit numbers separated by a ,')
Adrian DCdf290222017-09-07 22:59:54 +0200189 parser.add_argument('-c', '--check-picked', type=int, default=10, help='pass the amount of commits to check for already picked changes')
Michael Bestas3952f6c2016-08-26 01:12:08 +0300190 args = parser.parse_args()
191 if not args.start_branch and args.abandon_first:
192 parser.error('if --abandon-first is set, you must also give the branch name with --start-branch')
193 if args.auto_branch:
194 args.abandon_first = True
195 args.ignore_missing = True
196 if not args.start_branch:
197 args.start_branch = ['auto']
198 if args.quiet and args.verbose:
199 parser.error('--quiet and --verbose cannot be specified together')
200
201 if (1 << bool(args.change_number) << bool(args.topic) << bool(args.query)) != 2:
202 parser.error('One (and only one) of change_number, topic, and query are allowed')
203
204 # Change current directory to the top of the tree
205 if 'ANDROID_BUILD_TOP' in os.environ:
206 top = os.environ['ANDROID_BUILD_TOP']
207
208 if not is_subdir(os.getcwd(), top):
209 sys.stderr.write('ERROR: You must run this tool from within $ANDROID_BUILD_TOP!\n')
210 sys.exit(1)
211 os.chdir(os.environ['ANDROID_BUILD_TOP'])
212
213 # Sanity check that we are being run from the top level of the tree
214 if not os.path.isdir('.repo'):
215 sys.stderr.write('ERROR: No .repo directory found. Please run this from the top of your tree.\n')
216 sys.exit(1)
217
218 # If --abandon-first is given, abandon the branch before starting
219 if args.abandon_first:
220 # Determine if the branch already exists; skip the abandon if it does not
221 plist = subprocess.check_output(['repo', 'info'])
222 if not hasattr(plist, 'encode'):
223 plist = plist.decode()
224 needs_abandon = False
225 for pline in plist.splitlines():
226 matchObj = re.match(r'Local Branches.*\[(.*)\]', pline)
227 if matchObj:
228 local_branches = re.split('\s*,\s*', matchObj.group(1))
229 if any(args.start_branch[0] in s for s in local_branches):
230 needs_abandon = True
231
232 if needs_abandon:
233 # Perform the abandon only if the branch already exists
234 if not args.quiet:
235 print('Abandoning branch: %s' % args.start_branch[0])
236 subprocess.check_output(['repo', 'abandon', args.start_branch[0]])
237 if not args.quiet:
238 print('')
239
240 # Get the master manifest from repo
241 # - convert project name and revision to a path
242 project_name_to_data = {}
243 manifest = subprocess.check_output(['repo', 'manifest'])
244 xml_root = ElementTree.fromstring(manifest)
245 projects = xml_root.findall('project')
246 remotes = xml_root.findall('remote')
247 default_revision = xml_root.findall('default')[0].get('revision')
248
249 #dump project data into the a list of dicts with the following data:
250 #{project: {path, revision}}
251
252 for project in projects:
253 name = project.get('name')
Aaron Kling149d66a2020-06-19 18:43:16 -0500254 # when name and path are equal, "repo manifest" doesn't return a path at all, so fall back to name
255 path = project.get('path', name)
Michael Bestas3952f6c2016-08-26 01:12:08 +0300256 revision = project.get('revision')
257 if revision is None:
258 for remote in remotes:
259 if remote.get('name') == project.get('remote'):
260 revision = remote.get('revision')
261 if revision is None:
Simon Shields2bdb18f2016-09-26 17:52:03 +1000262 revision = default_revision
Michael Bestas3952f6c2016-08-26 01:12:08 +0300263
264 if not name in project_name_to_data:
265 project_name_to_data[name] = {}
266 revision = revision.split('refs/heads/')[-1]
267 project_name_to_data[name][revision] = path
268
269 # get data on requested changes
270 reviews = []
271 change_numbers = []
Gabriele M5b610ae2018-03-31 14:26:59 +0200272
273 def cmp_reviews(review_a, review_b):
274 current_a = review_a['current_revision']
275 parents_a = [r['commit'] for r in review_a['revisions'][current_a]['commit']['parents']]
276 current_b = review_b['current_revision']
277 parents_b = [r['commit'] for r in review_b['revisions'][current_b]['commit']['parents']]
278 if current_a in parents_b:
279 return -1
280 elif current_b in parents_a:
281 return 1
282 else:
283 return cmp(review_a['number'], review_b['number'])
284
Michael Bestas3952f6c2016-08-26 01:12:08 +0300285 if args.topic:
Giuseppe Maggiobcf8e892019-03-18 17:24:06 +0100286 for t in args.topic:
287 # Store current topic to process for change_numbers
288 topic = fetch_query(args.gerrit, 'topic:{0}'.format(t))
289 # Append topic to reviews, for later reference
290 reviews += topic
291 # Cycle through the current topic to get the change numbers
292 change_numbers += sorted([str(r['number']) for r in topic], key=int)
Michael Bestas3952f6c2016-08-26 01:12:08 +0300293 if args.query:
294 reviews = fetch_query(args.gerrit, args.query)
Gabriele M5b610ae2018-03-31 14:26:59 +0200295 change_numbers = [str(r['number']) for r in sorted(reviews, key=cmp_to_key(cmp_reviews))]
Michael Bestas3952f6c2016-08-26 01:12:08 +0300296 if args.change_number:
Gabriele Maf970b62018-04-01 17:50:58 +0200297 change_url_re = re.compile('https?://.+?/([0-9]+(?:/[0-9]+)?)/?')
Michael Bestas3952f6c2016-08-26 01:12:08 +0300298 for c in args.change_number:
Gabriele Maf970b62018-04-01 17:50:58 +0200299 change_number = change_url_re.findall(c)
300 if change_number:
301 change_numbers.extend(change_number)
302 elif '-' in c:
Michael Bestas3952f6c2016-08-26 01:12:08 +0300303 templist = c.split('-')
304 for i in range(int(templist[0]), int(templist[1]) + 1):
305 change_numbers.append(str(i))
306 else:
307 change_numbers.append(c)
308 reviews = fetch_query(args.gerrit, ' OR '.join('change:{0}'.format(x.split('/')[0]) for x in change_numbers))
309
310 # make list of things to actually merge
311 mergables = []
312
313 # If --exclude is given, create the list of commits to ignore
314 exclude = []
315 if args.exclude:
316 exclude = args.exclude[0].split(',')
317
318 for change in change_numbers:
319 patchset = None
320 if '/' in change:
321 (change, patchset) = change.split('/')
322
323 if change in exclude:
324 continue
325
326 change = int(change)
LuK1337ad5d9a02017-03-24 23:25:13 +0100327
Gabriele Mde9e0ae2018-04-01 17:50:55 +0200328 if patchset:
LuK1337ad5d9a02017-03-24 23:25:13 +0100329 patchset = int(patchset)
330
Michael Bestas3952f6c2016-08-26 01:12:08 +0300331 review = next((x for x in reviews if x['number'] == change), None)
332 if review is None:
333 print('Change %d not found, skipping' % change)
334 continue
335
336 mergables.append({
337 'subject': review['subject'],
338 'project': review['project'],
339 'branch': review['branch'],
340 'change_id': review['change_id'],
341 'change_number': review['number'],
342 'status': review['status'],
Gabriele M88c0e5d2018-04-01 17:50:57 +0200343 'fetch': None,
344 'patchset': review['revisions'][review['current_revision']]['_number'],
Michael Bestas3952f6c2016-08-26 01:12:08 +0300345 })
Gabriele M88c0e5d2018-04-01 17:50:57 +0200346
Michael Bestas3952f6c2016-08-26 01:12:08 +0300347 mergables[-1]['fetch'] = review['revisions'][review['current_revision']]['fetch']
348 mergables[-1]['id'] = change
349 if patchset:
350 try:
LuK133727564182017-03-24 20:00:48 +0100351 mergables[-1]['fetch'] = [review['revisions'][x]['fetch'] for x in review['revisions'] if review['revisions'][x]['_number'] == patchset][0]
Michael Bestas3952f6c2016-08-26 01:12:08 +0300352 mergables[-1]['id'] = '{0}/{1}'.format(change, patchset)
Gabriele M88c0e5d2018-04-01 17:50:57 +0200353 mergables[-1]['patchset'] = patchset
Michael Bestas3952f6c2016-08-26 01:12:08 +0300354 except (IndexError, ValueError):
355 args.quiet or print('ERROR: The patch set {0}/{1} could not be found, using CURRENT_REVISION instead.'.format(change, patchset))
356
357 for item in mergables:
358 args.quiet or print('Applying change number {0}...'.format(item['id']))
359 # Check if change is open and exit if it's not, unless -f is specified
Dan Pasanen63f767e2017-07-09 09:41:33 -0500360 if (item['status'] != 'OPEN' and item['status'] != 'NEW' and item['status'] != 'DRAFT') and not args.query:
Michael Bestas3952f6c2016-08-26 01:12:08 +0300361 if args.force:
362 print('!! Force-picking a closed change !!\n')
363 else:
364 print('Change status is ' + item['status'] + '. Skipping the cherry pick.\nUse -f to force this pick.')
365 continue
366
367 # Convert the project name to a project path
368 # - check that the project path exists
369 project_path = None
370
371 if item['project'] in project_name_to_data and item['branch'] in project_name_to_data[item['project']]:
372 project_path = project_name_to_data[item['project']][item['branch']]
373 elif args.path:
374 project_path = args.path
Adrian DCff74c6a2019-10-13 12:34:06 +0200375 elif item['project'] in project_name_to_data and len(project_name_to_data[item['project']]) == 1:
376 local_branch = list(project_name_to_data[item['project']])[0]
377 project_path = project_name_to_data[item['project']][local_branch]
378 print('WARNING: Project {0} has a different branch ("{1}" != "{2}")'.format(project_path, local_branch, item['branch']))
Michael Bestas3952f6c2016-08-26 01:12:08 +0300379 elif args.ignore_missing:
380 print('WARNING: Skipping {0} since there is no project directory for: {1}\n'.format(item['id'], item['project']))
381 continue
382 else:
383 sys.stderr.write('ERROR: For {0}, could not determine the project path for project {1}\n'.format(item['id'], item['project']))
384 sys.exit(1)
385
386 # If --start-branch is given, create the branch (more than once per path is okay; repo ignores gracefully)
387 if args.start_branch:
388 subprocess.check_output(['repo', 'start', args.start_branch[0], project_path])
389
390 # Determine the maximum commits to check already picked changes
Adrian DCdf290222017-09-07 22:59:54 +0200391 check_picked_count = args.check_picked
Michael Bestas3952f6c2016-08-26 01:12:08 +0300392 branch_commits_count = int(subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], cwd=project_path))
393 if branch_commits_count <= check_picked_count:
394 check_picked_count = branch_commits_count - 1
395
396 # Check if change is already picked to HEAD...HEAD~check_picked_count
397 found_change = False
398 for i in range(0, check_picked_count):
Adrian DC13b02ff2016-12-04 12:30:26 +0100399 if subprocess.call(['git', 'cat-file', '-e', 'HEAD~{0}'.format(i)], cwd=project_path, stderr=open(os.devnull, 'wb')):
400 continue
Michael Bestas3952f6c2016-08-26 01:12:08 +0300401 output = subprocess.check_output(['git', 'show', '-q', 'HEAD~{0}'.format(i)], cwd=project_path).split()
402 if 'Change-Id:' in output:
403 head_change_id = ''
404 for j,t in enumerate(reversed(output)):
405 if t == 'Change-Id:':
406 head_change_id = output[len(output) - j]
407 break
408 if head_change_id.strip() == item['change_id']:
409 print('Skipping {0} - already picked in {1} as HEAD~{2}'.format(item['id'], project_path, i))
410 found_change = True
411 break
412 if found_change:
413 continue
414
415 # Print out some useful info
416 if not args.quiet:
LuK1337c62a9fb2019-09-21 11:47:33 +0200417 print(u'--> Subject: "{0}"'.format(item['subject']))
Michael Bestas3952f6c2016-08-26 01:12:08 +0300418 print('--> Project path: {0}'.format(project_path))
Gabriele M88c0e5d2018-04-01 17:50:57 +0200419 print('--> Change number: {0} (Patch Set {1})'.format(item['id'], item['patchset']))
Michael Bestas3952f6c2016-08-26 01:12:08 +0300420
421 if 'anonymous http' in item['fetch']:
422 method = 'anonymous http'
423 else:
424 method = 'ssh'
425
426 # Try fetching from GitHub first if using default gerrit
427 if args.gerrit == default_gerrit:
428 if args.verbose:
429 print('Trying to fetch the change from GitHub')
430
431 if args.pull:
432 cmd = ['git pull --no-edit github', item['fetch'][method]['ref']]
433 else:
434 cmd = ['git fetch github', item['fetch'][method]['ref']]
435 if args.quiet:
436 cmd.append('--quiet')
437 else:
438 print(cmd)
439 result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True)
440 FETCH_HEAD = '{0}/.git/FETCH_HEAD'.format(project_path)
441 if result != 0 and os.stat(FETCH_HEAD).st_size != 0:
442 print('ERROR: git command failed')
443 sys.exit(result)
444 # Check if it worked
445 if args.gerrit != default_gerrit or os.stat(FETCH_HEAD).st_size == 0:
446 # If not using the default gerrit or github failed, fetch from gerrit.
447 if args.verbose:
448 if args.gerrit == default_gerrit:
449 print('Fetching from GitHub didn\'t work, trying to fetch the change from Gerrit')
450 else:
451 print('Fetching from {0}'.format(args.gerrit))
452
453 if args.pull:
454 cmd = ['git pull --no-edit', item['fetch'][method]['url'], item['fetch'][method]['ref']]
455 else:
456 cmd = ['git fetch', item['fetch'][method]['url'], item['fetch'][method]['ref']]
457 if args.quiet:
458 cmd.append('--quiet')
459 else:
460 print(cmd)
461 result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True)
462 if result != 0:
463 print('ERROR: git command failed')
464 sys.exit(result)
465 # Perform the cherry-pick
466 if not args.pull:
Tim Schumacher49d26ba2018-10-13 14:37:54 +0200467 cmd = ['git cherry-pick --ff FETCH_HEAD']
Michael Bestas3952f6c2016-08-26 01:12:08 +0300468 if args.quiet:
469 cmd_out = open(os.devnull, 'wb')
470 else:
471 cmd_out = None
472 result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
473 if result != 0:
Adrian DC0f8230b2018-08-30 23:07:23 +0200474 cmd = ['git diff-index --quiet HEAD --']
475 result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
476 if result == 0:
477 print('WARNING: git command resulted with an empty commit, aborting cherry-pick')
478 cmd = ['git cherry-pick --abort']
479 subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
480 elif args.reset:
Harry Youd1c9c5a32017-07-18 18:52:42 +0100481 print('ERROR: git command failed, aborting cherry-pick')
482 cmd = ['git cherry-pick --abort']
483 subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
Adrian DC0f8230b2018-08-30 23:07:23 +0200484 sys.exit(result)
Harry Youd1c9c5a32017-07-18 18:52:42 +0100485 else:
486 print('ERROR: git command failed')
Adrian DC0f8230b2018-08-30 23:07:23 +0200487 sys.exit(result)
Michael Bestas3952f6c2016-08-26 01:12:08 +0300488 if not args.quiet:
489 print('')