2008-10-23 18:58:52 +00:00
|
|
|
# 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
|
2023-08-22 01:20:32 +00:00
|
|
|
from error import GitError
|
|
|
|
from error import NoSuchProjectError
|
|
|
|
from error import RepoExitError
|
2023-09-14 08:46:44 +00:00
|
|
|
from repo_logging import RepoLogger
|
2023-08-22 01:20:32 +00:00
|
|
|
|
2008-10-23 18:58:52 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
CHANGE_RE = re.compile(r"^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$")
|
2023-09-14 08:46:44 +00:00
|
|
|
logger = RepoLogger(__file__)
|
2008-10-23 18:58:52 +00:00
|
|
|
|
2020-02-12 06:20:19 +00:00
|
|
|
|
2023-08-08 21:12:53 +00:00
|
|
|
class DownloadCommandError(RepoExitError):
|
|
|
|
"""Error raised when download command fails."""
|
|
|
|
|
|
|
|
|
2008-10-23 18:58:52 +00:00
|
|
|
class Download(Command):
|
2023-03-11 06:46:20 +00:00
|
|
|
COMMON = True
|
|
|
|
helpSummary = "Download and checkout a change"
|
|
|
|
helpUsage = """
|
2017-06-29 07:15:54 +00:00
|
|
|
%prog {[project] change[/patchset]}...
|
2008-10-23 18:58:52 +00:00
|
|
|
"""
|
2023-03-11 06:46:20 +00:00
|
|
|
helpDescription = """
|
2008-10-23 18:58:52 +00:00
|
|
|
The '%prog' command downloads a change from the review system and
|
|
|
|
makes it available in your project's local working directory.
|
2017-06-29 07:15:54 +00:00
|
|
|
If no project is specified try to use current directory as a project.
|
2008-10-23 18:58:52 +00:00
|
|
|
"""
|
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
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:
|
2023-09-14 08:46:44 +00:00
|
|
|
logger.error(
|
2023-03-11 06:46:20 +00:00
|
|
|
"error: %s matches too many projects; please "
|
2023-09-14 08:46:44 +00:00
|
|
|
"re-run inside the project checkout.",
|
|
|
|
a,
|
2023-03-11 06:46:20 +00:00
|
|
|
)
|
|
|
|
for project in projects:
|
2023-09-14 08:46:44 +00:00
|
|
|
logger.error(
|
|
|
|
" %s/ @ %s",
|
|
|
|
project.RelPath(local=opt.this_manifest_only),
|
|
|
|
project.revisionExpr,
|
2023-03-11 06:46:20 +00:00
|
|
|
)
|
2023-07-14 23:45:35 +00:00
|
|
|
raise NoSuchProjectError()
|
2023-03-11 06:46:20 +00:00
|
|
|
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):
|
2023-08-08 21:12:53 +00:00
|
|
|
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):
|
2023-03-11 06:46:20 +00:00
|
|
|
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:
|
2023-09-14 08:46:44 +00:00
|
|
|
logger.error(
|
|
|
|
"[%s] change %d/%d has already been merged",
|
|
|
|
project.name,
|
|
|
|
change_id,
|
|
|
|
ps_id,
|
2023-03-11 06:46:20 +00:00
|
|
|
)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if len(dl.commits) > 1:
|
2023-09-14 08:46:44 +00:00
|
|
|
logger.error(
|
|
|
|
"[%s] %d/%d depends on %d unmerged changes:",
|
|
|
|
project.name,
|
|
|
|
change_id,
|
|
|
|
ps_id,
|
|
|
|
len(dl.commits),
|
2023-03-11 06:46:20 +00:00
|
|
|
)
|
|
|
|
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:
|
2023-09-14 08:46:44 +00:00
|
|
|
logger.error(
|
|
|
|
"[%s] Could not complete the %s of %s",
|
|
|
|
project.name,
|
|
|
|
mode,
|
|
|
|
dl.commit,
|
2023-03-11 06:46:20 +00:00
|
|
|
)
|
2023-08-08 21:12:53 +00:00
|
|
|
raise
|