diff --git a/project.py b/project.py index 2b4a4f95..e88afcca 100644 --- a/project.py +++ b/project.py @@ -45,6 +45,31 @@ def _info(fmt, *args): def not_rev(r): return '^' + r +class DownloadedChange(object): + _commit_cache = None + + def __init__(self, project, base, change_id, ps_id, commit): + self.project = project + self.base = base + self.change_id = change_id + self.ps_id = ps_id + self.commit = commit + + @property + def commits(self): + if self._commit_cache is None: + self._commit_cache = self.project.bare_git.rev_list( + '--abbrev=8', + '--abbrev-commit', + '--pretty=oneline', + '--reverse', + '--date-order', + not_rev(self.base), + self.commit, + '--') + return self._commit_cache + + class ReviewableBranch(object): _commit_cache = None @@ -612,6 +637,23 @@ class Project(object): src = os.path.join(self.worktree, src) self.copyfiles.append(_CopyFile(src, dest)) + def DownloadPatchSet(self, change_id, patch_id): + """Download a single patch set of a single change to FETCH_HEAD. + """ + remote = self.GetRemote(self.remote.name) + + cmd = ['fetch', remote.name] + cmd.append('refs/changes/%2.2d/%d/%d' \ + % (change_id % 100, change_id, patch_id)) + cmd.extend(map(lambda x: str(x), remote.fetch)) + if GitCommand(self, cmd, bare=True).Wait() != 0: + return None + return DownloadedChange(self, + remote.ToLocal(self.revision), + change_id, + patch_id, + self.bare_git.rev_parse('FETCH_HEAD')) + ## Branch Management ## diff --git a/subcmds/download.py b/subcmds/download.py new file mode 100644 index 00000000..a6f3aa45 --- /dev/null +++ b/subcmds/download.py @@ -0,0 +1,78 @@ +# +# 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 os +import re +import sys + +from command import Command + +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. +""" + + def _Options(self, p): + pass + + def _ParseChangeIds(self, args): + to_get = [] + project = None + + for a in args: + m = CHANGE_RE.match(a) + if m: + if not project: + self.Usage() + chg_id = int(m.group(1)) + if m.group(2): + ps_id = int(m.group(2)) + else: + ps_id = 1 + to_get.append((project, chg_id, ps_id)) + else: + project = self.GetProjects([a])[0] + return to_get + + def Execute(self, opt, args): + for project, change_id, ps_id in self._ParseChangeIds(args): + dl = project.DownloadPatchSet(change_id, ps_id) + if not dl: + print >>sys.stderr, \ + '[%s] change %d/%d not found' \ + % (project.name, change_id, ps_id) + sys.exit(1) + + if not dl.commits: + print >>sys.stderr, \ + '[%s] change %d/%d has already been merged' \ + % (project.name, change_id, ps_id) + continue + + if len(dl.commits) > 1: + print >>sys.stderr, \ + '[%s] %d/%d depends on %d unmerged changes:' \ + % (project.name, change_id, ps_id, len(dl.commits)) + for c in dl.commits: + print >>sys.stderr, ' %s' % (c) + project._Checkout(dl.commit)