blob: 3dec56e51ea02d1fe72b913b276b9bc789adde33 [file] [log] [blame]
Pulser72e23242013-09-29 09:56:55 +01001#!/usr/bin/env python
2#
Marko Manb58468a2018-03-19 13:01:19 +01003# Copyright (C) 2013-15 The CyanogenMod Project
4# (C) 2017 The LineageOS Project
Pulser72e23242013-09-29 09:56:55 +01005#
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 Md91609d2018-03-31 14:26:59 +020032from functools import cmp_to_key
Marko Manb58468a2018-03-19 13:01:19 +010033from xml.etree import ElementTree
Pulser72e23242013-09-29 09:56:55 +010034
35try:
Marko Manb58468a2018-03-19 13:01:19 +010036 import requests
Pulser72e23242013-09-29 09:56:55 +010037except ImportError:
Marko Manb58468a2018-03-19 13:01:19 +010038 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
Pulser72e23242013-09-29 09:56:55 +010049
Pulser72e23242013-09-29 09:56:55 +010050
Luca Weissd1bbac62018-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
Pulser72e23242013-09-29 09:56:55 +010057# Verifies whether pathA is a subdirectory (or the same) as pathB
Marko Manb58468a2018-03-19 13:01:19 +010058def is_subdir(a, b):
59 a = os.path.realpath(a) + '/'
60 b = os.path.realpath(b) + '/'
61 return b == a[:len(b)]
Pulser72e23242013-09-29 09:56:55 +010062
Pulser72e23242013-09-29 09:56:55 +010063
Marko Manb58468a2018-03-19 13:01:19 +010064def 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
Pulser72e23242013-09-29 09:56:55 +010075 else:
Marko Manb58468a2018-03-19 13:01:19 +010076 raise Exception('Malformed URI: Expecting ssh://[user@]host[:port]')
Pulser72e23242013-09-29 09:56:55 +010077
Pulser72e23242013-09-29 09:56:55 +010078
Marko Manb58468a2018-03-19 13:01:19 +010079 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 Mb12913b2018-04-10 18:35:12 +020093 '_number': int(patch_set['number']),
Marko Manb58468a2018-03-19 13:01:19 +010094 'fetch': {
95 'ssh': {
96 'ref': patch_set['ref'],
97 'url': 'ssh://{0}:{1}/{2}'.format(userhost, port, data['project'])
98 }
Gabriele Mb12913b2018-04-10 18:35:12 +020099 },
100 'commit': {
101 'parents': [{ 'commit': parent } for parent in patch_set['parents']]
102 },
Marko Manb58468a2018-03-19 13:01:19 +0100103 } 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
Pulser72e23242013-09-29 09:56:55 +0100113
Pulser72e23242013-09-29 09:56:55 +0100114
Marko Manb58468a2018-03-19 13:01:19 +0100115def fetch_query_via_http(remote_url, query):
116 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 Md91609d2018-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)
Marko Manb58468a2018-03-19 13:01:19 +0100127 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 Md91609d2018-03-31 14:26:59 +0200131 url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS&o=ALL_COMMITS'.format(remote_url, query)
Marko Manb58468a2018-03-19 13:01:19 +0100132 data = requests.get(url)
133 reviews = json.loads(data.text[5:])
134 else:
135 """Given a query, fetch the change numbers via http"""
Gabriele Md91609d2018-03-31 14:26:59 +0200136 url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS&o=ALL_COMMITS'.format(remote_url, query)
Marko Manb58468a2018-03-19 13:01:19 +0100137 data = urllib.request.urlopen(url).read().decode('utf-8')
138 reviews = json.loads(data[5:])
139
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__':
156 # Default to OmniRom Gerrit
157 default_gerrit = 'https://gerrit.omnirom.org'
158
159 parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\
160 repopick.py is a utility to simplify the process of cherry picking
161 patches from OmniRom's Gerrit instance (or any gerrit instance of your choosing)
162
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')
177 parser.add_argument('-r', '--reset', action='store_true', help='reset to initial state (abort cherry-pick) if there is a conflict')
178 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')
185 parser.add_argument('-t', '--topic', help='pick all commits from a specified topic')
186 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 ,')
189 parser.add_argument('-c', '--check-picked', type=int, default=10, help='pass the amount of commits to check for already picked changes')
190 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')
Pulser72e23242013-09-29 09:56:55 +0100216 sys.exit(1)
217
Marko Manb58468a2018-03-19 13:01:19 +0100218 # 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
Pulser72e23242013-09-29 09:56:55 +0100231
Marko Manb58468a2018-03-19 13:01:19 +0100232 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('')
Pulser72e23242013-09-29 09:56:55 +0100239
Marko Manb58468a2018-03-19 13:01:19 +0100240 # 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')
254 path = project.get('path')
255 revision = project.get('revision')
256 if revision is None:
257 for remote in remotes:
258 if remote.get('name') == project.get('remote'):
259 revision = remote.get('revision')
260 if revision is None:
261 revision = default_revision
262
263 if not name in project_name_to_data:
264 project_name_to_data[name] = {}
265 revision = revision.split('refs/heads/')[-1]
266 project_name_to_data[name][revision] = path
267
268 # get data on requested changes
269 reviews = []
270 change_numbers = []
Gabriele Md91609d2018-03-31 14:26:59 +0200271
272 def cmp_reviews(review_a, review_b):
273 current_a = review_a['current_revision']
274 parents_a = [r['commit'] for r in review_a['revisions'][current_a]['commit']['parents']]
275 current_b = review_b['current_revision']
276 parents_b = [r['commit'] for r in review_b['revisions'][current_b]['commit']['parents']]
277 if current_a in parents_b:
278 return -1
279 elif current_b in parents_a:
280 return 1
281 else:
282 return cmp(review_a['number'], review_b['number'])
283
Marko Manb58468a2018-03-19 13:01:19 +0100284 if args.topic:
285 reviews = fetch_query(args.gerrit, 'topic:{0}'.format(args.topic))
Gabriele Md91609d2018-03-31 14:26:59 +0200286 change_numbers = [str(r['number']) for r in sorted(reviews, key=cmp_to_key(cmp_reviews))]
Marko Manb58468a2018-03-19 13:01:19 +0100287 if args.query:
288 reviews = fetch_query(args.gerrit, args.query)
Gabriele Md91609d2018-03-31 14:26:59 +0200289 change_numbers = [str(r['number']) for r in sorted(reviews, key=cmp_to_key(cmp_reviews))]
Marko Manb58468a2018-03-19 13:01:19 +0100290 if args.change_number:
Gabriele M1009aeb2018-04-01 17:50:58 +0200291 change_url_re = re.compile('https?://.+?/([0-9]+(?:/[0-9]+)?)/?')
Marko Manb58468a2018-03-19 13:01:19 +0100292 for c in args.change_number:
Gabriele M1009aeb2018-04-01 17:50:58 +0200293 change_number = change_url_re.findall(c)
294 if change_number:
295 change_numbers.extend(change_number)
296 elif '-' in c:
Marko Manb58468a2018-03-19 13:01:19 +0100297 templist = c.split('-')
298 for i in range(int(templist[0]), int(templist[1]) + 1):
299 change_numbers.append(str(i))
300 else:
301 change_numbers.append(c)
302 reviews = fetch_query(args.gerrit, ' OR '.join('change:{0}'.format(x.split('/')[0]) for x in change_numbers))
303
304 # make list of things to actually merge
305 mergables = []
306
307 # If --exclude is given, create the list of commits to ignore
308 exclude = []
309 if args.exclude:
310 exclude = args.exclude[0].split(',')
311
312 for change in change_numbers:
313 patchset = None
314 if '/' in change:
315 (change, patchset) = change.split('/')
316
317 if change in exclude:
318 continue
319
320 change = int(change)
321
322 if patchset is not None:
323 patchset = int(patchset)
324
325 review = next((x for x in reviews if x['number'] == change), None)
326 if review is None:
327 print('Change %d not found, skipping' % change)
328 continue
329
330 mergables.append({
331 'subject': review['subject'],
332 'project': review['project'],
333 'branch': review['branch'],
334 'change_id': review['change_id'],
335 'change_number': review['number'],
336 'status': review['status'],
Gabriele M1188cbd2018-04-01 17:50:57 +0200337 'fetch': None,
338 'patchset': review['revisions'][review['current_revision']]['_number'],
Marko Manb58468a2018-03-19 13:01:19 +0100339 })
Gabriele M1188cbd2018-04-01 17:50:57 +0200340
Marko Manb58468a2018-03-19 13:01:19 +0100341 mergables[-1]['fetch'] = review['revisions'][review['current_revision']]['fetch']
342 mergables[-1]['id'] = change
343 if patchset:
344 try:
345 mergables[-1]['fetch'] = [review['revisions'][x]['fetch'] for x in review['revisions'] if review['revisions'][x]['_number'] == patchset][0]
346 mergables[-1]['id'] = '{0}/{1}'.format(change, patchset)
Gabriele M1188cbd2018-04-01 17:50:57 +0200347 mergables[-1]['patchset'] = patchset
Marko Manb58468a2018-03-19 13:01:19 +0100348 except (IndexError, ValueError):
349 args.quiet or print('ERROR: The patch set {0}/{1} could not be found, using CURRENT_REVISION instead.'.format(change, patchset))
350
351 for item in mergables:
352 args.quiet or print('Applying change number {0}...'.format(item['id']))
353 # Check if change is open and exit if it's not, unless -f is specified
354 if (item['status'] != 'OPEN' and item['status'] != 'NEW' and item['status'] != 'DRAFT') and not args.query:
355 if args.force:
356 print('!! Force-picking a closed change !!\n')
357 else:
358 print('Change status is ' + item['status'] + '. Skipping the cherry pick.\nUse -f to force this pick.')
359 continue
Pulser72e23242013-09-29 09:56:55 +0100360
361 # Convert the project name to a project path
362 # - check that the project path exists
Marko Manb58468a2018-03-19 13:01:19 +0100363 project_path = None
364
365 if item['project'] in project_name_to_data and item['branch'] in project_name_to_data[item['project']]:
366 project_path = project_name_to_data[item['project']][item['branch']]
367 elif args.path:
368 project_path = args.path
Adrian DC76b15f92019-10-13 12:34:06 +0200369 elif item['project'] in project_name_to_data and len(project_name_to_data[item['project']]) == 1:
370 local_branch = list(project_name_to_data[item['project']])[0]
371 project_path = project_name_to_data[item['project']][local_branch]
372 print('WARNING: Project {0} has a different branch ("{1}" != "{2}")'.format(project_path, local_branch, item['branch']))
Pulser72e23242013-09-29 09:56:55 +0100373 elif args.ignore_missing:
Marko Manb58468a2018-03-19 13:01:19 +0100374 print('WARNING: Skipping {0} since there is no project directory for: {1}\n'.format(item['id'], item['project']))
375 continue
Pulser72e23242013-09-29 09:56:55 +0100376 else:
Marko Manb58468a2018-03-19 13:01:19 +0100377 sys.stderr.write('ERROR: For {0}, could not determine the project path for project {1}\n'.format(item['id'], item['project']))
378 sys.exit(1)
Pulser72e23242013-09-29 09:56:55 +0100379
380 # If --start-branch is given, create the branch (more than once per path is okay; repo ignores gracefully)
381 if args.start_branch:
Marko Manb58468a2018-03-19 13:01:19 +0100382 subprocess.check_output(['repo', 'start', args.start_branch[0], project_path])
383
384 # Determine the maximum commits to check already picked changes
385 check_picked_count = args.check_picked
386 branch_commits_count = int(subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], cwd=project_path))
387 if branch_commits_count <= check_picked_count:
388 check_picked_count = branch_commits_count - 1
389
390 # Check if change is already picked to HEAD...HEAD~check_picked_count
391 found_change = False
392 for i in range(0, check_picked_count):
393 if subprocess.call(['git', 'cat-file', '-e', 'HEAD~{0}'.format(i)], cwd=project_path, stderr=open(os.devnull, 'wb')):
394 continue
Simon Shields6a726492019-11-18 23:56:08 +1100395 output = subprocess.check_output(['git', 'show', '-q', 'HEAD~{0}'.format(i)], cwd=project_path)
396 # make sure we have a string on Python 3
397 if isinstance(output, bytes):
398 output = output.decode('utf-8')
399 output = output.split()
Marko Manb58468a2018-03-19 13:01:19 +0100400 if 'Change-Id:' in output:
401 head_change_id = ''
402 for j,t in enumerate(reversed(output)):
403 if t == 'Change-Id:':
404 head_change_id = output[len(output) - j]
405 break
406 if head_change_id.strip() == item['change_id']:
407 print('Skipping {0} - already picked in {1} as HEAD~{2}'.format(item['id'], project_path, i))
408 found_change = True
409 break
410 if found_change:
411 continue
Pulser72e23242013-09-29 09:56:55 +0100412
413 # Print out some useful info
414 if not args.quiet:
LuK133774f08b02019-09-21 11:47:33 +0200415 print(u'--> Subject: "{0}"'.format(item['subject']))
Marko Manb58468a2018-03-19 13:01:19 +0100416 print('--> Project path: {0}'.format(project_path))
Gabriele M1188cbd2018-04-01 17:50:57 +0200417 print('--> Change number: {0} (Patch Set {1})'.format(item['id'], item['patchset']))
Pulser72e23242013-09-29 09:56:55 +0100418
Marko Manb58468a2018-03-19 13:01:19 +0100419 if 'anonymous http' in item['fetch']:
420 method = 'anonymous http'
Pulser72e23242013-09-29 09:56:55 +0100421 else:
Marko Manb58468a2018-03-19 13:01:19 +0100422 method = 'ssh'
Pulser72e23242013-09-29 09:56:55 +0100423
Marko Manb58468a2018-03-19 13:01:19 +0100424 # Try fetching from GitHub first if using default gerrit
425 if args.gerrit == default_gerrit:
426 if args.verbose:
427 print('Trying to fetch the change from GitHub')
428
429 if args.pull:
430 cmd = ['git pull --no-edit omnirom', item['fetch'][method]['ref']]
431 else:
432 cmd = ['git fetch omnirom', item['fetch'][method]['ref']]
433 if args.quiet:
434 cmd.append('--quiet')
435 else:
436 print(cmd)
437 result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True)
438 FETCH_HEAD = '{0}/.git/FETCH_HEAD'.format(project_path)
439 if result != 0 and os.stat(FETCH_HEAD).st_size != 0:
440 print('ERROR: git command failed')
441 sys.exit(result)
442 # Check if it worked
443 if args.gerrit != default_gerrit or os.stat(FETCH_HEAD).st_size == 0:
444 # If not using the default gerrit or github failed, fetch from gerrit.
445 if args.verbose:
446 if args.gerrit == default_gerrit:
447 print('Fetching from GitHub didn\'t work, trying to fetch the change from Gerrit')
448 else:
449 print('Fetching from {0}'.format(args.gerrit))
450
451 if args.pull:
452 cmd = ['git pull --no-edit', item['fetch'][method]['url'], item['fetch'][method]['ref']]
453 else:
454 cmd = ['git fetch', item['fetch'][method]['url'], item['fetch'][method]['ref']]
455 if args.quiet:
456 cmd.append('--quiet')
457 else:
458 print(cmd)
459 result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True)
460 if result != 0:
461 print('ERROR: git command failed')
462 sys.exit(result)
463 # Perform the cherry-pick
464 if not args.pull:
Tim Schumacher074b77b2018-10-13 14:37:54 +0200465 cmd = ['git cherry-pick --ff FETCH_HEAD']
Marko Manb58468a2018-03-19 13:01:19 +0100466 if args.quiet:
467 cmd_out = open(os.devnull, 'wb')
468 else:
469 cmd_out = None
470 result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
471 if result != 0:
Adrian DC7bc808f2018-08-30 23:07:23 +0200472 cmd = ['git diff-index --quiet HEAD --']
473 result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
474 if result == 0:
475 print('WARNING: git command resulted with an empty commit, aborting cherry-pick')
476 cmd = ['git cherry-pick --abort']
477 subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
478 elif args.reset:
Marko Manb58468a2018-03-19 13:01:19 +0100479 print('ERROR: git command failed, aborting cherry-pick')
480 cmd = ['git cherry-pick --abort']
481 subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out)
Adrian DC7bc808f2018-08-30 23:07:23 +0200482 sys.exit(result)
Marko Manb58468a2018-03-19 13:01:19 +0100483 else:
484 print('ERROR: git command failed')
Adrian DC7bc808f2018-08-30 23:07:23 +0200485 sys.exit(result)
Pulser72e23242013-09-29 09:56:55 +0100486 if not args.quiet:
Marko Manb58468a2018-03-19 13:01:19 +0100487 print('')