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)