blob: 4396c9e7f58f6b59ef96ae932fc103a5405b6023 [file] [log] [blame]
# 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
from error import NoSuchProjectError
from error import RepoExitError
from repo_logging import RepoLogger
CHANGE_RE = re.compile(r"^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$")
logger = RepoLogger(__file__)
class DownloadCommandError(RepoExitError):
"""Error raised when download command fails."""
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:
logger.error(
"error: %s matches too many projects; please "
"re-run inside the project checkout.",
a,
)
for project in projects:
logger.error(
" %s/ @ %s",
project.RelPath(local=opt.this_manifest_only),
project.revisionExpr,
)
raise NoSuchProjectError()
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):
try:
self._ExecuteHelper(opt, args)
except Exception as e:
if isinstance(e, RepoExitError):
raise e
raise DownloadCommandError(aggregate_errors=[e])
def _ExecuteHelper(self, opt, args):
for project, change_id, ps_id in self._ParseChangeIds(opt, args):
dl = project.DownloadPatchSet(change_id, ps_id)
if not opt.revert and not dl.commits:
logger.error(
"[%s] change %d/%d has already been merged",
project.name,
change_id,
ps_id,
)
continue
if len(dl.commits) > 1:
logger.error(
"[%s] %d/%d depends on %d unmerged changes:",
project.name,
change_id,
ps_id,
len(dl.commits),
)
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:
logger.error(
"[%s] Could not complete the %s of %s",
project.name,
mode,
dl.commit,
)
raise