Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 3 | # Copyright (C) 2013-15 The CyanogenMod Project |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # |
| 17 | |
| 18 | # |
| 19 | # Run repopick.py -h for a description of this utility. |
| 20 | # |
| 21 | |
| 22 | from __future__ import print_function |
| 23 | |
| 24 | import sys |
| 25 | import json |
| 26 | import os |
| 27 | import subprocess |
| 28 | import re |
| 29 | import argparse |
| 30 | import textwrap |
| 31 | |
| 32 | try: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 33 | # For python3 |
| 34 | import urllib.error |
| 35 | import urllib.request |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 36 | except ImportError: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 37 | # For python2 |
| 38 | import imp |
| 39 | import urllib2 |
| 40 | urllib = imp.new_module('urllib') |
| 41 | urllib.error = urllib2 |
| 42 | urllib.request = urllib2 |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 43 | |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 44 | |
| 45 | # Verifies whether pathA is a subdirectory (or the same) as pathB |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 46 | def is_subdir(a, b): |
| 47 | a = os.path.realpath(a) + '/' |
| 48 | b = os.path.realpath(b) + '/' |
| 49 | return b == a[:len(b)] |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 50 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 51 | |
| 52 | def fetch_query_via_ssh(remote_url, query): |
| 53 | """Given a remote_url and a query, return the list of changes that fit it |
| 54 | This function is slightly messy - the ssh api does not return data in the same structure as the HTTP REST API |
| 55 | We have to get the data, then transform it to match what we're expecting from the HTTP RESET API""" |
| 56 | if remote_url.count(':') == 2: |
| 57 | (uri, userhost, port) = remote_url.split(':') |
Tom Powell | ff0032a | 2015-09-01 16:52:40 -0700 | [diff] [blame] | 58 | userhost = userhost[2:] |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 59 | elif remote_url.count(':') == 1: |
| 60 | (uri, userhost) = remote_url.split(':') |
Tom Powell | ff0032a | 2015-09-01 16:52:40 -0700 | [diff] [blame] | 61 | userhost = userhost[2:] |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 62 | port = 29418 |
| 63 | else: |
| 64 | raise Exception('Malformed URI: Expecting ssh://[user@]host[:port]') |
| 65 | |
| 66 | |
| 67 | out = subprocess.check_output(['ssh', '-x', '-p{0}'.format(port), userhost, 'gerrit', 'query', '--format=JSON --patch-sets --current-patch-set', query]) |
| 68 | |
| 69 | reviews = [] |
| 70 | for line in out.split('\n'): |
| 71 | try: |
| 72 | data = json.loads(line) |
| 73 | # make our data look like the http rest api data |
| 74 | review = { |
| 75 | 'branch': data['branch'], |
| 76 | 'change_id': data['id'], |
| 77 | 'current_revision': data['currentPatchSet']['revision'], |
| 78 | 'number': int(data['number']), |
| 79 | 'revisions': {patch_set['revision']: { |
| 80 | 'number': int(patch_set['number']), |
| 81 | 'fetch': { |
| 82 | 'ssh': { |
| 83 | 'ref': patch_set['ref'], |
| 84 | 'url': u'ssh://{0}:{1}/{2}'.format(userhost, port, data['project']) |
| 85 | } |
| 86 | } |
| 87 | } for patch_set in data['patchSets']}, |
| 88 | 'subject': data['subject'], |
| 89 | 'project': data['project'], |
| 90 | 'status': data['status'] |
| 91 | } |
| 92 | reviews.append(review) |
Tom Powell | ff0032a | 2015-09-01 16:52:40 -0700 | [diff] [blame] | 93 | except: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 94 | pass |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 95 | args.quiet or print('Found {0} reviews'.format(len(reviews))) |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 96 | return reviews |
| 97 | |
| 98 | |
| 99 | def fetch_query_via_http(remote_url, query): |
| 100 | |
| 101 | """Given a query, fetch the change numbers via http""" |
| 102 | url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS'.format(remote_url, query) |
| 103 | data = urllib.request.urlopen(url).read().decode('utf-8') |
| 104 | reviews = json.loads(data[5:]) |
| 105 | |
| 106 | for review in reviews: |
| 107 | review[u'number'] = review.pop('_number') |
| 108 | |
| 109 | return reviews |
| 110 | |
| 111 | |
| 112 | def fetch_query(remote_url, query): |
| 113 | """Wrapper for fetch_query_via_proto functions""" |
Tom Powell | ff0032a | 2015-09-01 16:52:40 -0700 | [diff] [blame] | 114 | if remote_url[0:3] == 'ssh': |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 115 | return fetch_query_via_ssh(remote_url, query) |
| 116 | elif remote_url[0:4] == 'http': |
| 117 | return fetch_query_via_http(remote_url, query.replace(' ', '+')) |
| 118 | else: |
| 119 | raise Exception('Gerrit URL should be in the form http[s]://hostname/ or ssh://[user@]host[:port]') |
| 120 | |
| 121 | if __name__ == '__main__': |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 122 | # Default to CyanogenMod Gerrit |
| 123 | default_gerrit = 'http://review.cyanogenmod.org' |
| 124 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 125 | parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\ |
| 126 | repopick.py is a utility to simplify the process of cherry picking |
| 127 | patches from CyanogenMod's Gerrit instance (or any gerrit instance of your choosing) |
| 128 | |
| 129 | Given a list of change numbers, repopick will cd into the project path |
| 130 | and cherry pick the latest patch available. |
| 131 | |
| 132 | With the --start-branch argument, the user can specify that a branch |
| 133 | should be created before cherry picking. This is useful for |
| 134 | cherry-picking many patches into a common branch which can be easily |
| 135 | abandoned later (good for testing other's changes.) |
| 136 | |
| 137 | The --abandon-first argument, when used in conjunction with the |
| 138 | --start-branch option, will cause repopick to abandon the specified |
| 139 | branch in all repos first before performing any cherry picks.''')) |
| 140 | parser.add_argument('change_number', nargs='*', help='change number to cherry pick. Use {change number}/{patchset number} to get a specific revision.') |
| 141 | parser.add_argument('-i', '--ignore-missing', action='store_true', help='do not error out if a patch applies to a missing directory') |
| 142 | parser.add_argument('-s', '--start-branch', nargs=1, help='start the specified branch before cherry picking') |
| 143 | parser.add_argument('-a', '--abandon-first', action='store_true', help='before cherry picking, abandon the branch specified in --start-branch') |
| 144 | parser.add_argument('-b', '--auto-branch', action='store_true', help='shortcut to "--start-branch auto --abandon-first --ignore-missing"') |
| 145 | parser.add_argument('-q', '--quiet', action='store_true', help='print as little as possible') |
| 146 | parser.add_argument('-v', '--verbose', action='store_true', help='print extra information to aid in debug') |
jrior001 | fd11d07 | 2015-08-21 17:23:25 -0400 | [diff] [blame] | 147 | parser.add_argument('-f', '--force', action='store_true', help='force cherry pick even if change is closed') |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 148 | parser.add_argument('-p', '--pull', action='store_true', help='execute pull instead of cherry-pick') |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 149 | parser.add_argument('-P', '--path', help='use the specified path for the change') |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 150 | parser.add_argument('-t', '--topic', help='pick all commits from a specified topic') |
| 151 | parser.add_argument('-Q', '--query', help='pick all commits using the specified query') |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 152 | parser.add_argument('-g', '--gerrit', default=default_gerrit, help='Gerrit Instance to use. Form proto://[user@]host[:port]') |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 153 | args = parser.parse_args() |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 154 | if not args.start_branch and args.abandon_first: |
| 155 | parser.error('if --abandon-first is set, you must also give the branch name with --start-branch') |
| 156 | if args.auto_branch: |
| 157 | args.abandon_first = True |
| 158 | args.ignore_missing = True |
| 159 | if not args.start_branch: |
| 160 | args.start_branch = ['auto'] |
| 161 | if args.quiet and args.verbose: |
| 162 | parser.error('--quiet and --verbose cannot be specified together') |
| 163 | |
| 164 | if (1 << bool(args.change_number) << bool(args.topic) << bool(args.query)) != 2: |
| 165 | parser.error('One (and only one) of change_number, topic, and query are allowed') |
| 166 | |
| 167 | # Change current directory to the top of the tree |
| 168 | if 'ANDROID_BUILD_TOP' in os.environ: |
| 169 | top = os.environ['ANDROID_BUILD_TOP'] |
| 170 | |
| 171 | if not is_subdir(os.getcwd(), top): |
| 172 | sys.stderr.write('ERROR: You must run this tool from within $ANDROID_BUILD_TOP!\n') |
| 173 | sys.exit(1) |
| 174 | os.chdir(os.environ['ANDROID_BUILD_TOP']) |
| 175 | |
| 176 | # Sanity check that we are being run from the top level of the tree |
| 177 | if not os.path.isdir('.repo'): |
| 178 | sys.stderr.write('ERROR: No .repo directory found. Please run this from the top of your tree.\n') |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 179 | sys.exit(1) |
| 180 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 181 | # If --abandon-first is given, abandon the branch before starting |
| 182 | if args.abandon_first: |
| 183 | # Determine if the branch already exists; skip the abandon if it does not |
| 184 | plist = subprocess.check_output(['repo', 'info']) |
| 185 | needs_abandon = False |
| 186 | for pline in plist: |
| 187 | matchObj = re.match(r'Local Branches.*\[(.*)\]', pline) |
| 188 | if matchObj: |
| 189 | local_branches = re.split('\s*,\s*', matchObj.group(1)) |
| 190 | if any(args.start_branch[0] in s for s in local_branches): |
| 191 | needs_abandon = True |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 192 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 193 | if needs_abandon: |
| 194 | # Perform the abandon only if the branch already exists |
| 195 | if not args.quiet: |
| 196 | print('Abandoning branch: %s' % args.start_branch[0]) |
| 197 | subprocess.check_output(['repo', 'abandon', args.start_branch[0]]) |
| 198 | if not args.quiet: |
| 199 | print('') |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 200 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 201 | # Get the list of projects that repo knows about |
| 202 | # - convert the project name to a project path |
| 203 | project_name_to_path = {} |
| 204 | plist = subprocess.check_output(['repo', 'list']).split('\n') |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 205 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 206 | for pline in plist: |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 207 | if not pline: |
| 208 | break |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 209 | ppaths = pline.split(' : ') |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 210 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 211 | project_name_to_path[ppaths[1]] = ppaths[0] |
| 212 | |
| 213 | # get data on requested changes |
| 214 | reviews = [] |
| 215 | change_numbers = [] |
| 216 | if args.topic: |
| 217 | reviews = fetch_query(args.gerrit, 'topic:{0}'.format(args.topic)) |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 218 | change_numbers = sorted([str(r['number']) for r in reviews]) |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 219 | if args.query: |
| 220 | reviews = fetch_query(args.gerrit, args.query) |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 221 | change_numbers = sorted([str(r['number']) for r in reviews]) |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 222 | if args.change_number: |
| 223 | reviews = fetch_query(args.gerrit, ' OR '.join('change:{0}'.format(x.split('/')[0]) for x in args.change_number)) |
| 224 | change_numbers = args.change_number |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 225 | |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 226 | # make list of things to actually merge |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 227 | mergables = [] |
| 228 | |
| 229 | for change in change_numbers: |
| 230 | patchset = None |
| 231 | if '/' in change: |
| 232 | (change, patchset) = change.split('/') |
| 233 | change = int(change) |
| 234 | |
| 235 | review = [x for x in reviews if x['number'] == change][0] |
| 236 | mergables.append({ |
| 237 | 'subject': review['subject'], |
| 238 | 'project': review['project'], |
| 239 | 'branch': review['branch'], |
| 240 | 'change_number': review['number'], |
| 241 | 'status': review['status'], |
| 242 | 'fetch': None |
| 243 | }) |
| 244 | mergables[-1]['fetch'] = review['revisions'][review['current_revision']]['fetch'] |
| 245 | mergables[-1]['id'] = change |
| 246 | if patchset: |
| 247 | try: |
| 248 | mergables[-1]['fetch'] = [x['fetch'] for x in review['revisions'] if x['_number'] == patchset][0] |
| 249 | mergables[-1]['id'] = '{0}/{1}'.format(change, patchset) |
| 250 | except (IndexError, ValueError): |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 251 | args.quiet or print('ERROR: The patch set {0}/{1} could not be found, using CURRENT_REVISION instead.'.format(change, patchset)) |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 252 | |
| 253 | for item in mergables: |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 254 | args.quiet or print('Applying change number {0}...'.format(item['id'])) |
jrior001 | fd11d07 | 2015-08-21 17:23:25 -0400 | [diff] [blame] | 255 | # Check if change is open and exit if it's not, unless -f is specified |
Tom Powell | c627f07 | 2015-09-02 05:46:55 +0000 | [diff] [blame] | 256 | if (item['status'] != 'OPEN' and item['status'] != 'NEW') and not args.query: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 257 | if args.force: |
jrior001 | fd11d07 | 2015-08-21 17:23:25 -0400 | [diff] [blame] | 258 | print('!! Force-picking a closed change !!\n') |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 259 | else: |
Dan Pasanen | fe63628 | 2015-09-09 23:38:16 -0500 | [diff] [blame] | 260 | print('Change status is ' + item['status'] + '. Skipping the cherry pick.\nUse -f to force this pick.') |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 261 | continue |
| 262 | |
| 263 | # Convert the project name to a project path |
| 264 | # - check that the project path exists |
| 265 | project_path = None |
| 266 | if item['project'] in project_name_to_path: |
| 267 | project_path = project_name_to_path[item['project']] |
| 268 | |
| 269 | if project_path.startswith('hardware/qcom/'): |
| 270 | split_path = project_path.split('/') |
| 271 | # split_path[2] might be display or it might be display-caf, trim the -caf |
| 272 | split_path[2] = split_path[2].split('-')[0] |
| 273 | |
| 274 | # Need to treat hardware/qcom/{audio,display,media} specially |
| 275 | if split_path[2] == 'audio' or split_path[2] == 'display' or split_path[2] == 'media': |
| 276 | split_branch = item['branch'].split('-') |
| 277 | |
| 278 | # display is extra special |
| 279 | if split_path[2] == 'display' and len(split_path) == 3: |
| 280 | project_path = '/'.join(split_path) |
| 281 | else: |
| 282 | project_path = '/'.join(split_path[:-1]) |
| 283 | |
| 284 | if len(split_branch) == 4 and split_branch[0] == 'cm' and split_branch[2] == 'caf': |
| 285 | project_path += '-caf/msm' + split_branch[3] |
| 286 | # audio and media are different from display |
| 287 | elif split_path[2] == 'audio' or split_path[2] == 'media': |
| 288 | project_path += '/default' |
Anthony King | 8e1ccda | 2015-07-20 17:39:20 -0400 | [diff] [blame] | 289 | elif project_path.startswith('hardware/ril'): |
| 290 | project_path = project_path.rstrip('-caf') |
| 291 | if item["branch"].split('-')[-1] == 'caf': |
| 292 | project_path += '-caf' |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 293 | elif args.path: |
| 294 | project_path = args.path |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 295 | elif args.ignore_missing: |
| 296 | print('WARNING: Skipping {0} since there is no project directory for: {1}\n'.format(item['id'], item['project'])) |
| 297 | continue |
| 298 | else: |
| 299 | sys.stderr.write('ERROR: For {0}, could not determine the project path for project {1}\n'.format(item['id'], item['project'])) |
| 300 | sys.exit(1) |
| 301 | |
| 302 | # If --start-branch is given, create the branch (more than once per path is okay; repo ignores gracefully) |
| 303 | if args.start_branch: |
| 304 | subprocess.check_output(['repo', 'start', args.start_branch[0], project_path]) |
| 305 | |
| 306 | # Print out some useful info |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 307 | if not args.quiet: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 308 | print('--> Subject: "{0}"'.format(item['subject'])) |
| 309 | print('--> Project path: {0}'.format(project_path)) |
| 310 | print('--> Change number: {0} (Patch Set {0})'.format(item['id'])) |
| 311 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 312 | if 'anonymous http' in item['fetch']: |
| 313 | method = 'anonymous http' |
| 314 | else: |
| 315 | method = 'ssh' |
| 316 | |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 317 | # Try fetching from GitHub first if using default gerrit |
| 318 | if args.gerrit == default_gerrit: |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 319 | if args.verbose: |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 320 | print('Trying to fetch the change from GitHub') |
| 321 | |
| 322 | if args.pull: |
| 323 | cmd = ['git pull --no-edit github', item['fetch'][method]['ref']] |
| 324 | else: |
| 325 | cmd = ['git fetch github', item['fetch'][method]['ref']] |
| 326 | if args.quiet: |
| 327 | cmd.append('--quiet') |
| 328 | else: |
| 329 | print(cmd) |
| 330 | result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True) |
| 331 | if result != 0: |
| 332 | print('ERROR: git command failed') |
| 333 | sys.exit(result) |
| 334 | FETCH_HEAD = '{0}/.git/FETCH_HEAD'.format(project_path) |
| 335 | # Check if it worked |
| 336 | if args.gerrit != default_gerrit or os.stat(FETCH_HEAD).st_size == 0: |
| 337 | # If not using the default gerrit or github failed, fetch from gerrit. |
| 338 | if args.verbose: |
| 339 | if args.gerrit == default_gerrit: |
| 340 | print('Fetching from GitHub didn\'t work, trying to fetch the change from Gerrit') |
| 341 | else: |
| 342 | print('Fetching from {0}'.format(args.gerrit)) |
| 343 | |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 344 | if args.pull: |
| 345 | cmd = ['git pull --no-edit', item['fetch'][method]['url'], item['fetch'][method]['ref']] |
| 346 | else: |
| 347 | cmd = ['git fetch', item['fetch'][method]['url'], item['fetch'][method]['ref']] |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 348 | if args.quiet: |
| 349 | cmd.append('--quiet') |
| 350 | else: |
| 351 | print(cmd) |
| 352 | result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True) |
| 353 | if result != 0: |
| 354 | print('ERROR: git command failed') |
| 355 | sys.exit(result) |
Tom Powell | c858030 | 2015-08-04 15:37:12 -0700 | [diff] [blame] | 356 | # Perform the cherry-pick |
| 357 | if not args.pull: |
| 358 | cmd = ['git cherry-pick FETCH_HEAD'] |
Brint E. Kriebel | 9c1a3c3 | 2015-09-09 22:29:28 -0700 | [diff] [blame^] | 359 | if args.quiet: |
| 360 | cmd_out = open(os.devnull, 'wb') |
| 361 | else: |
| 362 | cmd_out = None |
| 363 | result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out) |
| 364 | if result != 0: |
| 365 | print('ERROR: git command failed') |
| 366 | sys.exit(result) |
Chirayu Desai | 4a319b8 | 2013-06-05 20:14:33 +0530 | [diff] [blame] | 367 | if not args.quiet: |
| 368 | print('') |
| 369 | |