| # Copyright (C) 2008 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import re |
| import sys |
| |
| from command import Command |
| from error import GitError, NoSuchProjectError |
| |
| CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$') |
| |
| |
| class Download(Command): |
| COMMON = True |
| helpSummary = "Download and checkout a change" |
| helpUsage = """ |
| %prog {[project] change[/patchset]}... |
| """ |
| helpDescription = """ |
| The '%prog' command downloads a change from the review system and |
| makes it available in your project's local working directory. |
| If no project is specified try to use current directory as a project. |
| """ |
| |
| def _Options(self, p): |
| p.add_option('-b', '--branch', |
| help='create a new branch first') |
| p.add_option('-c', '--cherry-pick', |
| dest='cherrypick', action='store_true', |
| help="cherry-pick instead of checkout") |
| p.add_option('-x', '--record-origin', action='store_true', |
| help='pass -x when cherry-picking') |
| p.add_option('-r', '--revert', |
| dest='revert', action='store_true', |
| help="revert instead of checkout") |
| p.add_option('-f', '--ff-only', |
| dest='ffonly', action='store_true', |
| help="force fast-forward merge") |
| |
| def _ParseChangeIds(self, opt, args): |
| if not args: |
| self.Usage() |
| |
| to_get = [] |
| project = None |
| |
| for a in args: |
| m = CHANGE_RE.match(a) |
| if m: |
| if not project: |
| project = self.GetProjects(".")[0] |
| print('Defaulting to cwd project', project.name) |
| chg_id = int(m.group(1)) |
| if m.group(2): |
| ps_id = int(m.group(2)) |
| else: |
| ps_id = 1 |
| refs = 'refs/changes/%2.2d/%d/' % (chg_id % 100, chg_id) |
| output = project._LsRemote(refs + '*') |
| if output: |
| regex = refs + r'(\d+)' |
| rcomp = re.compile(regex, re.I) |
| for line in output.splitlines(): |
| match = rcomp.search(line) |
| if match: |
| ps_id = max(int(match.group(1)), ps_id) |
| to_get.append((project, chg_id, ps_id)) |
| else: |
| projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only) |
| if len(projects) > 1: |
| # If the cwd is one of the projects, assume they want that. |
| try: |
| project = self.GetProjects('.')[0] |
| except NoSuchProjectError: |
| project = None |
| if project not in projects: |
| print('error: %s matches too many projects; please re-run inside ' |
| 'the project checkout.' % (a,), file=sys.stderr) |
| for project in projects: |
| print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only), |
| project.revisionExpr), file=sys.stderr) |
| sys.exit(1) |
| else: |
| project = projects[0] |
| print('Defaulting to cwd project', project.name) |
| return to_get |
| |
| def ValidateOptions(self, opt, args): |
| if opt.record_origin: |
| if not opt.cherrypick: |
| self.OptionParser.error('-x only makes sense with --cherry-pick') |
| |
| if opt.ffonly: |
| self.OptionParser.error('-x and --ff are mutually exclusive options') |
| |
| def Execute(self, opt, args): |
| for project, change_id, ps_id in self._ParseChangeIds(opt, args): |
| dl = project.DownloadPatchSet(change_id, ps_id) |
| if not dl: |
| print('[%s] change %d/%d not found' |
| % (project.name, change_id, ps_id), |
| file=sys.stderr) |
| sys.exit(1) |
| |
| if not opt.revert and not dl.commits: |
| print('[%s] change %d/%d has already been merged' |
| % (project.name, change_id, ps_id), |
| file=sys.stderr) |
| continue |
| |
| if len(dl.commits) > 1: |
| print('[%s] %d/%d depends on %d unmerged changes:' |
| % (project.name, change_id, ps_id, len(dl.commits)), |
| file=sys.stderr) |
| for c in dl.commits: |
| print(' %s' % (c), file=sys.stderr) |
| |
| if opt.cherrypick: |
| mode = 'cherry-pick' |
| elif opt.revert: |
| mode = 'revert' |
| elif opt.ffonly: |
| mode = 'fast-forward merge' |
| else: |
| mode = 'checkout' |
| |
| # We'll combine the branch+checkout operation, but all the rest need a |
| # dedicated branch start. |
| if opt.branch and mode != 'checkout': |
| project.StartBranch(opt.branch) |
| |
| try: |
| if opt.cherrypick: |
| project._CherryPick(dl.commit, ffonly=opt.ffonly, |
| record_origin=opt.record_origin) |
| elif opt.revert: |
| project._Revert(dl.commit) |
| elif opt.ffonly: |
| project._FastForward(dl.commit, ffonly=True) |
| else: |
| if opt.branch: |
| project.StartBranch(opt.branch, revision=dl.commit) |
| else: |
| project._Checkout(dl.commit) |
| |
| except GitError: |
| print('[%s] Could not complete the %s of %s' |
| % (project.name, mode, dl.commit), file=sys.stderr) |
| sys.exit(1) |