Initial Contribution
diff --git a/subcmds/__init__.py b/subcmds/__init__.py
new file mode 100644
index 0000000..a2286e7
--- /dev/null
+++ b/subcmds/__init__.py
@@ -0,0 +1,49 @@
+#
+# 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
+
+all = {}
+
+my_dir = os.path.dirname(__file__)
+for py in os.listdir(my_dir):
+ if py == '__init__.py':
+ continue
+
+ if py.endswith('.py'):
+ name = py[:-3]
+
+ clsn = name.capitalize()
+ while clsn.find('_') > 0:
+ h = clsn.index('_')
+ clsn = clsn[0:h] + clsn[h + 1:].capitalize()
+
+ mod = __import__(__name__,
+ globals(),
+ locals(),
+ ['%s' % name])
+ mod = getattr(mod, name)
+ try:
+ cmd = getattr(mod, clsn)()
+ except AttributeError:
+ raise SyntaxError, '%s/%s does not define class %s' % (
+ __name__, py, clsn)
+
+ name = name.replace('_', '-')
+ cmd.NAME = name
+ all[name] = cmd
+
+if 'help' in all:
+ all['help'].commands = all
diff --git a/subcmds/compute_snapshot_check.py b/subcmds/compute_snapshot_check.py
new file mode 100644
index 0000000..82db359
--- /dev/null
+++ b/subcmds/compute_snapshot_check.py
@@ -0,0 +1,169 @@
+#
+# 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 sys
+import tempfile
+
+from command import Command
+from error import GitError, NoSuchProjectError
+from git_config import IsId
+from import_tar import ImportTar
+from import_zip import ImportZip
+from project import Project
+from remote import Remote
+
+def _ToCommit(project, rev):
+ return project.bare_git.rev_parse('--verify', '%s^0' % rev)
+
+def _Missing(project, rev):
+ return project._revlist('--objects', rev, '--not', '--all')
+
+
+class ComputeSnapshotCheck(Command):
+ common = False
+ helpSummary = "Compute the check value for a new snapshot"
+ helpUsage = """
+%prog -p NAME -v VERSION -s FILE [options]
+"""
+ helpDescription = """
+%prog computes and then displays the proper check value for a
+snapshot, so it can be pasted into the manifest file for a project.
+"""
+
+ def _Options(self, p):
+ g = p.add_option_group('Snapshot description options')
+ g.add_option('-p', '--project',
+ dest='project', metavar='NAME',
+ help='destination project name')
+ g.add_option('-v', '--version',
+ dest='version', metavar='VERSION',
+ help='upstream version/revision identifier')
+ g.add_option('-s', '--snapshot',
+ dest='snapshot', metavar='PATH',
+ help='local tarball path')
+ g.add_option('--new-project',
+ dest='new_project', action='store_true',
+ help='destinition is a new project')
+ g.add_option('--keep',
+ dest='keep_git', action='store_true',
+ help='keep the temporary git repository')
+
+ g = p.add_option_group('Base revision grafting options')
+ g.add_option('--prior',
+ dest='prior', metavar='COMMIT',
+ help='prior revision checksum')
+
+ g = p.add_option_group('Path mangling options')
+ g.add_option('--strip-prefix',
+ dest='strip_prefix', metavar='PREFIX',
+ help='remove prefix from all paths on import')
+ g.add_option('--insert-prefix',
+ dest='insert_prefix', metavar='PREFIX',
+ help='insert prefix before all paths on import')
+
+
+ def _Compute(self, opt):
+ try:
+ real_project = self.GetProjects([opt.project])[0]
+ except NoSuchProjectError:
+ if opt.new_project:
+ print >>sys.stderr, \
+ "warning: project '%s' does not exist" % opt.project
+ else:
+ raise NoSuchProjectError(opt.project)
+
+ self._tmpdir = tempfile.mkdtemp()
+ project = Project(manifest = self.manifest,
+ name = opt.project,
+ remote = Remote('origin'),
+ gitdir = os.path.join(self._tmpdir, '.git'),
+ worktree = self._tmpdir,
+ relpath = opt.project,
+ revision = 'refs/heads/master')
+ project._InitGitDir()
+
+ url = 'file://%s' % os.path.abspath(opt.snapshot)
+
+ imp = None
+ for cls in [ImportTar, ImportZip]:
+ if cls.CanAccept(url):
+ imp = cls()
+ break
+ if not imp:
+ print >>sys.stderr, 'error: %s unsupported' % opt.snapshot
+ sys.exit(1)
+
+ imp.SetProject(project)
+ imp.SetVersion(opt.version)
+ imp.AddUrl(url)
+
+ if opt.prior:
+ if opt.new_project:
+ if not IsId(opt.prior):
+ print >>sys.stderr, 'error: --prior=%s not valid' % opt.prior
+ sys.exit(1)
+ else:
+ try:
+ opt.prior = _ToCommit(real_project, opt.prior)
+ missing = _Missing(real_project, opt.prior)
+ except GitError, e:
+ print >>sys.stderr,\
+ 'error: --prior=%s not valid\n%s' \
+ % (opt.prior, e)
+ sys.exit(1)
+ if missing:
+ print >>sys.stderr,\
+ 'error: --prior=%s is valid, but is not reachable' \
+ % opt.prior
+ sys.exit(1)
+ imp.SetParent(opt.prior)
+
+ src = opt.strip_prefix
+ dst = opt.insert_prefix
+ if src or dst:
+ if src is None:
+ src = ''
+ if dst is None:
+ dst = ''
+ imp.RemapPath(src, dst)
+ commitId = imp.Import()
+
+ print >>sys.stderr,"%s\t%s" % (commitId, imp.version)
+ return project
+
+ def Execute(self, opt, args):
+ if args \
+ or not opt.project \
+ or not opt.version \
+ or not opt.snapshot:
+ self.Usage()
+
+ success = False
+ project = None
+ try:
+ self._tmpdir = None
+ project = self._Compute(opt)
+ finally:
+ if project and opt.keep_git:
+ print 'GIT_DIR = %s' % (project.gitdir)
+ elif self._tmpdir:
+ for root, dirs, files in os.walk(self._tmpdir, topdown=False):
+ for name in files:
+ os.remove(os.path.join(root, name))
+ for name in dirs:
+ os.rmdir(os.path.join(root, name))
+ os.rmdir(self._tmpdir)
+
diff --git a/subcmds/diff.py b/subcmds/diff.py
new file mode 100644
index 0000000..e024714
--- /dev/null
+++ b/subcmds/diff.py
@@ -0,0 +1,27 @@
+#
+# 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.
+
+from command import PagedCommand
+
+class Diff(PagedCommand):
+ common = True
+ helpSummary = "Show changes between commit and working tree"
+ helpUsage = """
+%prog [<project>...]
+"""
+
+ def Execute(self, opt, args):
+ for project in self.GetProjects(args):
+ project.PrintWorkTreeDiff()
diff --git a/subcmds/forall.py b/subcmds/forall.py
new file mode 100644
index 0000000..b22e22a
--- /dev/null
+++ b/subcmds/forall.py
@@ -0,0 +1,82 @@
+#
+# 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 os
+import sys
+import subprocess
+from command import Command
+
+class Forall(Command):
+ common = False
+ helpSummary = "Run a shell command in each project"
+ helpUsage = """
+%prog [<project>...] -c <command> [<arg>...]
+"""
+ helpDescription = """
+Executes the same shell command in each project.
+
+Environment
+-----------
+pwd is the project's working directory.
+
+REPO_PROJECT is set to the unique name of the project.
+
+shell positional arguments ($1, $2, .., $#) are set to any arguments
+following <command>.
+
+stdin, stdout, stderr are inherited from the terminal and are
+not redirected.
+"""
+
+ def _Options(self, p):
+ def cmd(option, opt_str, value, parser):
+ setattr(parser.values, option.dest, list(parser.rargs))
+ while parser.rargs:
+ del parser.rargs[0]
+ p.add_option('-c', '--command',
+ help='Command (and arguments) to execute',
+ dest='command',
+ action='callback',
+ callback=cmd)
+
+ def Execute(self, opt, args):
+ if not opt.command:
+ self.Usage()
+
+ cmd = [opt.command[0]]
+
+ shell = True
+ if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
+ shell = False
+
+ if shell:
+ cmd.append(cmd[0])
+ cmd.extend(opt.command[1:])
+
+ rc = 0
+ for project in self.GetProjects(args):
+ env = dict(os.environ.iteritems())
+ env['REPO_PROJECT'] = project.name
+
+ p = subprocess.Popen(cmd,
+ cwd = project.worktree,
+ shell = shell,
+ env = env)
+ r = p.wait()
+ if r != 0 and r != rc:
+ rc = r
+ if rc != 0:
+ sys.exit(rc)
diff --git a/subcmds/help.py b/subcmds/help.py
new file mode 100644
index 0000000..6e0238a
--- /dev/null
+++ b/subcmds/help.py
@@ -0,0 +1,147 @@
+#
+# 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 sys
+from formatter import AbstractFormatter, DumbWriter
+
+from color import Coloring
+from command import PagedCommand
+
+class Help(PagedCommand):
+ common = False
+ helpSummary = "Display detailed help on a command"
+ helpUsage = """
+%prog [--all|command]
+"""
+ helpDescription = """
+Displays detailed usage information about a command.
+"""
+
+ def _PrintAllCommands(self):
+ print 'usage: repo COMMAND [ARGS]'
+ print """
+The complete list of recognized repo commands are:
+"""
+ commandNames = self.commands.keys()
+ commandNames.sort()
+
+ maxlen = 0
+ for name in commandNames:
+ maxlen = max(maxlen, len(name))
+ fmt = ' %%-%ds %%s' % maxlen
+
+ for name in commandNames:
+ command = self.commands[name]
+ try:
+ summary = command.helpSummary.strip()
+ except AttributeError:
+ summary = ''
+ print fmt % (name, summary)
+ print """
+See 'repo help <command>' for more information on a specific command.
+"""
+
+ def _PrintCommonCommands(self):
+ print 'usage: repo COMMAND [ARGS]'
+ print """
+The most commonly used repo commands are:
+"""
+ commandNames = [name
+ for name in self.commands.keys()
+ if self.commands[name].common]
+ commandNames.sort()
+
+ maxlen = 0
+ for name in commandNames:
+ maxlen = max(maxlen, len(name))
+ fmt = ' %%-%ds %%s' % maxlen
+
+ for name in commandNames:
+ command = self.commands[name]
+ try:
+ summary = command.helpSummary.strip()
+ except AttributeError:
+ summary = ''
+ print fmt % (name, summary)
+ print """
+See 'repo help <command>' for more information on a specific command.
+"""
+
+ def _PrintCommandHelp(self, cmd):
+ class _Out(Coloring):
+ def __init__(self, gc):
+ Coloring.__init__(self, gc, 'help')
+ self.heading = self.printer('heading', attr='bold')
+
+ self.wrap = AbstractFormatter(DumbWriter())
+
+ def _PrintSection(self, heading, bodyAttr):
+ try:
+ body = getattr(cmd, bodyAttr)
+ except AttributeError:
+ return
+
+ self.nl()
+
+ self.heading('%s', heading)
+ self.nl()
+
+ self.heading('%s', ''.ljust(len(heading), '-'))
+ self.nl()
+
+ me = 'repo %s' % cmd.NAME
+ body = body.strip()
+ body = body.replace('%prog', me)
+
+ for para in body.split("\n\n"):
+ if para.startswith(' '):
+ self.write('%s', para)
+ self.nl()
+ self.nl()
+ else:
+ self.wrap.add_flowing_data(para)
+ self.wrap.end_paragraph(1)
+ self.wrap.end_paragraph(0)
+
+ out = _Out(self.manifest.globalConfig)
+ cmd.OptionParser.print_help()
+ out._PrintSection('Summary', 'helpSummary')
+ out._PrintSection('Description', 'helpDescription')
+
+ def _Options(self, p):
+ p.add_option('-a', '--all',
+ dest='show_all', action='store_true',
+ help='show the complete list of commands')
+
+ def Execute(self, opt, args):
+ if len(args) == 0:
+ if opt.show_all:
+ self._PrintAllCommands()
+ else:
+ self._PrintCommonCommands()
+
+ elif len(args) == 1:
+ name = args[0]
+
+ try:
+ cmd = self.commands[name]
+ except KeyError:
+ print >>sys.stderr, "repo: '%s' is not a repo command." % name
+ sys.exit(1)
+
+ self._PrintCommandHelp(cmd)
+
+ else:
+ self._PrintCommandHelp(self)
diff --git a/subcmds/init.py b/subcmds/init.py
new file mode 100644
index 0000000..03f358d
--- /dev/null
+++ b/subcmds/init.py
@@ -0,0 +1,193 @@
+#
+# 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 sys
+
+from color import Coloring
+from command import InteractiveCommand
+from error import ManifestParseError
+from remote import Remote
+from git_command import git, MIN_GIT_VERSION
+
+class Init(InteractiveCommand):
+ common = True
+ helpSummary = "Initialize repo in the current directory"
+ helpUsage = """
+%prog [options]
+"""
+ helpDescription = """
+The '%prog' command is run once to install and initialize repo.
+The latest repo source code and manifest collection is downloaded
+from the server and is installed in the .repo/ directory in the
+current working directory.
+
+The optional <manifest> argument can be used to specify an alternate
+manifest to be used. If no manifest is specified, the manifest
+default.xml will be used.
+"""
+
+ def _Options(self, p):
+ # Logging
+ g = p.add_option_group('Logging options')
+ g.add_option('-q', '--quiet',
+ dest="quiet", action="store_true", default=False,
+ help="be quiet")
+
+ # Manifest
+ g = p.add_option_group('Manifest options')
+ g.add_option('-u', '--manifest-url',
+ dest='manifest_url',
+ help='manifest repository location', metavar='URL')
+ g.add_option('-b', '--manifest-branch',
+ dest='manifest_branch',
+ help='manifest branch or revision', metavar='REVISION')
+ g.add_option('-m', '--manifest-name',
+ dest='manifest_name', default='default.xml',
+ help='initial manifest file', metavar='NAME.xml')
+
+ # Tool
+ g = p.add_option_group('Version options')
+ g.add_option('--repo-url',
+ dest='repo_url',
+ help='repo repository location', metavar='URL')
+ g.add_option('--repo-branch',
+ dest='repo_branch',
+ help='repo branch or revision', metavar='REVISION')
+ g.add_option('--no-repo-verify',
+ dest='no_repo_verify', action='store_true',
+ help='do not verify repo source code')
+
+ def _CheckGitVersion(self):
+ ver_str = git.version()
+ if not ver_str.startswith('git version '):
+ print >>sys.stderr, 'error: "%s" unsupported' % ver_str
+ sys.exit(1)
+
+ ver_str = ver_str[len('git version '):].strip()
+ ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3]))
+ if ver_act < MIN_GIT_VERSION:
+ need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION))
+ print >>sys.stderr, 'fatal: git %s or later required' % need
+ sys.exit(1)
+
+ def _SyncManifest(self, opt):
+ m = self.manifest.manifestProject
+
+ if not m.Exists:
+ if not opt.manifest_url:
+ print >>sys.stderr, 'fatal: manifest url (-u) is required.'
+ sys.exit(1)
+
+ if not opt.quiet:
+ print >>sys.stderr, 'Getting manifest ...'
+ print >>sys.stderr, ' from %s' % opt.manifest_url
+ m._InitGitDir()
+
+ if opt.manifest_branch:
+ m.revision = opt.manifest_branch
+ else:
+ m.revision = 'refs/heads/master'
+ else:
+ if opt.manifest_branch:
+ m.revision = opt.manifest_branch
+ else:
+ m.PreSync()
+
+ if opt.manifest_url:
+ r = m.GetRemote(m.remote.name)
+ r.url = opt.manifest_url
+ r.ResetFetch()
+ r.Save()
+
+ m.Sync_NetworkHalf()
+ m.Sync_LocalHalf()
+ m.StartBranch('default')
+
+ def _LinkManifest(self, name):
+ if not name:
+ print >>sys.stderr, 'fatal: manifest name (-m) is required.'
+ sys.exit(1)
+
+ try:
+ self.manifest.Link(name)
+ except ManifestParseError, e:
+ print >>sys.stderr, "fatal: manifest '%s' not available" % name
+ print >>sys.stderr, 'fatal: %s' % str(e)
+ sys.exit(1)
+
+ def _PromptKey(self, prompt, key, value):
+ mp = self.manifest.manifestProject
+
+ sys.stdout.write('%-10s [%s]: ' % (prompt, value))
+ a = sys.stdin.readline().strip()
+ if a != '' and a != value:
+ mp.config.SetString(key, a)
+
+ def _ConfigureUser(self):
+ mp = self.manifest.manifestProject
+
+ print ''
+ self._PromptKey('Your Name', 'user.name', mp.UserName)
+ self._PromptKey('Your Email', 'user.email', mp.UserEmail)
+
+ def _HasColorSet(self, gc):
+ for n in ['ui', 'diff', 'status']:
+ if gc.Has('color.%s' % n):
+ return True
+ return False
+
+ def _ConfigureColor(self):
+ gc = self.manifest.globalConfig
+ if self._HasColorSet(gc):
+ return
+
+ class _Test(Coloring):
+ def __init__(self):
+ Coloring.__init__(self, gc, 'test color display')
+ self._on = True
+ out = _Test()
+
+ print ''
+ print "Testing colorized output (for 'repo diff', 'repo status'):"
+
+ for c in ['black','red','green','yellow','blue','magenta','cyan']:
+ out.write(' ')
+ out.printer(fg=c)(' %-6s ', c)
+ out.write(' ')
+ out.printer(fg='white', bg='black')(' %s ' % 'white')
+ out.nl()
+
+ for c in ['bold','dim','ul','reverse']:
+ out.write(' ')
+ out.printer(fg='black', attr=c)(' %-6s ', c)
+ out.nl()
+
+ sys.stdout.write('Enable color display in this user account (y/n)? ')
+ a = sys.stdin.readline().strip().lower()
+ if a in ('y', 'yes', 't', 'true', 'on'):
+ gc.SetString('color.ui', 'auto')
+
+ def Execute(self, opt, args):
+ self._CheckGitVersion()
+ self._SyncManifest(opt)
+ self._LinkManifest(opt.manifest_name)
+
+ if os.isatty(0) and os.isatty(1):
+ self._ConfigureUser()
+ self._ConfigureColor()
+
+ print ''
+ print 'repo initialized in %s' % self.manifest.topdir
diff --git a/subcmds/prune.py b/subcmds/prune.py
new file mode 100644
index 0000000..f412bd4
--- /dev/null
+++ b/subcmds/prune.py
@@ -0,0 +1,59 @@
+#
+# 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.
+
+from color import Coloring
+from command import PagedCommand
+
+class Prune(PagedCommand):
+ common = True
+ helpSummary = "Prune (delete) already merged topics"
+ helpUsage = """
+%prog [<project>...]
+"""
+
+ def Execute(self, opt, args):
+ all = []
+ for project in self.GetProjects(args):
+ all.extend(project.PruneHeads())
+
+ if not all:
+ return
+
+ class Report(Coloring):
+ def __init__(self, config):
+ Coloring.__init__(self, config, 'status')
+ self.project = self.printer('header', attr='bold')
+
+ out = Report(all[0].project.config)
+ out.project('Pending Branches')
+ out.nl()
+
+ project = None
+
+ for branch in all:
+ if project != branch.project:
+ project = branch.project
+ out.nl()
+ out.project('project %s/' % project.relpath)
+ out.nl()
+
+ commits = branch.commits
+ date = branch.date
+ print '%s %-33s (%2d commit%s, %s)' % (
+ branch.name == project.CurrentBranch and '*' or ' ',
+ branch.name,
+ len(commits),
+ len(commits) != 1 and 's' or ' ',
+ date)
diff --git a/subcmds/stage.py b/subcmds/stage.py
new file mode 100644
index 0000000..c451cd6
--- /dev/null
+++ b/subcmds/stage.py
@@ -0,0 +1,108 @@
+#
+# 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 sys
+
+from color import Coloring
+from command import InteractiveCommand
+from git_command import GitCommand
+
+class _ProjectList(Coloring):
+ def __init__(self, gc):
+ Coloring.__init__(self, gc, 'interactive')
+ self.prompt = self.printer('prompt', fg='blue', attr='bold')
+ self.header = self.printer('header', attr='bold')
+ self.help = self.printer('help', fg='red', attr='bold')
+
+class Stage(InteractiveCommand):
+ common = True
+ helpSummary = "Stage file(s) for commit"
+ helpUsage = """
+%prog -i [<project>...]
+"""
+ helpDescription = """
+The '%prog' command stages files to prepare the next commit.
+"""
+
+ def _Options(self, p):
+ p.add_option('-i', '--interactive',
+ dest='interactive', action='store_true',
+ help='use interactive staging')
+
+ def Execute(self, opt, args):
+ if opt.interactive:
+ self._Interactive(opt, args)
+ else:
+ self.Usage()
+
+ def _Interactive(self, opt, args):
+ all = filter(lambda x: x.IsDirty(), self.GetProjects(args))
+ if not all:
+ print >>sys.stderr,'no projects have uncommitted modifications'
+ return
+
+ out = _ProjectList(self.manifest.manifestProject.config)
+ while True:
+ out.header(' %-20s %s', 'project', 'path')
+ out.nl()
+
+ for i in xrange(0, len(all)):
+ p = all[i]
+ out.write('%3d: %-20s %s', i + 1, p.name, p.relpath + '/')
+ out.nl()
+ out.nl()
+
+ out.write('%3d: (', 0)
+ out.prompt('q')
+ out.write('uit)')
+ out.nl()
+
+ out.prompt('project> ')
+ try:
+ a = sys.stdin.readline()
+ except KeyboardInterrupt:
+ out.nl()
+ break
+ if a == '':
+ out.nl()
+ break
+
+ a = a.strip()
+ if a.lower() in ('q', 'quit', 'exit'):
+ break
+ if not a:
+ continue
+
+ try:
+ a_index = int(a)
+ except ValueError:
+ a_index = None
+
+ if a_index is not None:
+ if a_index == 0:
+ break
+ if 0 < a_index and a_index <= len(all):
+ _AddI(all[a_index - 1])
+ continue
+
+ p = filter(lambda x: x.name == a or x.relpath == a, all)
+ if len(p) == 1:
+ _AddI(p[0])
+ continue
+ print 'Bye.'
+
+def _AddI(project):
+ p = GitCommand(project, ['add', '--interactive'], bare=False)
+ p.Wait()
diff --git a/subcmds/start.py b/subcmds/start.py
new file mode 100644
index 0000000..4eb3e47
--- /dev/null
+++ b/subcmds/start.py
@@ -0,0 +1,51 @@
+#
+# 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 sys
+from command import Command
+from git_command import git
+
+class Start(Command):
+ common = True
+ helpSummary = "Start a new branch for development"
+ helpUsage = """
+%prog <newbranchname> [<project>...]
+
+This subcommand starts a new branch of development that is automatically
+pulled from a remote branch.
+
+It is equivalent to the following git commands:
+
+"git branch --track <newbranchname> m/<codeline>",
+or
+"git checkout --track -b <newbranchname> m/<codeline>".
+
+All three forms set up the config entries that repo bases some of its
+processing on. Use %prog or git branch or checkout with --track to ensure
+the configuration data is set up properly.
+
+"""
+
+ def Execute(self, opt, args):
+ if not args:
+ self.Usage()
+
+ nb = args[0]
+ if not git.check_ref_format('heads/%s' % nb):
+ print >>sys.stderr, "error: '%s' is not a valid name" % nb
+ sys.exit(1)
+
+ for project in self.GetProjects(args[1:]):
+ project.StartBranch(nb)
diff --git a/subcmds/status.py b/subcmds/status.py
new file mode 100644
index 0000000..1615b42
--- /dev/null
+++ b/subcmds/status.py
@@ -0,0 +1,27 @@
+#
+# 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.
+
+from command import PagedCommand
+
+class Status(PagedCommand):
+ common = True
+ helpSummary = "Show the working tree status"
+ helpUsage = """
+%prog [<project>...]
+"""
+
+ def Execute(self, opt, args):
+ for project in self.GetProjects(args):
+ project.PrintWorkTreeStatus()
diff --git a/subcmds/sync.py b/subcmds/sync.py
new file mode 100644
index 0000000..3eb44ed
--- /dev/null
+++ b/subcmds/sync.py
@@ -0,0 +1,150 @@
+#
+# 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 subprocess
+import sys
+
+from git_command import GIT
+from command import Command
+from error import RepoChangedException, GitError
+from project import R_HEADS
+
+class Sync(Command):
+ common = True
+ helpSummary = "Update working tree to the latest revision"
+ helpUsage = """
+%prog [<project>...]
+"""
+ helpDescription = """
+The '%prog' command synchronizes local project directories
+with the remote repositories specified in the manifest. If a local
+project does not yet exist, it will clone a new local directory from
+the remote repository and set up tracking branches as specified in
+the manifest. If the local project already exists, '%prog'
+will update the remote branches and rebase any new local changes
+on top of the new remote changes.
+
+'%prog' will synchronize all projects listed at the command
+line. Projects can be specified either by name, or by a relative
+or absolute path to the project's local directory. If no projects
+are specified, '%prog' will synchronize all projects listed in
+the manifest.
+"""
+
+ def _Options(self, p):
+ p.add_option('--no-repo-verify',
+ dest='no_repo_verify', action='store_true',
+ help='do not verify repo source code')
+
+ def _Fetch(self, *projects):
+ fetched = set()
+ for project in projects:
+ if project.Sync_NetworkHalf():
+ fetched.add(project.gitdir)
+ else:
+ print >>sys.stderr, 'error: Cannot fetch %s' % project.name
+ sys.exit(1)
+ return fetched
+
+ def Execute(self, opt, args):
+ rp = self.manifest.repoProject
+ rp.PreSync()
+
+ mp = self.manifest.manifestProject
+ mp.PreSync()
+
+ all = self.GetProjects(args, missing_ok=True)
+ fetched = self._Fetch(rp, mp, *all)
+
+ if rp.HasChanges:
+ print >>sys.stderr, 'info: A new version of repo is available'
+ print >>sys.stderr, ''
+ if opt.no_repo_verify or _VerifyTag(rp):
+ if not rp.Sync_LocalHalf():
+ sys.exit(1)
+ print >>sys.stderr, 'info: Restarting repo with latest version'
+ raise RepoChangedException()
+ else:
+ print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
+
+ if mp.HasChanges:
+ if not mp.Sync_LocalHalf():
+ sys.exit(1)
+
+ self.manifest._Unload()
+ all = self.GetProjects(args, missing_ok=True)
+ missing = []
+ for project in all:
+ if project.gitdir not in fetched:
+ missing.append(project)
+ self._Fetch(*missing)
+
+ for project in all:
+ if not project.Sync_LocalHalf():
+ sys.exit(1)
+
+
+def _VerifyTag(project):
+ gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
+ if not os.path.exists(gpg_dir):
+ print >>sys.stderr,\
+"""warning: GnuPG was not available during last "repo init"
+warning: Cannot automatically authenticate repo."""
+ return True
+
+ remote = project.GetRemote(project.remote.name)
+ ref = remote.ToLocal(project.revision)
+
+ try:
+ cur = project.bare_git.describe(ref)
+ except GitError:
+ cur = None
+
+ if not cur \
+ or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
+ rev = project.revision
+ if rev.startswith(R_HEADS):
+ rev = rev[len(R_HEADS):]
+
+ print >>sys.stderr
+ print >>sys.stderr,\
+ "warning: project '%s' branch '%s' is not signed" \
+ % (project.name, rev)
+ return False
+
+ env = dict(os.environ)
+ env['GIT_DIR'] = project.gitdir
+ env['GNUPGHOME'] = gpg_dir
+
+ cmd = [GIT, 'tag', '-v', cur]
+ proc = subprocess.Popen(cmd,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ env = env)
+ out = proc.stdout.read()
+ proc.stdout.close()
+
+ err = proc.stderr.read()
+ proc.stderr.close()
+
+ if proc.wait() != 0:
+ print >>sys.stderr
+ print >>sys.stderr, out
+ print >>sys.stderr, err
+ print >>sys.stderr
+ return False
+ return True
diff --git a/subcmds/upload.py b/subcmds/upload.py
new file mode 100644
index 0000000..ad05050
--- /dev/null
+++ b/subcmds/upload.py
@@ -0,0 +1,180 @@
+#
+# 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 InteractiveCommand
+from editor import Editor
+from error import UploadError
+
+def _die(fmt, *args):
+ msg = fmt % args
+ print >>sys.stderr, 'error: %s' % msg
+ sys.exit(1)
+
+class Upload(InteractiveCommand):
+ common = True
+ helpSummary = "Upload changes for code review"
+ helpUsage="""
+%prog [<project>]...
+"""
+ helpDescription = """
+The '%prog' command is used to send changes to the Gerrit code
+review system. It searches for changes in local projects that do
+not yet exist in the corresponding remote repository. If multiple
+changes are found, '%prog' opens an editor to allow the
+user to choose which change to upload. After a successful upload,
+repo prints the URL for the change in the Gerrit code review system.
+
+'%prog' searches for uploadable changes in all projects listed
+at the command line. Projects can be specified either by name, or
+by a relative or absolute path to the project's local directory. If
+no projects are specified, '%prog' will search for uploadable
+changes in all projects listed in the manifest.
+"""
+
+ def _SingleBranch(self, branch):
+ project = branch.project
+ name = branch.name
+ date = branch.date
+ list = branch.commits
+
+ print 'Upload project %s/:' % project.relpath
+ print ' branch %s (%2d commit%s, %s):' % (
+ name,
+ len(list),
+ len(list) != 1 and 's' or '',
+ date)
+ for commit in list:
+ print ' %s' % commit
+
+ sys.stdout.write('(y/n)? ')
+ answer = sys.stdin.readline().strip()
+ if answer in ('y', 'Y', 'yes', '1', 'true', 't'):
+ self._UploadAndReport([branch])
+ else:
+ _die("upload aborted by user")
+
+ def _MultipleBranches(self, pending):
+ projects = {}
+ branches = {}
+
+ script = []
+ script.append('# Uncomment the branches to upload:')
+ for project, avail in pending:
+ script.append('#')
+ script.append('# project %s/:' % project.relpath)
+
+ b = {}
+ for branch in avail:
+ name = branch.name
+ date = branch.date
+ list = branch.commits
+
+ if b:
+ script.append('#')
+ script.append('# branch %s (%2d commit%s, %s):' % (
+ name,
+ len(list),
+ len(list) != 1 and 's' or '',
+ date))
+ for commit in list:
+ script.append('# %s' % commit)
+ b[name] = branch
+
+ projects[project.relpath] = project
+ branches[project.name] = b
+ script.append('')
+
+ script = Editor.EditString("\n".join(script)).split("\n")
+
+ project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
+ branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')
+
+ project = None
+ todo = []
+
+ for line in script:
+ m = project_re.match(line)
+ if m:
+ name = m.group(1)
+ project = projects.get(name)
+ if not project:
+ _die('project %s not available for upload', name)
+ continue
+
+ m = branch_re.match(line)
+ if m:
+ name = m.group(1)
+ if not project:
+ _die('project for branch %s not in script', name)
+ branch = branches[project.name].get(name)
+ if not branch:
+ _die('branch %s not in %s', name, project.relpath)
+ todo.append(branch)
+ if not todo:
+ _die("nothing uncommented for upload")
+ self._UploadAndReport(todo)
+
+ def _UploadAndReport(self, todo):
+ have_errors = False
+ for branch in todo:
+ try:
+ branch.UploadForReview()
+ branch.uploaded = True
+ except UploadError, e:
+ branch.error = e
+ branch.uploaded = False
+ have_errors = True
+
+ print >>sys.stderr, ''
+ print >>sys.stderr, '--------------------------------------------'
+
+ if have_errors:
+ for branch in todo:
+ if not branch.uploaded:
+ print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
+ branch.project.relpath + '/', \
+ branch.name, \
+ branch.error)
+ print >>sys.stderr, ''
+
+ for branch in todo:
+ if branch.uploaded:
+ print >>sys.stderr, '[OK ] %-15s %s' % (
+ branch.project.relpath + '/',
+ branch.name)
+ print >>sys.stderr, '%s' % branch.tip_url
+ print >>sys.stderr, ''
+
+ if have_errors:
+ sys.exit(1)
+
+ def Execute(self, opt, args):
+ project_list = self.GetProjects(args)
+ pending = []
+
+ for project in project_list:
+ avail = project.GetUploadableBranches()
+ if avail:
+ pending.append((project, avail))
+
+ if not pending:
+ print >>sys.stdout, "no branches ready for upload"
+ elif len(pending) == 1 and len(pending[0][1]) == 1:
+ self._SingleBranch(pending[0][1][0])
+ else:
+ self._MultipleBranches(pending)