Merge "TBR: Revert "TBR: Revert "jiri: The jiri script must put $JIRI_ROOT/.jiri_root/bin in PATH before invoking the jiri binary."""
diff --git a/bootstrap_jiri_test.go b/bootstrap_jiri_test.go
deleted file mode 100644
index 3bdca29..0000000
--- a/bootstrap_jiri_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-	"testing"
-
-	"v.io/x/lib/gosh"
-)
-
-func TestBootstrapJiri(t *testing.T) {
-	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf, PropagateChildOutput: true})
-	defer sh.Cleanup()
-
-	bootstrap, err := filepath.Abs("./scripts/bootstrap_jiri")
-	if err != nil {
-		t.Fatalf("couldn't determine absolute path to bootstrap_jiri script")
-	}
-	rootDir := filepath.Join(sh.MakeTempDir(), "root")
-	stdout, stderr := sh.Cmd(bootstrap, rootDir).StdoutStderr()
-	if got, want := stdout, fmt.Sprintf("Please add %s to your PATH.\n", filepath.Join(rootDir, ".jiri_root", "scripts")); got != want {
-		t.Errorf("stdout got %q, want %q", got, want)
-	}
-	if got, want := stderr, ""; got != want {
-		t.Errorf("stderr got %q, want %q", got, want)
-	}
-	if _, err := os.Stat(filepath.Join(rootDir, ".jiri_root", "bin", "jiri")); err != nil {
-		t.Error(err)
-	}
-	if _, err := os.Stat(filepath.Join(rootDir, ".jiri_root", "scripts", "jiri")); err != nil {
-		t.Error(err)
-	}
-}
-
-func TestBootstrapJiriAlreadyExists(t *testing.T) {
-	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf, PropagateChildOutput: true})
-	defer sh.Cleanup()
-
-	bootstrap, err := filepath.Abs("./scripts/bootstrap_jiri")
-	if err != nil {
-		t.Fatalf("couldn't determine absolute path to bootstrap_jiri script")
-	}
-	rootDir := sh.MakeTempDir()
-	c := sh.Cmd(bootstrap, rootDir)
-	c.ExitErrorIsOk = true
-	stdout, stderr := c.StdoutStderr()
-	if c.Err == nil {
-		t.Errorf("error got %q, want nil", c.Err)
-	}
-	if got, want := stdout, ""; got != want {
-		t.Errorf("stdout got %q, want %q", got, want)
-	}
-	if got, want := stderr, rootDir+" already exists"; !strings.Contains(got, want) {
-		t.Errorf("stderr got %q, want substr %q", got, want)
-	}
-}
diff --git a/cl.go b/cl.go
deleted file mode 100644
index b5a8721..0000000
--- a/cl.go
+++ /dev/null
@@ -1,1120 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"net/url"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	"v.io/jiri/collect"
-	"v.io/jiri/gerrit"
-	"v.io/jiri/gitutil"
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/runutil"
-	"v.io/x/lib/cmdline"
-)
-
-const (
-	commitMessageFileName  = ".gerrit_commit_message"
-	dependencyPathFileName = ".dependency_path"
-)
-
-var (
-	autosubmitFlag   bool
-	ccsFlag          string
-	draftFlag        bool
-	editFlag         bool
-	forceFlag        bool
-	hostFlag         string
-	messageFlag      string
-	presubmitFlag    string
-	remoteBranchFlag string
-	reviewersFlag    string
-	setTopicFlag     bool
-	topicFlag        string
-	uncommittedFlag  bool
-	verifyFlag       bool
-)
-
-// Special labels stored in the commit message.
-var (
-	// Auto submit label.
-	autosubmitLabelRE *regexp.Regexp = regexp.MustCompile("AutoSubmit")
-
-	// Change-Ids start with 'I' and are followed by 40 characters of hex.
-	changeIDRE *regexp.Regexp = regexp.MustCompile("Change-Id: (I[0123456789abcdefABCDEF]{40})")
-
-	// Presubmit test label.
-	// PresubmitTest: <type>
-	presubmitTestLabelRE *regexp.Regexp = regexp.MustCompile(`PresubmitTest:\s*(.*)`)
-)
-
-// init carries out the package initialization.
-func init() {
-	cmdCLCleanup.Flags.BoolVar(&forceFlag, "f", false, `Ignore unmerged changes.`)
-	cmdCLCleanup.Flags.StringVar(&remoteBranchFlag, "remote-branch", "master", `Name of the remote branch the CL pertains to, without the leading "origin/".`)
-	cmdCLMail.Flags.BoolVar(&autosubmitFlag, "autosubmit", false, `Automatically submit the changelist when feasible.`)
-	cmdCLMail.Flags.StringVar(&ccsFlag, "cc", "", `Comma-seperated list of emails or LDAPs to cc.`)
-	cmdCLMail.Flags.BoolVar(&draftFlag, "d", false, `Send a draft changelist.`)
-	cmdCLMail.Flags.BoolVar(&editFlag, "edit", true, `Open an editor to edit the CL description.`)
-	cmdCLMail.Flags.StringVar(&hostFlag, "host", "", `Gerrit host to use.  Defaults to gerrit host specified in manifest.`)
-	cmdCLMail.Flags.StringVar(&messageFlag, "m", "", `CL description.`)
-	cmdCLMail.Flags.StringVar(&presubmitFlag, "presubmit", string(gerrit.PresubmitTestTypeAll),
-		fmt.Sprintf("The type of presubmit tests to run. Valid values: %s.", strings.Join(gerrit.PresubmitTestTypes(), ",")))
-	cmdCLMail.Flags.StringVar(&remoteBranchFlag, "remote-branch", "master", `Name of the remote branch the CL pertains to, without the leading "origin/".`)
-	cmdCLMail.Flags.StringVar(&reviewersFlag, "r", "", `Comma-seperated list of emails or LDAPs to request review.`)
-	cmdCLMail.Flags.BoolVar(&setTopicFlag, "set-topic", true, `Set Gerrit CL topic.`)
-	cmdCLMail.Flags.StringVar(&topicFlag, "topic", "", `CL topic, defaults to <username>-<branchname>.`)
-	cmdCLMail.Flags.BoolVar(&uncommittedFlag, "check-uncommitted", true, `Check that no uncommitted changes exist.`)
-	cmdCLMail.Flags.BoolVar(&verifyFlag, "verify", true, `Run pre-push git hooks.`)
-	cmdCLSync.Flags.StringVar(&remoteBranchFlag, "remote-branch", "master", `Name of the remote branch the CL pertains to, without the leading "origin/".`)
-}
-
-func getCommitMessageFileName(jirix *jiri.X, branch string) (string, error) {
-	topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(topLevel, jiri.ProjectMetaDir, branch, commitMessageFileName), nil
-}
-
-func getDependencyPathFileName(jirix *jiri.X, branch string) (string, error) {
-	topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(topLevel, jiri.ProjectMetaDir, branch, dependencyPathFileName), nil
-}
-
-func getDependentCLs(jirix *jiri.X, branch string) ([]string, error) {
-	file, err := getDependencyPathFileName(jirix, branch)
-	if err != nil {
-		return nil, err
-	}
-	data, err := jirix.NewSeq().ReadFile(file)
-	var branches []string
-	if err != nil {
-		if !runutil.IsNotExist(err) {
-			return nil, err
-		}
-		if branch != remoteBranchFlag {
-			branches = []string{remoteBranchFlag}
-		}
-	} else {
-		branches = strings.Split(strings.TrimSpace(string(data)), "\n")
-	}
-	return branches, nil
-}
-
-// cmdCL represents the "jiri cl" command.
-var cmdCL = &cmdline.Command{
-	Name:     "cl",
-	Short:    "Manage project changelists",
-	Long:     "Manage project changelists.",
-	Children: []*cmdline.Command{cmdCLCleanup, cmdCLMail, cmdCLNew, cmdCLSync},
-}
-
-// cmdCLCleanup represents the "jiri cl cleanup" command.
-//
-// TODO(jsimsa): Replace this with a "submit" command that talks to
-// Gerrit to submit the CL and then (optionally) removes it locally.
-var cmdCLCleanup = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runCLCleanup),
-	Name:   "cleanup",
-	Short:  "Clean up changelists that have been merged",
-	Long: `
-Command "cleanup" checks that the given branches have been merged into
-the corresponding remote branch. If a branch differs from the
-corresponding remote branch, the command reports the difference and
-stops. Otherwise, it deletes the given branches.
-`,
-	ArgsName: "<branches>",
-	ArgsLong: "<branches> is a list of branches to cleanup.",
-}
-
-func cleanupCL(jirix *jiri.X, branches []string) (e error) {
-	originalBranch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return err
-	}
-	stashed, err := gitutil.New(jirix.NewSeq()).Stash()
-	if err != nil {
-		return err
-	}
-	if stashed {
-		defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).StashPop() }, &e)
-	}
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(remoteBranchFlag); err != nil {
-		return err
-	}
-	checkoutOriginalBranch := true
-	defer collect.Error(func() error {
-		if checkoutOriginalBranch {
-			return gitutil.New(jirix.NewSeq()).CheckoutBranch(originalBranch)
-		}
-		return nil
-	}, &e)
-	if err := gitutil.New(jirix.NewSeq()).FetchRefspec("origin", remoteBranchFlag); err != nil {
-		return err
-	}
-	s := jirix.NewSeq()
-	for _, branch := range branches {
-		cleanupFn := func() error { return cleanupBranch(jirix, branch) }
-		if err := s.Call(cleanupFn, "Cleaning up branch: %s", branch).Done(); err != nil {
-			return err
-		}
-		if branch == originalBranch {
-			checkoutOriginalBranch = false
-		}
-	}
-	return nil
-}
-
-func cleanupBranch(jirix *jiri.X, branch string) error {
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(branch); err != nil {
-		return err
-	}
-	if !forceFlag {
-		trackingBranch := "origin/" + remoteBranchFlag
-		if err := gitutil.New(jirix.NewSeq()).Merge(trackingBranch); err != nil {
-			return err
-		}
-		files, err := gitutil.New(jirix.NewSeq()).ModifiedFiles(trackingBranch, branch)
-		if err != nil {
-			return err
-		}
-		if len(files) != 0 {
-			return fmt.Errorf("unmerged changes in\n%s", strings.Join(files, "\n"))
-		}
-	}
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(remoteBranchFlag); err != nil {
-		return err
-	}
-	if err := gitutil.New(jirix.NewSeq()).DeleteBranch(branch, gitutil.ForceOpt(true)); err != nil {
-		return err
-	}
-	reviewBranch := branch + "-REVIEW"
-	if gitutil.New(jirix.NewSeq()).BranchExists(reviewBranch) {
-		if err := gitutil.New(jirix.NewSeq()).DeleteBranch(reviewBranch, gitutil.ForceOpt(true)); err != nil {
-			return err
-		}
-	}
-	// Delete branch metadata.
-	topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return err
-	}
-	s := jirix.NewSeq()
-	// Remove the branch from all dependency paths.
-	metadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir)
-	fileInfos, err := s.RemoveAll(filepath.Join(metadataDir, branch)).
-		ReadDir(metadataDir)
-	if err != nil {
-		return err
-	}
-	for _, fileInfo := range fileInfos {
-		if !fileInfo.IsDir() {
-			continue
-		}
-		file, err := getDependencyPathFileName(jirix, fileInfo.Name())
-		if err != nil {
-			return err
-		}
-		data, err := s.ReadFile(file)
-		if err != nil {
-			if !runutil.IsNotExist(err) {
-				return err
-			}
-			continue
-		}
-		branches := strings.Split(string(data), "\n")
-		for i, tmpBranch := range branches {
-			if branch == tmpBranch {
-				data := []byte(strings.Join(append(branches[:i], branches[i+1:]...), "\n"))
-				if err := s.WriteFile(file, data, os.FileMode(0644)).Done(); err != nil {
-					return err
-				}
-				break
-			}
-		}
-	}
-	return nil
-}
-
-func runCLCleanup(jirix *jiri.X, args []string) error {
-	if len(args) == 0 {
-		return jirix.UsageErrorf("cleanup requires at least one argument")
-	}
-	return cleanupCL(jirix, args)
-}
-
-// cmdCLMail represents the "jiri cl mail" command.
-var cmdCLMail = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runCLMail),
-	Name:   "mail",
-	Short:  "Mail a changelist for review",
-	Long: `
-Command "mail" squashes all commits of a local branch into a single
-"changelist" and mails this changelist to Gerrit as a single
-commit. First time the command is invoked, it generates a Change-Id
-for the changelist, which is appended to the commit
-message. Consecutive invocations of the command use the same Change-Id
-by default, informing Gerrit that the incomming commit is an update of
-an existing changelist.
-`,
-}
-
-type changeConflictError struct {
-	localBranch  string
-	message      string
-	remoteBranch string
-}
-
-func (e changeConflictError) Error() string {
-	result := "changelist conflicts with the remote " + e.remoteBranch + " branch\n\n"
-	result += "To resolve this problem, run 'git pull origin " + e.remoteBranch + ":" + e.localBranch + "',\n"
-	result += "resolve the conflicts identified below, and then try again.\n"
-	result += e.message
-	return result
-}
-
-type emptyChangeError struct{}
-
-func (_ emptyChangeError) Error() string {
-	return "current branch has no commits"
-}
-
-type gerritError string
-
-func (e gerritError) Error() string {
-	result := "sending code review failed\n\n"
-	result += string(e)
-	return result
-}
-
-type noChangeIDError struct{}
-
-func (_ noChangeIDError) Error() string {
-	result := "changelist is missing a Change-ID"
-	return result
-}
-
-type uncommittedChangesError []string
-
-func (e uncommittedChangesError) Error() string {
-	result := "uncommitted local changes in files:\n"
-	result += "  " + strings.Join(e, "\n  ")
-	return result
-}
-
-var defaultMessageHeader = `
-# Describe your changelist, specifying what package(s) your change
-# pertains to, followed by a short summary and, in case of non-trivial
-# changelists, provide a detailed description.
-#
-# For example:
-#
-# rpc/stream/proxy: add publish address
-#
-# The listen address is not always the same as the address that external
-# users need to connect to. This CL adds a new argument to proxy.New()
-# to specify the published address that clients should connect to.
-
-# FYI, you are about to submit the following local commits for review:
-#
-`
-
-// currentProject returns the Project containing the current working directory.
-// The current working directory must be inside JIRI_ROOT.
-func currentProject(jirix *jiri.X) (project.Project, error) {
-	dir, err := os.Getwd()
-	if err != nil {
-		return project.Project{}, fmt.Errorf("os.Getwd() failed: %v", err)
-	}
-
-	// Error if current working dir is not inside jirix.Root.
-	if !strings.HasPrefix(dir, jirix.Root) {
-		return project.Project{}, fmt.Errorf("'jiri cl mail' must be run from within a project in JIRI_ROOT")
-	}
-
-	// Walk up the path until we find a project at that path, or hit the jirix.Root.
-	for dir != jirix.Root {
-		p, err := project.ProjectAtPath(jirix, dir)
-		if err != nil {
-			dir = filepath.Dir(dir)
-			continue
-		}
-		return p, nil
-	}
-	return project.Project{}, fmt.Errorf("directory %q is not contained in a project", dir)
-}
-
-// runCLMail is a wrapper that sets up and runs a review instance.
-func runCLMail(jirix *jiri.X, _ []string) error {
-	// Sanity checks for the <presubmitFlag> flag.
-	if !checkPresubmitFlag() {
-		return jirix.UsageErrorf("invalid value for the -presubmit flag. Valid values: %s.",
-			strings.Join(gerrit.PresubmitTestTypes(), ","))
-	}
-
-	p, err := currentProject(jirix)
-	if err != nil {
-		return err
-	}
-
-	host := hostFlag
-	if host == "" {
-		if p.GerritHost == "" {
-			return fmt.Errorf("No gerrit host found.  Please use the '--host' flag, or add a 'gerrithost' attribute for project %q.", p.Name)
-		}
-		host = p.GerritHost
-	}
-	hostUrl, err := url.Parse(host)
-	if err != nil {
-		return fmt.Errorf("invalid Gerrit host %q: %v", host, err)
-	}
-	projectRemoteUrl, err := url.Parse(p.Remote)
-	if err != nil {
-		return fmt.Errorf("invalid project remote: %v", p.Remote, err)
-	}
-	gerritRemote := *hostUrl
-	gerritRemote.Path = projectRemoteUrl.Path
-
-	// Create and run the review.
-	review, err := newReview(jirix, gerrit.CLOpts{
-		Autosubmit:   autosubmitFlag,
-		Ccs:          parseEmails(ccsFlag),
-		Draft:        draftFlag,
-		Edit:         editFlag,
-		Remote:       gerritRemote.String(),
-		Host:         hostUrl,
-		Presubmit:    gerrit.PresubmitTestType(presubmitFlag),
-		RemoteBranch: remoteBranchFlag,
-		Reviewers:    parseEmails(reviewersFlag),
-		Verify:       verifyFlag,
-	})
-	if err != nil {
-		return err
-	}
-	if confirmed, err := review.confirmFlagChanges(); err != nil {
-		return err
-	} else if !confirmed {
-		return nil
-	}
-	return review.run()
-}
-
-// parseEmails input a list of comma separated tokens and outputs a
-// list of email addresses. The tokens can either be email addresses
-// or Google LDAPs in which case the suffix @google.com is appended to
-// them to turn them into email addresses.
-func parseEmails(value string) []string {
-	var emails []string
-	tokens := strings.Split(value, ",")
-	for _, token := range tokens {
-		if token == "" {
-			continue
-		}
-		if !strings.Contains(token, "@") {
-			token += "@google.com"
-		}
-		emails = append(emails, token)
-	}
-	return emails
-}
-
-// checkDependents makes sure that all CLs in the sequence of
-// dependent CLs leading to (but not including) the current branch
-// have been exported to Gerrit.
-func checkDependents(jirix *jiri.X) (e error) {
-	originalBranch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return err
-	}
-	branches, err := getDependentCLs(jirix, originalBranch)
-	if err != nil {
-		return err
-	}
-	for i := 1; i < len(branches); i++ {
-		file, err := getCommitMessageFileName(jirix, branches[i])
-		if err != nil {
-			return err
-		}
-		if _, err := jirix.NewSeq().Stat(file); err != nil {
-			if !runutil.IsNotExist(err) {
-				return err
-			}
-			return fmt.Errorf(`Failed to export the branch %q to Gerrit because its ancestor %q has not been exported to Gerrit yet.
-The following steps are needed before the operation can be retried:
-$ git checkout %v
-$ jiri cl mail
-$ git checkout %v
-# retry the original command
-`, originalBranch, branches[i], branches[i], originalBranch)
-		}
-	}
-
-	return nil
-}
-
-type review struct {
-	jirix        *jiri.X
-	reviewBranch string
-	gerrit.CLOpts
-}
-
-func newReview(jirix *jiri.X, opts gerrit.CLOpts) (*review, error) {
-	// Sync all CLs in the sequence of dependent CLs ending in the
-	// current branch.
-	if err := syncCL(jirix); err != nil {
-		return nil, err
-	}
-
-	// Make sure that all CLs in the above sequence (possibly except for
-	// the current branch) have been exported to Gerrit. This is needed
-	// to make sure we have commit messages for all but the last CL.
-	//
-	// NOTE: The alternative here is to prompt the user for multiple
-	// commit messages, which seems less user friendly.
-	if err := checkDependents(jirix); err != nil {
-		return nil, err
-	}
-
-	branch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return nil, err
-	}
-	opts.Branch = branch
-	if opts.Topic == "" {
-		opts.Topic = fmt.Sprintf("%s-%s", os.Getenv("USER"), branch) // use <username>-<branchname> as the default
-	}
-	if opts.Presubmit == gerrit.PresubmitTestType("") {
-		opts.Presubmit = gerrit.PresubmitTestTypeAll // use gerrit.PresubmitTestTypeAll as the default
-	}
-	if opts.RemoteBranch == "" {
-		opts.RemoteBranch = "master" // use master as the default
-	}
-	return &review{
-		jirix:        jirix,
-		reviewBranch: branch + "-REVIEW",
-		CLOpts:       opts,
-	}, nil
-}
-
-func checkPresubmitFlag() bool {
-	for _, t := range gerrit.PresubmitTestTypes() {
-		if presubmitFlag == t {
-			return true
-		}
-	}
-	return false
-}
-
-// confirmFlagChanges asks users to confirm if any of the
-// presubmit and autosubmit flags changes.
-func (review *review) confirmFlagChanges() (bool, error) {
-	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
-	if err != nil {
-		return false, err
-	}
-	bytes, err := review.jirix.NewSeq().ReadFile(file)
-	if err != nil {
-		if runutil.IsNotExist(err) {
-			return true, nil
-		}
-		return false, err
-	}
-	content := string(bytes)
-	changes := []string{}
-
-	// Check presubmit label change.
-	prevPresubmitType := string(gerrit.PresubmitTestTypeAll)
-	matches := presubmitTestLabelRE.FindStringSubmatch(content)
-	if matches != nil {
-		prevPresubmitType = matches[1]
-	}
-	if presubmitFlag != prevPresubmitType {
-		changes = append(changes, fmt.Sprintf("- presubmit=%s to presubmit=%s", prevPresubmitType, presubmitFlag))
-	}
-
-	// Check autosubmit label change.
-	prevAutosubmit := autosubmitLabelRE.MatchString(content)
-	if autosubmitFlag != prevAutosubmit {
-		changes = append(changes, fmt.Sprintf("- autosubmit=%v to autosubmit=%v", prevAutosubmit, autosubmitFlag))
-
-	}
-
-	if len(changes) > 0 {
-		fmt.Printf("Changes:\n%s\n", strings.Join(changes, "\n"))
-		fmt.Print("Are you sure you want to make the above changes? y/N:")
-		var response string
-		if _, err := fmt.Scanf("%s\n", &response); err != nil || response != "y" {
-			return false, nil
-		}
-	}
-	return true, nil
-}
-
-// cleanup cleans up after the review.
-func (review *review) cleanup(stashed bool) error {
-	if err := gitutil.New(review.jirix.NewSeq()).CheckoutBranch(review.CLOpts.Branch); err != nil {
-		return err
-	}
-	if gitutil.New(review.jirix.NewSeq()).BranchExists(review.reviewBranch) {
-		if err := gitutil.New(review.jirix.NewSeq()).DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
-			return err
-		}
-	}
-	if stashed {
-		if err := gitutil.New(review.jirix.NewSeq()).StashPop(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// createReviewBranch creates a clean review branch from the remote
-// branch this CL pertains to and then iterates over the sequence of
-// dependent CLs leading to the current branch, creating one commit
-// per CL by squashing all commits of each individual CL. The commit
-// message for all but that last CL is derived from their
-// <commitMessageFileName>, while the <message> argument is used as
-// the commit message for the last commit.
-func (review *review) createReviewBranch(message string) (e error) {
-	// Create the review branch.
-	if err := gitutil.New(review.jirix.NewSeq()).FetchRefspec("origin", review.CLOpts.RemoteBranch); err != nil {
-		return err
-	}
-	if gitutil.New(review.jirix.NewSeq()).BranchExists(review.reviewBranch) {
-		if err := gitutil.New(review.jirix.NewSeq()).DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
-			return err
-		}
-	}
-	upstream := "origin/" + review.CLOpts.RemoteBranch
-	if err := gitutil.New(review.jirix.NewSeq()).CreateBranchWithUpstream(review.reviewBranch, upstream); err != nil {
-		return err
-	}
-	if err := gitutil.New(review.jirix.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil {
-		return err
-	}
-	// Register a cleanup handler in case of subsequent errors.
-	cleanup := true
-	defer collect.Error(func() error {
-		if !cleanup {
-			return gitutil.New(review.jirix.NewSeq()).CheckoutBranch(review.CLOpts.Branch)
-		}
-		gitutil.New(review.jirix.NewSeq()).CheckoutBranch(review.CLOpts.Branch, gitutil.ForceOpt(true))
-		gitutil.New(review.jirix.NewSeq()).DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true))
-		return nil
-	}, &e)
-
-	// Report an error if the CL is empty.
-	if !review.jirix.DryRun() {
-		hasDiff, err := gitutil.New(review.jirix.NewSeq()).BranchesDiffer(review.CLOpts.Branch, review.reviewBranch)
-		if err != nil {
-			return err
-		}
-		if !hasDiff {
-			return emptyChangeError(struct{}{})
-		}
-	}
-
-	// If <message> is empty, replace it with the default message.
-	if len(message) == 0 {
-		var err error
-		message, err = review.defaultCommitMessage()
-		if err != nil {
-			return err
-		}
-	}
-
-	// Iterate over all dependent CLs leading to (and including) the
-	// current branch, creating one commit in the review branch per CL
-	// by squashing all commits of each individual CL.
-	branches, err := getDependentCLs(review.jirix, review.CLOpts.Branch)
-	if err != nil {
-		return err
-	}
-	branches = append(branches, review.CLOpts.Branch)
-	if err := review.squashBranches(branches, message); err != nil {
-		return err
-	}
-
-	cleanup = false
-	return nil
-}
-
-// squashBranches iterates over the given list of branches, creating
-// one commit per branch in the current branch by squashing all
-// commits of each individual branch.
-//
-// TODO(jsimsa): Consider using "git rebase --onto" to avoid having to
-// deal with merge conflicts.
-func (review *review) squashBranches(branches []string, message string) (e error) {
-	for i := 1; i < len(branches); i++ {
-		// We want to merge the <branches[i]> branch on top of the review
-		// branch, forcing all conflicts to be reviewed in favor of the
-		// <branches[i]> branch. Unfortunately, git merge does not offer a
-		// strategy that would do that for us. The solution implemented
-		// here is based on:
-		//
-		// http://stackoverflow.com/questions/173919/is-there-a-theirs-version-of-git-merge-s-ours
-		if err := gitutil.New(review.jirix.NewSeq()).Merge(branches[i], gitutil.SquashOpt(true), gitutil.StrategyOpt("ours")); err != nil {
-			return changeConflictError{
-				localBranch:  branches[i],
-				remoteBranch: review.CLOpts.RemoteBranch,
-				message:      err.Error(),
-			}
-		}
-		// Fetch the timestamp of the last commit of <branches[i]> and use
-		// it to create the squashed commit. This is needed to make sure
-		// that the commit hash of the squashed commit stays the same as
-		// long as the squashed sequence of commits does not change. If
-		// this was not the case, consecutive invocations of "jiri cl mail"
-		// could fail if some, but not all, of the dependent CLs submitted
-		// to Gerrit have changed.
-		output, err := gitutil.New(review.jirix.NewSeq()).Log(branches[i], branches[i]+"^", "%ad%n%cd")
-		if err != nil {
-			return err
-		}
-		if len(output) < 1 || len(output[0]) < 2 {
-			return fmt.Errorf("unexpected output length: %v", output)
-		}
-		authorDate := gitutil.AuthorDateOpt(output[0][0])
-		committer := gitutil.CommitterDateOpt(output[0][1])
-		git := gitutil.New(review.jirix.NewSeq(), authorDate, committer)
-		if i < len(branches)-1 {
-			file, err := getCommitMessageFileName(review.jirix, branches[i])
-			if err != nil {
-				return err
-			}
-			message, err := review.jirix.NewSeq().ReadFile(file)
-			if err != nil {
-				return err
-			}
-			if err := git.CommitWithMessage(string(message)); err != nil {
-				return err
-			}
-		} else {
-			committer := git.NewCommitter(review.CLOpts.Edit)
-			if err := committer.Commit(message); err != nil {
-				return err
-			}
-		}
-		tmpBranch := review.reviewBranch + "-" + branches[i] + "-TMP"
-		if err := git.CreateBranch(tmpBranch); err != nil {
-			return err
-		}
-		defer collect.Error(func() error {
-			return gitutil.New(review.jirix.NewSeq()).DeleteBranch(tmpBranch, gitutil.ForceOpt(true))
-		}, &e)
-		if err := git.Reset(branches[i]); err != nil {
-			return err
-		}
-		if err := git.Reset(tmpBranch, gitutil.ModeOpt("soft")); err != nil {
-			return err
-		}
-		if err := git.CommitAmend(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// defaultCommitMessage creates the default commit message from the
-// list of commits on the branch.
-func (review *review) defaultCommitMessage() (string, error) {
-	commitMessages, err := gitutil.New(review.jirix.NewSeq()).CommitMessages(review.CLOpts.Branch, review.reviewBranch)
-	if err != nil {
-		return "", err
-	}
-	// Strip "Change-Id: ..." from the commit messages.
-	strippedMessages := changeIDRE.ReplaceAllLiteralString(commitMessages, "")
-	// Add comment markers (#) to every line.
-	commentedMessages := "# " + strings.Replace(strippedMessages, "\n", "\n# ", -1)
-	message := defaultMessageHeader + commentedMessages
-	return message, nil
-}
-
-// ensureChangeID makes sure that the last commit contains a Change-Id, and
-// returns an error if it does not.
-func (review *review) ensureChangeID() error {
-	latestCommitMessage, err := gitutil.New(review.jirix.NewSeq()).LatestCommitMessage()
-	if err != nil {
-		return err
-	}
-	changeID := changeIDRE.FindString(latestCommitMessage)
-	if changeID == "" {
-		return noChangeIDError(struct{}{})
-	}
-	return nil
-}
-
-// processLabels adds/removes labels for the given commit message.
-func (review *review) processLabels(message string) string {
-	// Find the Change-ID line.
-	changeIDLine := changeIDRE.FindString(message)
-
-	// Strip existing labels and change-ID.
-	message = autosubmitLabelRE.ReplaceAllLiteralString(message, "")
-	message = presubmitTestLabelRE.ReplaceAllLiteralString(message, "")
-	message = changeIDRE.ReplaceAllLiteralString(message, "")
-
-	// Insert labels and change-ID back.
-	if review.CLOpts.Autosubmit {
-		message += fmt.Sprintf("AutoSubmit\n")
-	}
-	if review.CLOpts.Presubmit != gerrit.PresubmitTestTypeAll {
-		message += fmt.Sprintf("PresubmitTest: %s\n", review.CLOpts.Presubmit)
-	}
-	if changeIDLine != "" && !strings.HasSuffix(message, "\n") {
-		message += "\n"
-	}
-	message += changeIDLine
-
-	return message
-}
-
-// run implements checks that the review passes all local checks
-// and then mails it to Gerrit.
-func (review *review) run() (e error) {
-	if uncommittedFlag {
-		changes, err := gitutil.New(review.jirix.NewSeq()).FilesWithUncommittedChanges()
-		if err != nil {
-			return err
-		}
-		if len(changes) != 0 {
-			return uncommittedChangesError(changes)
-		}
-	}
-	if review.CLOpts.Branch == remoteBranchFlag {
-		return fmt.Errorf("cannot do a review from the %q branch.", remoteBranchFlag)
-	}
-	stashed, err := gitutil.New(review.jirix.NewSeq()).Stash()
-	if err != nil {
-		return err
-	}
-	wd, err := os.Getwd()
-	if err != nil {
-		return fmt.Errorf("Getwd() failed: %v", err)
-	}
-	defer collect.Error(func() error { return review.jirix.NewSeq().Chdir(wd).Done() }, &e)
-	topLevel, err := gitutil.New(review.jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return err
-	}
-	s := review.jirix.NewSeq()
-	if err := s.Chdir(topLevel).Done(); err != nil {
-		return err
-	}
-	defer collect.Error(func() error { return review.cleanup(stashed) }, &e)
-	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
-	if err != nil {
-		return err
-	}
-	message := messageFlag
-	if message == "" {
-		// Message was not passed in flag.  Attempt to read it from file.
-		data, err := s.ReadFile(file)
-		if err != nil {
-			if !runutil.IsNotExist(err) {
-				return err
-			}
-		} else {
-			message = string(data)
-		}
-	}
-
-	// Add/remove labels to/from the commit message before asking users
-	// to edit it. We do this only when this is not the initial commit
-	// where the message is empty.
-	//
-	// For the initial commit, the labels will be processed after the
-	// message is edited by users, which happens in the
-	// updateReviewMessage method.
-	if message != "" {
-		message = review.processLabels(message)
-	}
-	if err := review.createReviewBranch(message); err != nil {
-		return err
-	}
-	if err := review.updateReviewMessage(file); err != nil {
-		return err
-	}
-	if err := review.send(); err != nil {
-		return err
-	}
-	if setTopicFlag {
-		if err := review.setTopic(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// send mails the current branch out for review.
-func (review *review) send() error {
-	if !review.jirix.DryRun() {
-		if err := review.ensureChangeID(); err != nil {
-			return err
-		}
-	}
-	if err := gerrit.Push(review.jirix.NewSeq(), review.CLOpts); err != nil {
-		return gerritError(err.Error())
-	}
-	return nil
-}
-
-// getChangeID reads the commit message and extracts the change-Id.
-func (review *review) getChangeID() (string, error) {
-	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
-	if err != nil {
-		return "", err
-	}
-	bytes, err := review.jirix.NewSeq().ReadFile(file)
-	if err != nil {
-		return "", err
-	}
-	changeID := changeIDRE.FindSubmatch(bytes)
-	if changeID == nil || len(changeID) < 2 {
-		return "", fmt.Errorf("could not find Change-Id in:\n%s", bytes)
-	}
-	return string(changeID[1]), nil
-}
-
-// setTopic sets the topic for the CL corresponding to the branch the
-// review was created for.
-func (review *review) setTopic() error {
-	changeID, err := review.getChangeID()
-	if err != nil {
-		return err
-	}
-	host := review.CLOpts.Host
-	if host.Scheme != "http" && host.Scheme != "https" {
-		return fmt.Errorf("Cannot set topic for gerrit host %q. Please use a host url with 'https' scheme or run with '--set-topic=false'.", host.String())
-	}
-	if err := review.jirix.Gerrit(host).SetTopic(changeID, review.CLOpts); err != nil {
-		return err
-	}
-	return nil
-}
-
-// updateReviewMessage writes the commit message to the given file.
-func (review *review) updateReviewMessage(file string) error {
-	if err := gitutil.New(review.jirix.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil {
-		return err
-	}
-	newMessage, err := gitutil.New(review.jirix.NewSeq()).LatestCommitMessage()
-	if err != nil {
-		return err
-	}
-	s := review.jirix.NewSeq()
-	// For the initial commit where the commit message file doesn't exist,
-	// add/remove labels after users finish editing the commit message.
-	//
-	// This behavior is consistent with how Change-ID is added for the
-	// initial commit so we don't confuse users.
-	if _, err := s.Stat(file); err != nil {
-		if runutil.IsNotExist(err) {
-			newMessage = review.processLabels(newMessage)
-			if err := gitutil.New(review.jirix.NewSeq()).CommitAmendWithMessage(newMessage); err != nil {
-				return err
-			}
-		} else {
-			return err
-		}
-	}
-	topLevel, err := gitutil.New(review.jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return err
-	}
-	newMetadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir, review.CLOpts.Branch)
-	if err := s.MkdirAll(newMetadataDir, os.FileMode(0755)).
-		WriteFile(file, []byte(newMessage), 0644).Done(); err != nil {
-		return err
-	}
-	return nil
-}
-
-// cmdCLNew represents the "jiri cl new" command.
-var cmdCLNew = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runCLNew),
-	Name:   "new",
-	Short:  "Create a new local branch for a changelist",
-	Long: fmt.Sprintf(`
-Command "new" creates a new local branch for a changelist. In
-particular, it forks a new branch with the given name from the current
-branch and records the relationship between the current branch and the
-new branch in the %v metadata directory. The information recorded in
-the %v metadata directory tracks dependencies between CLs and is used
-by the "jiri cl sync" and "jiri cl mail" commands.
-`, jiri.ProjectMetaDir, jiri.ProjectMetaDir),
-	ArgsName: "<name>",
-	ArgsLong: "<name> is the changelist name.",
-}
-
-func runCLNew(jirix *jiri.X, args []string) error {
-	if got, want := len(args), 1; got != want {
-		return jirix.UsageErrorf("unexpected number of arguments: got %v, want %v", got, want)
-	}
-	return newCL(jirix, args)
-}
-
-func newCL(jirix *jiri.X, args []string) error {
-	topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return err
-	}
-	originalBranch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return err
-	}
-
-	// Create a new branch using the current branch.
-	newBranch := args[0]
-	if err := gitutil.New(jirix.NewSeq()).CreateAndCheckoutBranch(newBranch); err != nil {
-		return err
-	}
-
-	// Register a cleanup handler in case of subsequent errors.
-	cleanup := true
-	defer func() {
-		if cleanup {
-			gitutil.New(jirix.NewSeq()).CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
-			gitutil.New(jirix.NewSeq()).DeleteBranch(newBranch, gitutil.ForceOpt(true))
-		}
-	}()
-
-	s := jirix.NewSeq()
-	// Record the dependent CLs for the new branch. The dependent CLs
-	// are recorded in a <dependencyPathFileName> file as a
-	// newline-separated list of branch names.
-	branches, err := getDependentCLs(jirix, originalBranch)
-	if err != nil {
-		return err
-	}
-	branches = append(branches, originalBranch)
-	newMetadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir, newBranch)
-	if err := s.MkdirAll(newMetadataDir, os.FileMode(0755)).Done(); err != nil {
-		return err
-	}
-	file, err := getDependencyPathFileName(jirix, newBranch)
-	if err != nil {
-		return err
-	}
-	if err := s.WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)).Done(); err != nil {
-		return err
-	}
-
-	cleanup = false
-	return nil
-}
-
-// cmdCLSync represents the "jiri cl sync" command.
-var cmdCLSync = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runCLSync),
-	Name:   "sync",
-	Short:  "Bring a changelist up to date",
-	Long: fmt.Sprintf(`
-Command "sync" brings the CL identified by the current branch up to
-date with the branch tracking the remote branch this CL pertains
-to. To do that, the command uses the information recorded in the %v
-metadata directory to identify the sequence of dependent CLs leading
-to the current branch. The command then iterates over this sequence
-bringing each of the CLs up to date with its ancestor. The end result
-of this process is that all CLs in the sequence are up to date with
-the branch that tracks the remote branch this CL pertains to.
-
-NOTE: It is possible that the command cannot automatically merge
-changes in an ancestor into its dependent. When that occurs, the
-command is aborted and prints instructions that need to be followed
-before the command can be retried.
-`, jiri.ProjectMetaDir),
-}
-
-func runCLSync(jirix *jiri.X, _ []string) error {
-	return syncCL(jirix)
-}
-
-func syncCL(jirix *jiri.X) (e error) {
-	stashed, err := gitutil.New(jirix.NewSeq()).Stash()
-	if err != nil {
-		return err
-	}
-	if stashed {
-		defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).StashPop() }, &e)
-	}
-
-	// Register a cleanup handler in case of subsequent errors.
-	forceOriginalBranch := true
-	originalBranch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return err
-	}
-	originalWd, err := os.Getwd()
-	if err != nil {
-		return err
-	}
-
-	defer func() {
-		if forceOriginalBranch {
-			gitutil.New(jirix.NewSeq()).CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
-		}
-		jirix.NewSeq().Chdir(originalWd)
-	}()
-
-	s := jirix.NewSeq()
-	// Switch to an existing directory in master so we can run commands.
-	topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel()
-	if err != nil {
-		return err
-	}
-	if err := s.Chdir(topLevel).Done(); err != nil {
-		return err
-	}
-
-	// Identify the dependents CLs leading to (and including) the
-	// current branch.
-	branches, err := getDependentCLs(jirix, originalBranch)
-	if err != nil {
-		return err
-	}
-	branches = append(branches, originalBranch)
-
-	// Sync from upstream.
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(branches[0]); err != nil {
-		return err
-	}
-	if err := gitutil.New(jirix.NewSeq()).Pull("origin", branches[0]); err != nil {
-		return err
-	}
-
-	// Bring all CLs in the sequence of dependent CLs leading to the
-	// current branch up to date with the <remoteBranchFlag> branch.
-	for i := 1; i < len(branches); i++ {
-		if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(branches[i]); err != nil {
-			return err
-		}
-		if err := gitutil.New(jirix.NewSeq()).Merge(branches[i-1]); err != nil {
-			return fmt.Errorf(`Failed to automatically merge branch %v into branch %v: %v
-The following steps are needed before the operation can be retried:
-$ git checkout %v
-$ git merge %v
-# resolve all conflicts
-$ git commit -a
-$ git checkout %v
-# retry the original operation
-`, branches[i], branches[i-1], err, branches[i], branches[i-1], originalBranch)
-		}
-	}
-
-	forceOriginalBranch = false
-	return nil
-}
diff --git a/cl_test.go b/cl_test.go
deleted file mode 100644
index e92f35b..0000000
--- a/cl_test.go
+++ /dev/null
@@ -1,1033 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"bytes"
-	"os"
-	"path"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"testing"
-
-	"v.io/jiri/gerrit"
-	"v.io/jiri/gitutil"
-	"v.io/jiri/jiri"
-	"v.io/jiri/jiritest"
-	"v.io/jiri/runutil"
-)
-
-// assertCommitCount asserts that the commit count between two
-// branches matches the expectedCount.
-func assertCommitCount(t *testing.T, jirix *jiri.X, branch, baseBranch string, expectedCount int) {
-	got, err := gitutil.New(jirix.NewSeq()).CountCommits(branch, baseBranch)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if want := 1; got != want {
-		t.Fatalf("unexpected number of commits: got %v, want %v", got, want)
-	}
-}
-
-// assertFileContent asserts that the content of the given file
-// matches the expected content.
-func assertFileContent(t *testing.T, jirix *jiri.X, file, want string) {
-	got, err := jirix.NewSeq().ReadFile(file)
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-	if string(got) != want {
-		t.Fatalf("unexpected content of file %v: got %v, want %v", file, got, want)
-	}
-}
-
-// assertFilesExist asserts that the files exist.
-func assertFilesExist(t *testing.T, jirix *jiri.X, files []string) {
-	s := jirix.NewSeq()
-	for _, file := range files {
-		if _, err := s.Stat(file); err != nil {
-			if runutil.IsNotExist(err) {
-				t.Fatalf("expected file %v to exist but it did not", file)
-			}
-			t.Fatalf("%v", err)
-		}
-	}
-}
-
-// assertFilesDoNotExist asserts that the files do not exist.
-func assertFilesDoNotExist(t *testing.T, jirix *jiri.X, files []string) {
-	s := jirix.NewSeq()
-	for _, file := range files {
-		if _, err := s.Stat(file); err != nil && !runutil.IsNotExist(err) {
-			t.Fatalf("%v", err)
-		} else if err == nil {
-			t.Fatalf("expected file %v to not exist but it did", file)
-		}
-	}
-}
-
-// assertFilesCommitted asserts that the files exist and are committed
-// in the current branch.
-func assertFilesCommitted(t *testing.T, jirix *jiri.X, files []string) {
-	assertFilesExist(t, jirix, files)
-	for _, file := range files {
-		if !gitutil.New(jirix.NewSeq()).IsFileCommitted(file) {
-			t.Fatalf("expected file %v to be committed but it is not", file)
-		}
-	}
-}
-
-// assertFilesNotCommitted asserts that the files exist and are *not*
-// committed in the current branch.
-func assertFilesNotCommitted(t *testing.T, jirix *jiri.X, files []string) {
-	assertFilesExist(t, jirix, files)
-	for _, file := range files {
-		if gitutil.New(jirix.NewSeq()).IsFileCommitted(file) {
-			t.Fatalf("expected file %v not to be committed but it is", file)
-		}
-	}
-}
-
-// assertFilesPushedToRef asserts that the given files have been
-// pushed to the given remote repository reference.
-func assertFilesPushedToRef(t *testing.T, jirix *jiri.X, repoPath, gerritPath, pushedRef string, files []string) {
-	chdir(t, jirix, gerritPath)
-	assertCommitCount(t, jirix, pushedRef, "master", 1)
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(pushedRef); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertFilesCommitted(t, jirix, files)
-	chdir(t, jirix, repoPath)
-}
-
-// assertStashSize asserts that the stash size matches the expected
-// size.
-func assertStashSize(t *testing.T, jirix *jiri.X, want int) {
-	got, err := gitutil.New(jirix.NewSeq()).StashSize()
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if got != want {
-		t.Fatalf("unxpected stash size: got %v, want %v", got, want)
-	}
-}
-
-// commitFile commits a file with the specified content into a branch
-func commitFile(t *testing.T, jirix *jiri.X, filename string, content string) {
-	s := jirix.NewSeq()
-	if err := s.WriteFile(filename, []byte(content), 0644).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitMessage := "Commit " + filename
-	if err := gitutil.New(jirix.NewSeq()).CommitFile(filename, commitMessage); err != nil {
-		t.Fatalf("%v", err)
-	}
-}
-
-// commitFiles commits the given files into to current branch.
-func commitFiles(t *testing.T, jirix *jiri.X, filenames []string) {
-	// Create and commit the files one at a time.
-	for _, filename := range filenames {
-		content := "This is file " + filename
-		commitFile(t, jirix, filename, content)
-	}
-}
-
-// createRepo creates a new repository with the given prefix.
-func createRepo(t *testing.T, jirix *jiri.X, prefix string) string {
-	s := jirix.NewSeq()
-	repoPath, err := s.TempDir(jirix.Root, "repo-"+prefix)
-	if err != nil {
-		t.Fatalf("TempDir() failed: %v", err)
-	}
-	if err := os.Chmod(repoPath, 0777); err != nil {
-		t.Fatalf("Chmod(%v) failed: %v", repoPath, err)
-	}
-	if err := gitutil.New(jirix.NewSeq()).Init(repoPath); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := s.MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	return repoPath
-}
-
-// Simple commit-msg hook that adds a fake Change Id.
-var commitMsgHook string = `#!/bin/sh
-MSG="$1"
-echo "Change-Id: I0000000000000000000000000000000000000000" >> $MSG
-`
-
-// installCommitMsgHook links the gerrit commit-msg hook into a different repo.
-func installCommitMsgHook(t *testing.T, jirix *jiri.X, repoPath string) {
-	hookLocation := path.Join(repoPath, ".git/hooks/commit-msg")
-	if err := jirix.NewSeq().WriteFile(hookLocation, []byte(commitMsgHook), 0755).Done(); err != nil {
-		t.Fatalf("WriteFile(%v) failed: %v", hookLocation, err)
-	}
-}
-
-// chdir changes the runtime working directory and traps any errors.
-func chdir(t *testing.T, jirix *jiri.X, path string) {
-	if err := jirix.NewSeq().Chdir(path).Done(); err != nil {
-		_, file, line, _ := runtime.Caller(1)
-		t.Fatalf("%s: %d: Chdir(%v) failed: %v", file, line, path, err)
-	}
-}
-
-// createRepoFromOrigin creates a Git repo tracking origin/master.
-func createRepoFromOrigin(t *testing.T, jirix *jiri.X, subpath string, originPath string) string {
-	repoPath := createRepo(t, jirix, subpath)
-	chdir(t, jirix, repoPath)
-	if err := gitutil.New(jirix.NewSeq()).AddRemote("origin", originPath); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(jirix.NewSeq()).Pull("origin", "master"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	return repoPath
-}
-
-// createTestRepos sets up three local repositories: origin, gerrit,
-// and the main test repository which pulls from origin and can push
-// to gerrit.
-func createTestRepos(t *testing.T, jirix *jiri.X) (string, string, string) {
-	// Create origin.
-	originPath := createRepo(t, jirix, "origin")
-	chdir(t, jirix, originPath)
-	if err := gitutil.New(jirix.NewSeq()).CommitWithMessage("initial commit"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	// Create test repo.
-	repoPath := createRepoFromOrigin(t, jirix, "test", originPath)
-	// Add Gerrit remote.
-	gerritPath := createRepoFromOrigin(t, jirix, "gerrit", originPath)
-	// Switch back to test repo.
-	chdir(t, jirix, repoPath)
-	return repoPath, originPath, gerritPath
-}
-
-// submit mocks a Gerrit review submit by pushing the Gerrit remote to origin.
-// Actually origin pulls from Gerrit since origin isn't actually a bare git repo.
-// Some of our tests actually rely on accessing .git in origin, so it must be non-bare.
-func submit(t *testing.T, jirix *jiri.X, originPath string, gerritPath string, review *review) {
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("Getwd() failed: %v", err)
-	}
-	chdir(t, jirix, originPath)
-	expectedRef := gerrit.Reference(review.CLOpts)
-	if err := gitutil.New(jirix.NewSeq()).Pull(gerritPath, expectedRef); err != nil {
-		t.Fatalf("Pull gerrit to origin failed: %v", err)
-	}
-	chdir(t, jirix, cwd)
-}
-
-// setupTest creates a setup for testing the review tool.
-func setupTest(t *testing.T, installHook bool) (fake *jiritest.FakeJiriRoot, repoPath, originPath, gerritPath string, cleanup func()) {
-	oldWD, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("Getwd() failed: %v", err)
-	}
-	var cleanupFake func()
-	if fake, cleanupFake = jiritest.NewFakeJiriRoot(t); err != nil {
-		t.Fatalf("%v", err)
-	}
-	repoPath, originPath, gerritPath = createTestRepos(t, fake.X)
-	if installHook == true {
-		for _, path := range []string{repoPath, originPath, gerritPath} {
-			installCommitMsgHook(t, fake.X, path)
-		}
-	}
-	chdir(t, fake.X, repoPath)
-	cleanup = func() {
-		chdir(t, fake.X, oldWD)
-		cleanupFake()
-	}
-	return
-}
-
-func createCLWithFiles(t *testing.T, jirix *jiri.X, branch string, files ...string) {
-	if err := newCL(jirix, []string{branch}); err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitFiles(t, jirix, files)
-}
-
-// TestCleanupClean checks that cleanup succeeds if the branch to be
-// cleaned up has been merged with the master.
-func TestCleanupClean(t *testing.T) {
-	fake, repoPath, originPath, _, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitFiles(t, fake.X, []string{"file1", "file2"})
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	chdir(t, fake.X, originPath)
-	commitFiles(t, fake.X, []string{"file1", "file2"})
-	chdir(t, fake.X, repoPath)
-	if err := cleanupCL(fake.X, []string{branch}); err != nil {
-		t.Fatalf("cleanup() failed: %v", err)
-	}
-	if gitutil.New(fake.X.NewSeq()).BranchExists(branch) {
-		t.Fatalf("cleanup failed to remove the feature branch")
-	}
-}
-
-// TestCleanupDirty checks that cleanup is a no-op if the branch to be
-// cleaned up has unmerged changes.
-func TestCleanupDirty(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	files := []string{"file1", "file2"}
-	commitFiles(t, fake.X, files)
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := cleanupCL(fake.X, []string{branch}); err == nil {
-		t.Fatalf("cleanup did not fail when it should")
-	}
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertFilesCommitted(t, fake.X, files)
-}
-
-// TestCreateReviewBranch checks that the temporary review branch is
-// created correctly.
-func TestCreateReviewBranch(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, fake.X, files)
-	review, err := newReview(fake.X, gerrit.CLOpts{})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if expected, got := branch+"-REVIEW", review.reviewBranch; expected != got {
-		t.Fatalf("Unexpected review branch name: expected %v, got %v", expected, got)
-	}
-	commitMessage := "squashed commit"
-	if err := review.createReviewBranch(commitMessage); err != nil {
-		t.Fatalf("%v", err)
-	}
-	// Verify that the branch exists.
-	if !gitutil.New(fake.X.NewSeq()).BranchExists(review.reviewBranch) {
-		t.Fatalf("review branch not found")
-	}
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertCommitCount(t, fake.X, review.reviewBranch, "master", 1)
-	assertFilesCommitted(t, fake.X, files)
-}
-
-// TestCreateReviewBranchWithEmptyChange checks that running
-// createReviewBranch() on a branch with no changes will result in an
-// EmptyChangeError.
-func TestCreateReviewBranchWithEmptyChange(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	review, err := newReview(fake.X, gerrit.CLOpts{Remote: branch})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitMessage := "squashed commit"
-	err = review.createReviewBranch(commitMessage)
-	if err == nil {
-		t.Fatalf("creating a review did not fail when it should")
-	}
-	if _, ok := err.(emptyChangeError); !ok {
-		t.Fatalf("unexpected error type: %v", err)
-	}
-}
-
-// TestSendReview checks the various options for sending a review.
-func TestSendReview(t *testing.T) {
-	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	files := []string{"file1"}
-	commitFiles(t, fake.X, files)
-	{
-		// Test with draft = false, no reviewiers, and no ccs.
-		review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := review.send(); err != nil {
-			t.Fatalf("failed to send a review: %v", err)
-		}
-		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	}
-	{
-		// Test with draft = true, no reviewers, and no ccs.
-		review, err := newReview(fake.X, gerrit.CLOpts{
-			Draft:  true,
-			Remote: gerritPath,
-		})
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := review.send(); err != nil {
-			t.Fatalf("failed to send a review: %v", err)
-		}
-		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	}
-	{
-		// Test with draft = false, reviewers, and no ccs.
-		review, err := newReview(fake.X, gerrit.CLOpts{
-			Remote:    gerritPath,
-			Reviewers: parseEmails("reviewer1,reviewer2@example.org"),
-		})
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := review.send(); err != nil {
-			t.Fatalf("failed to send a review: %v", err)
-		}
-		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	}
-	{
-		// Test with draft = true, reviewers, and ccs.
-		review, err := newReview(fake.X, gerrit.CLOpts{
-			Ccs:       parseEmails("cc1@example.org,cc2"),
-			Draft:     true,
-			Remote:    gerritPath,
-			Reviewers: parseEmails("reviewer3@example.org,reviewer4"),
-		})
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := review.send(); err != nil {
-			t.Fatalf("failed to send a review: %v", err)
-		}
-		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	}
-}
-
-// TestSendReviewNoChangeID checks that review.send() correctly errors when
-// not run with a commit hook that adds a Change-Id.
-func TestSendReviewNoChangeID(t *testing.T) {
-	// Pass 'false' to setup so it doesn't install the commit-msg hook.
-	fake, _, _, gerritPath, cleanup := setupTest(t, false)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitFiles(t, fake.X, []string{"file1"})
-	review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	err = review.send()
-	if err == nil {
-		t.Fatalf("sending a review did not fail when it should")
-	}
-	if _, ok := err.(noChangeIDError); !ok {
-		t.Fatalf("unexpected error type: %v", err)
-	}
-}
-
-// TestEndToEnd checks the end-to-end functionality of the review tool.
-func TestEndToEnd(t *testing.T) {
-	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, fake.X, files)
-	review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := review.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-}
-
-// TestLabelsInCommitMessage checks the labels are correctly processed
-// for the commit message.
-//
-// HACK ALERT: This test runs the review.run() function multiple
-// times. The function ends up pushing a commit to a fake "gerrit"
-// repository created by the setupTest() function. For the real gerrit
-// repository, it is possible to push to the refs/for/change reference
-// multiple times, because it is a special reference that "maps"
-// incoming commits to CL branches based on the commit message
-// Change-Id. The fake "gerrit" repository does not implement this
-// logic and thus the same reference cannot be pushed to multiple
-// times. To overcome this obstacle, the test takes advantage of the
-// fact that the reference name is a function of the reviewers and
-// uses different reviewers for different review runs.
-func TestLabelsInCommitMessage(t *testing.T) {
-	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	s := fake.X.NewSeq()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Test setting -presubmit=none and autosubmit.
-	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, fake.X, files)
-	review, err := newReview(fake.X, gerrit.CLOpts{
-		Autosubmit: true,
-		Presubmit:  gerrit.PresubmitTestTypeNone,
-		Remote:     gerritPath,
-		Reviewers:  parseEmails("run1"),
-	})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := review.run(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	// The last three lines of the gerrit commit message file should be:
-	// AutoSubmit
-	// PresubmitTest: none
-	// Change-Id: ...
-	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	bytes, err := s.ReadFile(file)
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-	content := string(bytes)
-	lines := strings.Split(content, "\n")
-	// Make sure the Change-Id line is the last line.
-	if got := lines[len(lines)-1]; !strings.HasPrefix(got, "Change-Id") {
-		t.Fatalf("no Change-Id line found: %s", got)
-	}
-	// Make sure the "AutoSubmit" label exists.
-	if autosubmitLabelRE.FindString(content) == "" {
-		t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content)
-	}
-	// Make sure the "PresubmitTest" label exists.
-	if presubmitTestLabelRE.FindString(content) == "" {
-		t.Fatalf("PresubmitTest label doesn't exist in the commit message: %s", content)
-	}
-
-	// Test setting -presubmit=all but keep autosubmit=true.
-	review, err = newReview(fake.X, gerrit.CLOpts{
-		Autosubmit: true,
-		Remote:     gerritPath,
-		Reviewers:  parseEmails("run2"),
-	})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := review.run(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	expectedRef = gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	bytes, err = s.ReadFile(file)
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-	content = string(bytes)
-	// Make sure there is no PresubmitTest=none any more.
-	match := presubmitTestLabelRE.FindString(content)
-	if match != "" {
-		t.Fatalf("want no presubmit label line, got: %s", match)
-	}
-	// Make sure the "AutoSubmit" label still exists.
-	if autosubmitLabelRE.FindString(content) == "" {
-		t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content)
-	}
-
-	// Test setting autosubmit=false.
-	review, err = newReview(fake.X, gerrit.CLOpts{
-		Remote:    gerritPath,
-		Reviewers: parseEmails("run3"),
-	})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := review.run(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	expectedRef = gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	bytes, err = s.ReadFile(file)
-	if err != nil {
-		t.Fatalf("%v\n", err)
-	}
-	content = string(bytes)
-	// Make sure there is no AutoSubmit label any more.
-	match = autosubmitLabelRE.FindString(content)
-	if match != "" {
-		t.Fatalf("want no AutoSubmit label line, got: %s", match)
-	}
-}
-
-// TestDirtyBranch checks that the tool correctly handles unstaged and
-// untracked changes in a working branch with stashed changes.
-func TestDirtyBranch(t *testing.T) {
-	fake, _, _, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	s := fake.X.NewSeq()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	files := []string{"file1", "file2"}
-	commitFiles(t, fake.X, files)
-	assertStashSize(t, fake.X, 0)
-	stashedFile, stashedFileContent := "stashed-file", "stashed-file content"
-	if err := s.WriteFile(stashedFile, []byte(stashedFileContent), 0644).Done(); err != nil {
-		t.Fatalf("WriteFile(%v, %v) failed: %v", stashedFile, stashedFileContent, err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).Add(stashedFile); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if _, err := gitutil.New(fake.X.NewSeq()).Stash(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertStashSize(t, fake.X, 1)
-	modifiedFile, modifiedFileContent := "file1", "modified-file content"
-	if err := s.WriteFile(modifiedFile, []byte(modifiedFileContent), 0644).Done(); err != nil {
-		t.Fatalf("WriteFile(%v, %v) failed: %v", modifiedFile, modifiedFileContent, err)
-	}
-	stagedFile, stagedFileContent := "file2", "staged-file content"
-	if err := s.WriteFile(stagedFile, []byte(stagedFileContent), 0644).Done(); err != nil {
-		t.Fatalf("WriteFile(%v, %v) failed: %v", stagedFile, stagedFileContent, err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).Add(stagedFile); err != nil {
-		t.Fatalf("%v", err)
-	}
-	untrackedFile, untrackedFileContent := "file3", "untracked-file content"
-	if err := s.WriteFile(untrackedFile, []byte(untrackedFileContent), 0644).Done(); err != nil {
-		t.Fatalf("WriteFile(%v, %v) failed: %v", untrackedFile, untrackedFileContent, err)
-	}
-	review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := review.run(); err == nil {
-		t.Fatalf("run() didn't fail when it should")
-	}
-	assertFilesNotCommitted(t, fake.X, []string{stagedFile})
-	assertFilesNotCommitted(t, fake.X, []string{untrackedFile})
-	assertFileContent(t, fake.X, modifiedFile, modifiedFileContent)
-	assertFileContent(t, fake.X, stagedFile, stagedFileContent)
-	assertFileContent(t, fake.X, untrackedFile, untrackedFileContent)
-	// As of git 2.4.3 "git stash pop" fails if there are uncommitted
-	// changes in the index. So we need to commit them first.
-	if err := gitutil.New(fake.X.NewSeq()).Commit(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertStashSize(t, fake.X, 1)
-	if err := gitutil.New(fake.X.NewSeq()).StashPop(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertStashSize(t, fake.X, 0)
-	assertFilesNotCommitted(t, fake.X, []string{stashedFile})
-	assertFileContent(t, fake.X, stashedFile, stashedFileContent)
-}
-
-// TestRunInSubdirectory checks that the command will succeed when run from
-// within a subdirectory of a branch that does not exist on master branch, and
-// will return the user to the subdirectory after completion.
-func TestRunInSubdirectory(t *testing.T) {
-	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	s := fake.X.NewSeq()
-	branch := "my-branch"
-	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
-		t.Fatalf("%v", err)
-	}
-	subdir := "sub/directory"
-	subdirPerms := os.FileMode(0744)
-	if err := s.MkdirAll(subdir, subdirPerms).Done(); err != nil {
-		t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err)
-	}
-	files := []string{path.Join(subdir, "file1")}
-	commitFiles(t, fake.X, files)
-	chdir(t, fake.X, subdir)
-	review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := review.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-	path := path.Join(repoPath, subdir)
-	want, err := filepath.EvalSymlinks(path)
-	if err != nil {
-		t.Fatalf("EvalSymlinks(%v) failed: %v", path, err)
-	}
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	got, err := filepath.EvalSymlinks(cwd)
-	if err != nil {
-		t.Fatalf("EvalSymlinks(%v) failed: %v", cwd, err)
-	}
-	if got != want {
-		t.Fatalf("unexpected working directory: got %v, want %v", got, want)
-	}
-	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-}
-
-// TestProcessLabels checks that the processLabels function works as expected.
-func TestProcessLabels(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-	testCases := []struct {
-		autosubmit      bool
-		presubmitType   gerrit.PresubmitTestType
-		originalMessage string
-		expectedMessage string
-	}{
-		{
-			presubmitType:   gerrit.PresubmitTestTypeNone,
-			originalMessage: "",
-			expectedMessage: "PresubmitTest: none\n",
-		},
-		{
-			autosubmit:      true,
-			presubmitType:   gerrit.PresubmitTestTypeNone,
-			originalMessage: "",
-			expectedMessage: "AutoSubmit\nPresubmitTest: none\n",
-		},
-		{
-			presubmitType:   gerrit.PresubmitTestTypeNone,
-			originalMessage: "review message\n",
-			expectedMessage: "review message\nPresubmitTest: none\n",
-		},
-		{
-			autosubmit:      true,
-			presubmitType:   gerrit.PresubmitTestTypeNone,
-			originalMessage: "review message\n",
-			expectedMessage: "review message\nAutoSubmit\nPresubmitTest: none\n",
-		},
-		{
-			presubmitType: gerrit.PresubmitTestTypeNone,
-			originalMessage: `review message
-
-Change-Id: I0000000000000000000000000000000000000000`,
-			expectedMessage: `review message
-
-PresubmitTest: none
-Change-Id: I0000000000000000000000000000000000000000`,
-		},
-		{
-			autosubmit:    true,
-			presubmitType: gerrit.PresubmitTestTypeNone,
-			originalMessage: `review message
-
-Change-Id: I0000000000000000000000000000000000000000`,
-			expectedMessage: `review message
-
-AutoSubmit
-PresubmitTest: none
-Change-Id: I0000000000000000000000000000000000000000`,
-		},
-		{
-			presubmitType:   gerrit.PresubmitTestTypeAll,
-			originalMessage: "",
-			expectedMessage: "",
-		},
-		{
-			presubmitType:   gerrit.PresubmitTestTypeAll,
-			originalMessage: "review message\n",
-			expectedMessage: "review message\n",
-		},
-		{
-			presubmitType: gerrit.PresubmitTestTypeAll,
-			originalMessage: `review message
-
-Change-Id: I0000000000000000000000000000000000000000`,
-			expectedMessage: `review message
-
-Change-Id: I0000000000000000000000000000000000000000`,
-		},
-	}
-	for _, test := range testCases {
-		review, err := newReview(fake.X, gerrit.CLOpts{
-			Autosubmit: test.autosubmit,
-			Presubmit:  test.presubmitType,
-		})
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if got := review.processLabels(test.originalMessage); got != test.expectedMessage {
-			t.Fatalf("want %s, got %s", test.expectedMessage, got)
-		}
-	}
-}
-
-// TestCLNew checks the operation of the "jiri cl new" command.
-func TestCLNew(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-
-	// Create some dependent CLs.
-	if err := newCL(fake.X, []string{"feature1"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := newCL(fake.X, []string{"feature2"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Check that their dependency paths have been recorded correctly.
-	testCases := []struct {
-		branch string
-		data   []byte
-	}{
-		{
-			branch: "feature1",
-			data:   []byte("master"),
-		},
-		{
-			branch: "feature2",
-			data:   []byte("master\nfeature1"),
-		},
-	}
-	s := fake.X.NewSeq()
-	for _, testCase := range testCases {
-		file, err := getDependencyPathFileName(fake.X, testCase.branch)
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		data, err := s.ReadFile(file)
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if bytes.Compare(data, testCase.data) != 0 {
-			t.Fatalf("unexpected data:\ngot\n%v\nwant\n%v", string(data), string(testCase.data))
-		}
-	}
-}
-
-// TestDependentClsWithEditDelete exercises a previously observed failure case
-// where if a CL edits a file and a dependent CL deletes it, jiri cl mail after
-// the deletion failed with unrecoverable merge errors.
-func TestDependentClsWithEditDelete(t *testing.T) {
-	fake, repoPath, originPath, gerritPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	chdir(t, fake.X, originPath)
-	commitFiles(t, fake.X, []string{"A", "B"})
-
-	chdir(t, fake.X, repoPath)
-	if err := syncCL(fake.X); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertFilesExist(t, fake.X, []string{"A", "B"})
-
-	createCLWithFiles(t, fake.X, "editme", "C")
-	if err := fake.X.NewSeq().WriteFile("B", []byte("Will I dream?"), 0644).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("editing stuff"); err != nil {
-		t.Fatalf("git commit failed: %v", err)
-	}
-	review, err := newReview(fake.X, gerrit.CLOpts{
-		Remote:    gerritPath,
-		Reviewers: parseEmails("run1"), // See hack note about TestLabelsInCommitMessage
-	})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := review.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-
-	if err := newCL(fake.X, []string{"deleteme"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).Remove("B", "C"); err != nil {
-		t.Fatalf("git rm B C failed: %v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("deleting stuff"); err != nil {
-		t.Fatalf("git commit failed: %v", err)
-	}
-	review, err = newReview(fake.X, gerrit.CLOpts{
-		Remote:    gerritPath,
-		Reviewers: parseEmails("run2"),
-	})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := review.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-
-	chdir(t, fake.X, gerritPath)
-	expectedRef := gerrit.Reference(review.CLOpts)
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertFilesExist(t, fake.X, []string{"A"})
-	assertFilesDoNotExist(t, fake.X, []string{"B", "C"})
-}
-
-// TestParallelDev checks "jiri cl mail" behavior when parallel development has
-// been submitted upstream.
-func TestParallelDev(t *testing.T) {
-	fake, repoPath, originPath, gerritAPath, cleanup := setupTest(t, true)
-	defer cleanup()
-	gerritBPath := createRepoFromOrigin(t, fake.X, "gerritB", originPath)
-	chdir(t, fake.X, repoPath)
-
-	// Create parallel branches with:
-	// * non-conflicting changes in different files
-	// * conflicting changes in a file
-	createCLWithFiles(t, fake.X, "feature1-A", "A")
-
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	createCLWithFiles(t, fake.X, "feature1-B", "B")
-	commitFile(t, fake.X, "A", "Don't tread on me.")
-
-	reviewB, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritBPath})
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	setTopicFlag = false
-	if err := reviewB.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-
-	// Submit B and verify A doesn't revert it.
-	submit(t, fake.X, originPath, gerritBPath, reviewB)
-
-	// Assert files pushed to origin.
-	chdir(t, fake.X, originPath)
-	assertFilesExist(t, fake.X, []string{"A", "B"})
-	chdir(t, fake.X, repoPath)
-
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature1-A"); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	reviewA, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritAPath})
-	if err == nil {
-		t.Fatalf("creating a review did not fail when it should")
-	}
-	// Assert state restored after failed review.
-	assertFileContent(t, fake.X, "A", "This is file A")
-	assertFilesDoNotExist(t, fake.X, []string{"B"})
-
-	// Manual conflict resolution.
-	if err := gitutil.New(fake.X.NewSeq()).Merge("master", gitutil.ResetOnFailureOpt(false)); err == nil {
-		t.Fatalf("merge applied cleanly when it shouldn't")
-	}
-	assertFilesNotCommitted(t, fake.X, []string{"A", "B"})
-	assertFileContent(t, fake.X, "B", "This is file B")
-
-	if err := fake.X.NewSeq().WriteFile("A", []byte("This is file A. Don't tread on me."), 0644).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	if err := gitutil.New(fake.X.NewSeq()).Add("A"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("Conflict resolution"); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Retry review.
-	reviewA, err = newReview(fake.X, gerrit.CLOpts{Remote: gerritAPath})
-	if err != nil {
-		t.Fatalf("review failed: %v", err)
-	}
-
-	if err := reviewA.run(); err != nil {
-		t.Fatalf("run() failed: %v", err)
-	}
-
-	chdir(t, fake.X, gerritAPath)
-	expectedRef := gerrit.Reference(reviewA.CLOpts)
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil {
-		t.Fatalf("%v", err)
-	}
-	assertFilesExist(t, fake.X, []string{"B"})
-}
-
-// TestCLSync checks the operation of the "jiri cl sync" command.
-func TestCLSync(t *testing.T) {
-	fake, _, _, _, cleanup := setupTest(t, true)
-	defer cleanup()
-
-	// Create some dependent CLs.
-	if err := newCL(fake.X, []string{"feature1"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := newCL(fake.X, []string{"feature2"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Add the "test" file to the master.
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	commitFiles(t, fake.X, []string{"test"})
-
-	// Sync the dependent CLs.
-	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature2"); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := syncCL(fake.X); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Check that the "test" file exists in the dependent CLs.
-	for _, branch := range []string{"feature1", "feature2"} {
-		if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil {
-			t.Fatalf("%v", err)
-		}
-		assertFilesExist(t, fake.X, []string{"test"})
-	}
-}
diff --git a/cmd.go b/cmd.go
deleted file mode 100644
index 0d4d2ee..0000000
--- a/cmd.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// The following enables go generate to generate the doc.go file.
-//go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go -env="" .
-
-package main
-
-import (
-	"runtime"
-
-	"v.io/jiri/tool"
-	"v.io/x/lib/cmdline"
-)
-
-func init() {
-	runtime.GOMAXPROCS(runtime.NumCPU())
-
-	tool.InitializeRunFlags(&cmdRoot.Flags)
-}
-
-func main() {
-	cmdline.Main(cmdRoot)
-}
-
-// cmdRoot represents the root of the jiri tool.
-var cmdRoot = &cmdline.Command{
-	Name:  "jiri",
-	Short: "Multi-purpose tool for multi-repo development",
-	Long: `
-Command jiri is a multi-purpose tool for multi-repo development.
-`,
-	LookPath: true,
-	Children: []*cmdline.Command{
-		cmdCL,
-		cmdContributors,
-		cmdImport,
-		cmdProfile,
-		cmdProject,
-		cmdRebuild,
-		cmdSnapshot,
-		cmdUpdate,
-		cmdUpgrade,
-		cmdWhich,
-	},
-	Topics: []cmdline.Topic{
-		topicFileSystem,
-		topicManifest,
-	},
-}
-
-var topicFileSystem = cmdline.Topic{
-	Name:  "filesystem",
-	Short: "Description of jiri file system layout",
-	Long: `
-All data managed by the jiri tool is located in the file system under a root
-directory, colloquially called the jiri root directory.  The file system layout
-looks like this:
-
- [root]                              # root directory (name picked by user)
- [root]/.jiri_root                   # root metadata directory
- [root]/.jiri_root/bin               # contains tool binaries (jiri, etc.)
- [root]/.jiri_root/update_history    # contains history of update snapshots
- [root]/.manifest                    # contains jiri manifests
- [root]/[project1]                   # project directory (name picked by user)
- [root]/[project1]/.jiri             # project metadata directory
- [root]/[project1]/.jiri/metadata.v2 # project metadata file
- [root]/[project1]/.jiri/<<cls>>     # project per-cl metadata directories
- [root]/[project1]/<<files>>         # project files
- [root]/[project2]...
-
-The [root] and [projectN] directory names are picked by the user.  The <<cls>>
-are named via jiri cl new, and the <<files>> are named as the user adds files
-and directories to their project.  All other names above have special meaning to
-the jiri tool, and cannot be changed; you must ensure your path names don't
-collide with these special names.
-
-There are two ways to run the jiri tool:
-
-1) Shim script (recommended approach).  This is a shell script that looks for
-the [root] directory.  If the JIRI_ROOT environment variable is set, that is
-assumed to be the [root] directory.  Otherwise the script looks for the
-.jiri_root directory, starting in the current working directory and walking up
-the directory chain.  The search is terminated successfully when the .jiri_root
-directory is found; it fails after it reaches the root of the file system.  Thus
-the shim must be invoked from the [root] directory or one of its subdirectories.
-
-Once the [root] is found, the JIRI_ROOT environment variable is set to its
-location, and [root]/.jiri_root/bin/jiri is invoked.  That file contains the
-actual jiri binary.
-
-The point of the shim script is to make it easy to use the jiri tool with
-multiple [root] directories on your file system.  Keep in mind that when "jiri
-update" is run, the jiri tool itself is automatically updated along with all
-projects.  By using the shim script, you only need to remember to invoke the
-jiri tool from within the appropriate [root] directory, and the projects and
-tools under that [root] directory will be updated.
-
-The shim script is located at [root]/release/go/src/v.io/jiri/scripts/jiri
-
-2) Direct binary.  This is the jiri binary, containing all of the actual jiri
-tool logic.  The binary requires the JIRI_ROOT environment variable to point to
-the [root] directory.
-
-Note that if you have multiple [root] directories on your file system, you must
-remember to run the jiri binary corresponding to the setting of your JIRI_ROOT
-environment variable.  Things may fail if you mix things up, since the jiri
-binary is updated with each call to "jiri update", and you may encounter version
-mismatches between the jiri binary and the various metadata files or other
-logic.  This is the reason the shim script is recommended over running the
-binary directly.
-
-The binary is located at [root]/.jiri_root/bin/jiri
-`,
-}
-
-// TODO(toddw): Update the description of manifest files.
-var topicManifest = cmdline.Topic{
-	Name:  "manifest",
-	Short: "Description of manifest files",
-	Long: `
-Jiri manifests are revisioned and stored in a "manifest" repository, that is
-available locally in $JIRI_ROOT/.manifest. The manifest uses the following XML
-schema:
-
- <manifest>
-   <imports>
-     <import name="default"/>
-     ...
-   </imports>
-   <projects>
-     <project name="release.go.jiri"
-              path="release/go/src/v.io/jiri"
-              protocol="git"
-              name="https://vanadium.googlesource.com/release.go.jiri"
-              revision="HEAD"/>
-     ...
-   </projects>
-   <tools>
-     <tool name="jiri" package="v.io/jiri"/>
-     ...
-   </tools>
- </manifest>
-
-The <import> element can be used to share settings across multiple
-manifests. Import names are interpreted relative to the $JIRI_ROOT/.manifest/v2
-directory. Import cycles are not allowed and if a project or a tool is specified
-multiple times, the last specification takes effect. In particular, the elements
-<project name="foo" exclude="true"/> and <tool name="bar" exclude="true"/> can
-be used to exclude previously included projects and tools.
-
-The tool identifies which manifest to use using the following algorithm. If the
-$JIRI_ROOT/.local_manifest file exists, then it is used. Otherwise, the
-$JIRI_ROOT/.manifest/v2/<manifest>.xml file is used, where <manifest> is the
-value of the -manifest command-line flag, which defaults to "default".
-`,
-}
diff --git a/cmd/jiri/import_test.go b/cmd/jiri/import_test.go
index 7b13b97..6dd60c6 100644
--- a/cmd/jiri/import_test.go
+++ b/cmd/jiri/import_test.go
@@ -138,7 +138,7 @@
 	opts := gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf}
 	sh := gosh.NewShell(opts)
 	defer sh.Cleanup()
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
+	jiriTool := sh.BuildGoPkg("v.io/jiri/cmd/jiri")
 	for _, test := range tests {
 		if err := testImport(opts, jiriTool, test); err != nil {
 			t.Errorf("%v: %v", test.Args, err)
diff --git a/cmd/jiri/scripts/bootstrap_jiri b/cmd/jiri/scripts/bootstrap_jiri
index 8f00a2f..3cf0f2c 100755
--- a/cmd/jiri/scripts/bootstrap_jiri
+++ b/cmd/jiri/scripts/bootstrap_jiri
@@ -65,9 +65,9 @@
 
 # Go get the jiri source files, build the jiri binary, and copy the jiri shim
 # script from the sources.
-GOPATH="${tmp_dir}" go get -d v.io/jiri
-GOPATH="${tmp_dir}" go build -o "${bin_dir}/jiri" v.io/jiri
-cp "${tmp_dir}/src/v.io/jiri/scripts/jiri" "${scripts_dir}/jiri"
+GOPATH="${tmp_dir}" go get -d v.io/jiri/cmd/jiri
+GOPATH="${tmp_dir}" go build -o "${bin_dir}/jiri" v.io/jiri/cmd/jiri
+cp "${tmp_dir}/src/v.io/jiri/cmd/jiri/scripts/jiri" "${scripts_dir}/jiri"
 
 # Clean up the tmp_dir.
 rm -rf "${tmp_dir}"
diff --git a/cmd/jiri/snapshot.go b/cmd/jiri/snapshot.go
index 437cd81..21d572d 100644
--- a/cmd/jiri/snapshot.go
+++ b/cmd/jiri/snapshot.go
@@ -266,19 +266,18 @@
 	}
 
 	// Check that all labels exist.
-	failed := false
+	var notexist []string
 	for _, label := range args {
 		labelDir := filepath.Join(snapshotDir, "labels", label)
-		if _, err := jirix.NewSeq().Stat(labelDir); err != nil {
-			if !runutil.IsNotExist(err) {
-				return err
-			}
-			failed = true
-			fmt.Fprintf(jirix.Stderr(), "snapshot label %q not found", label)
+		switch _, err := jirix.NewSeq().Stat(labelDir); {
+		case runutil.IsNotExist(err):
+			notexist = append(notexist, label)
+		case err != nil:
+			return err
 		}
 	}
-	if failed {
-		return cmdline.ErrExitCode(2)
+	if len(notexist) > 0 {
+		return fmt.Errorf("snapshot labels %v not found", notexist)
 	}
 
 	// Print snapshots for all labels.
diff --git a/cmd/jiri/upgrade_test.go b/cmd/jiri/upgrade_test.go
index a7c5eab..d8913ed 100644
--- a/cmd/jiri/upgrade_test.go
+++ b/cmd/jiri/upgrade_test.go
@@ -201,7 +201,7 @@
 	opts := gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf}
 	sh := gosh.NewShell(opts)
 	defer sh.Cleanup()
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
+	jiriTool := sh.BuildGoPkg("v.io/jiri/cmd/jiri")
 	for _, test := range tests {
 		if err := testUpgrade(opts, jiriTool, test); err != nil {
 			t.Errorf("%v: %v", test.Args, err)
@@ -264,7 +264,7 @@
 	defer sh.Cleanup()
 	jiriRoot := sh.MakeTempDir()
 	sh.Pushd(jiriRoot)
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
+	jiriTool := sh.BuildGoPkg("v.io/jiri/cmd/jiri")
 	localData := `<manifest/>`
 	jiriData := `<manifest>
   <imports>
diff --git a/cmd/jiri/which_test.go b/cmd/jiri/which_test.go
index dbe46f1..a60655a 100644
--- a/cmd/jiri/which_test.go
+++ b/cmd/jiri/which_test.go
@@ -16,7 +16,7 @@
 	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf, PropagateChildOutput: true})
 	defer sh.Cleanup()
 
-	jiriBinary := sh.BuildGoPkg("v.io/jiri")
+	jiriBinary := sh.BuildGoPkg("v.io/jiri/cmd/jiri")
 	stdout, stderr := sh.Cmd(jiriBinary, []string{"which"}...).StdoutStderr()
 	if got, want := stdout, fmt.Sprintf("# binary\n%s\n", jiriBinary); got != want {
 		t.Errorf("stdout got %q, want %q", got, want)
diff --git a/contrib.go b/contrib.go
deleted file mode 100644
index 1fb31e6..0000000
--- a/contrib.go
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"encoding/xml"
-	"fmt"
-	"path/filepath"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
-
-	"v.io/jiri/collect"
-	"v.io/jiri/gitutil"
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/tool"
-	"v.io/x/lib/cmdline"
-	"v.io/x/lib/set"
-)
-
-const (
-	aliasesFileName = "aliases.v1.xml"
-)
-
-var (
-	countFlag   bool
-	aliasesFlag string
-)
-
-func init() {
-	cmdContributors.Flags.BoolVar(&countFlag, "n", false, "Show number of contributions.")
-	cmdContributors.Flags.StringVar(&aliasesFlag, "aliases", "", "Path to the aliases file.")
-}
-
-// cmdContributors represents the "jiri contributors" command.
-var cmdContributors = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runContributors),
-	Name:   "contributors",
-	Short:  "List project contributors",
-	Long: `
-Lists project contributors. Projects to consider can be specified as
-an argument. If no projects are specified, all projects in the current
-manifest are considered by default.
-`,
-	ArgsName: "<projects>",
-	ArgsLong: "<projects> is a list of projects to consider.",
-}
-
-type contributor struct {
-	count int
-	email string
-	name  string
-}
-
-var (
-	contributorRE = regexp.MustCompile("^(.*)\t(.*) <(.*)>$")
-)
-
-type aliasesSchema struct {
-	XMLName xml.Name      `xml:"aliases"`
-	Names   []nameSchema  `xml:"name"`
-	Emails  []emailSchema `xml:"email"`
-}
-
-type nameSchema struct {
-	Canonical string   `xml:"canonical"`
-	Aliases   []string `xml:"alias"`
-}
-
-type emailSchema struct {
-	Canonical string   `xml:"canonical"`
-	Aliases   []string `xml:"alias"`
-}
-
-type aliasMaps struct {
-	emails map[string]string
-	names  map[string]string
-}
-
-func canonicalize(aliases *aliasMaps, email, name string) (string, string) {
-	canonicalEmail, canonicalName := email, name
-	if email, ok := aliases.emails[email]; ok {
-		canonicalEmail = email
-	}
-	if name, ok := aliases.names[name]; ok {
-		canonicalName = name
-	}
-	return canonicalEmail, canonicalName
-}
-
-func loadAliases(jirix *jiri.X) (*aliasMaps, error) {
-	aliasesFile := aliasesFlag
-	if aliasesFile == "" {
-		dataDir, err := project.DataDirPath(jirix, tool.Name)
-		if err != nil {
-			return nil, err
-		}
-		aliasesFile = filepath.Join(dataDir, aliasesFileName)
-	}
-	bytes, err := jirix.NewSeq().ReadFile(aliasesFile)
-	if err != nil {
-		return nil, err
-	}
-	var data aliasesSchema
-	if err := xml.Unmarshal(bytes, &data); err != nil {
-		return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(bytes), err)
-	}
-	aliases := &aliasMaps{
-		emails: map[string]string{},
-		names:  map[string]string{},
-	}
-	for _, email := range data.Emails {
-		for _, alias := range email.Aliases {
-			aliases.emails[alias] = email.Canonical
-		}
-	}
-	for _, name := range data.Names {
-		for _, alias := range name.Aliases {
-			aliases.names[alias] = name.Canonical
-		}
-	}
-	return aliases, nil
-}
-
-func runContributors(jirix *jiri.X, args []string) error {
-	localProjects, err := project.LocalProjects(jirix, project.FastScan)
-	if err != nil {
-		return err
-	}
-	projectNames := map[string]struct{}{}
-	if len(args) != 0 {
-		projectNames = set.String.FromSlice(args)
-	} else {
-		for _, p := range localProjects {
-			projectNames[p.Name] = struct{}{}
-		}
-	}
-
-	aliases, err := loadAliases(jirix)
-	if err != nil {
-		return err
-	}
-	contributors := map[string]*contributor{}
-	for name, _ := range projectNames {
-		projects := localProjects.Find(name)
-		if len(projects) == 0 {
-			continue
-		}
-
-		for _, project := range projects {
-			if err := jirix.NewSeq().Chdir(project.Path).Done(); err != nil {
-				return err
-			}
-			switch project.Protocol {
-			case "git":
-				lines, err := listCommitters(jirix)
-				if err != nil {
-					return err
-				}
-				for _, line := range lines {
-					matches := contributorRE.FindStringSubmatch(line)
-					if got, want := len(matches), 4; got != want {
-						return fmt.Errorf("unexpected length of %v: got %v, want %v", matches, got, want)
-					}
-					count, err := strconv.Atoi(strings.TrimSpace(matches[1]))
-					if err != nil {
-						return fmt.Errorf("Atoi(%v) failed: %v", strings.TrimSpace(matches[1]), err)
-					}
-					c := &contributor{
-						count: count,
-						email: strings.TrimSpace(matches[3]),
-						name:  strings.TrimSpace(matches[2]),
-					}
-					if c.email == "jenkins.veyron@gmail.com" || c.email == "jenkins.veyron.rw@gmail.com" {
-						continue
-					}
-					c.email, c.name = canonicalize(aliases, c.email, c.name)
-					if existing, ok := contributors[c.name]; ok {
-						existing.count += c.count
-					} else {
-						contributors[c.name] = c
-					}
-				}
-			}
-		}
-	}
-	names := []string{}
-	for name, _ := range contributors {
-		names = append(names, name)
-	}
-	sort.Strings(names)
-	for _, name := range names {
-		c := contributors[name]
-		if countFlag {
-			fmt.Fprintf(jirix.Stdout(), "%4d ", c.count)
-		}
-		fmt.Fprintf(jirix.Stdout(), "%v <%v>\n", c.name, c.email)
-	}
-	return nil
-}
-
-func listCommitters(jirix *jiri.X) (_ []string, e error) {
-	branch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
-	if err != nil {
-		return nil, err
-	}
-	stashed, err := gitutil.New(jirix.NewSeq()).Stash()
-	if err != nil {
-		return nil, err
-	}
-	if stashed {
-		defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).StashPop() }, &e)
-	}
-	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch("master"); err != nil {
-		return nil, err
-	}
-	defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).CheckoutBranch(branch) }, &e)
-	return gitutil.New(jirix.NewSeq()).Committers()
-}
diff --git a/doc.go b/doc.go
deleted file mode 100644
index a6a93a5..0000000
--- a/doc.go
+++ /dev/null
@@ -1,907 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-
-/*
-Command jiri is a multi-purpose tool for multi-repo development.
-
-Usage:
-   jiri [flags] <command>
-
-The jiri commands are:
-   cl           Manage project changelists
-   contributors List project contributors
-   import       Adds imports to .jiri_manifest file
-   profile      Display information about installed profiles
-   project      Manage the jiri projects
-   rebuild      Rebuild all jiri tools
-   snapshot     Manage project snapshots
-   update       Update all jiri tools and projects
-   upgrade      Upgrade jiri to new-style manifests
-   which        Show path to the jiri tool
-   help         Display help for commands or topics
-
-The jiri additional help topics are:
-   filesystem  Description of jiri file system layout
-   manifest    Description of manifest files
-
-The jiri flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-The global flags are:
- -metadata=<just specify -metadata to activate>
-   Displays metadata for the program and exits.
- -time=false
-   Dump timing information to stderr before exiting the program.
-
-Jiri cl - Manage project changelists
-
-Manage project changelists.
-
-Usage:
-   jiri cl [flags] <command>
-
-The jiri cl commands are:
-   cleanup     Clean up changelists that have been merged
-   mail        Mail a changelist for review
-   new         Create a new local branch for a changelist
-   sync        Bring a changelist up to date
-
-The jiri cl flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri cl cleanup - Clean up changelists that have been merged
-
-Command "cleanup" checks that the given branches have been merged into the
-corresponding remote branch. If a branch differs from the corresponding remote
-branch, the command reports the difference and stops. Otherwise, it deletes the
-given branches.
-
-Usage:
-   jiri cl cleanup [flags] <branches>
-
-<branches> is a list of branches to cleanup.
-
-The jiri cl cleanup flags are:
- -f=false
-   Ignore unmerged changes.
- -remote-branch=master
-   Name of the remote branch the CL pertains to, without the leading "origin/".
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri cl mail - Mail a changelist for review
-
-Command "mail" squashes all commits of a local branch into a single "changelist"
-and mails this changelist to Gerrit as a single commit. First time the command
-is invoked, it generates a Change-Id for the changelist, which is appended to
-the commit message. Consecutive invocations of the command use the same
-Change-Id by default, informing Gerrit that the incomming commit is an update of
-an existing changelist.
-
-Usage:
-   jiri cl mail [flags]
-
-The jiri cl mail flags are:
- -autosubmit=false
-   Automatically submit the changelist when feasible.
- -cc=
-   Comma-seperated list of emails or LDAPs to cc.
- -check-uncommitted=true
-   Check that no uncommitted changes exist.
- -d=false
-   Send a draft changelist.
- -edit=true
-   Open an editor to edit the CL description.
- -host=
-   Gerrit host to use.  Defaults to gerrit host specified in manifest.
- -m=
-   CL description.
- -presubmit=all
-   The type of presubmit tests to run. Valid values: none,all.
- -r=
-   Comma-seperated list of emails or LDAPs to request review.
- -remote-branch=master
-   Name of the remote branch the CL pertains to, without the leading "origin/".
- -set-topic=true
-   Set Gerrit CL topic.
- -topic=
-   CL topic, defaults to <username>-<branchname>.
- -verify=true
-   Run pre-push git hooks.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri cl new - Create a new local branch for a changelist
-
-Command "new" creates a new local branch for a changelist. In particular, it
-forks a new branch with the given name from the current branch and records the
-relationship between the current branch and the new branch in the .jiri metadata
-directory. The information recorded in the .jiri metadata directory tracks
-dependencies between CLs and is used by the "jiri cl sync" and "jiri cl mail"
-commands.
-
-Usage:
-   jiri cl new [flags] <name>
-
-<name> is the changelist name.
-
-The jiri cl new flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri cl sync - Bring a changelist up to date
-
-Command "sync" brings the CL identified by the current branch up to date with
-the branch tracking the remote branch this CL pertains to. To do that, the
-command uses the information recorded in the .jiri metadata directory to
-identify the sequence of dependent CLs leading to the current branch. The
-command then iterates over this sequence bringing each of the CLs up to date
-with its ancestor. The end result of this process is that all CLs in the
-sequence are up to date with the branch that tracks the remote branch this CL
-pertains to.
-
-NOTE: It is possible that the command cannot automatically merge changes in an
-ancestor into its dependent. When that occurs, the command is aborted and prints
-instructions that need to be followed before the command can be retried.
-
-Usage:
-   jiri cl sync [flags]
-
-The jiri cl sync flags are:
- -remote-branch=master
-   Name of the remote branch the CL pertains to, without the leading "origin/".
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri contributors - List project contributors
-
-Lists project contributors. Projects to consider can be specified as an
-argument. If no projects are specified, all projects in the current manifest are
-considered by default.
-
-Usage:
-   jiri contributors [flags] <projects>
-
-<projects> is a list of projects to consider.
-
-The jiri contributors flags are:
- -aliases=
-   Path to the aliases file.
- -n=false
-   Show number of contributions.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri import
-
-Command "import" adds imports to the $JIRI_ROOT/.jiri_manifest file, which
-specifies manifest information for the jiri tool.  The file is created if it
-doesn't already exist, otherwise additional imports are added to the existing
-file.
-
-An <import> element is added to the manifest representing a remote manifest
-import.  The manifest file path is relative to the root directory of the remote
-import repository.
-
-Example:
-  $ jiri import myfile https://foo.com/bar.git
-
-Run "jiri help manifest" for details on manifests.
-
-Usage:
-   jiri import [flags] <manifest> <remote>
-
-<manifest> specifies the manifest file to use.
-
-<remote> specifies the remote manifest repository.
-
-The jiri import flags are:
- -name=
-   The name of the remote manifest project, used to disambiguate manifest
-   projects with the same remote.  Typically empty.
- -out=
-   The output file.  Uses $JIRI_ROOT/.jiri_manifest if unspecified.  Uses stdout
-   if set to "-".
- -overwrite=false
-   Write a new .jiri_manifest file with the given specification.  If it already
-   exists, the existing content will be ignored and the file will be
-   overwritten.
- -protocol=git
-   The version control protocol used by the remote manifest project.
- -remote-branch=master
-   The branch of the remote manifest project to track, without the leading
-   "origin/".
- -root=
-   Root to store the manifest project locally.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri profile - Display information about installed profiles
-
-Display information about installed profiles and their configuration.
-
-Usage:
-   jiri profile [flags] <command>
-
-The jiri profile commands are:
-   list        List available or installed profiles
-   env         Display profile environment variables
-   install     Install the given profiles
-   uninstall   Uninstall the given profiles
-   update      Install the latest default version of the given profiles
-   cleanup     Cleanup the locally installed profiles
-   available   List the available profiles
-
-The jiri profile flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri profile list - List available or installed profiles
-
-List available or installed profiles.
-
-Usage:
-   jiri profile list [flags] [<profiles>]
-
-<profiles> is a list of profiles to list, defaulting to all profiles if none are
-specifically requested.
-
-The jiri profile list flags are:
- -available=false
-   print the list of available profiles
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -info=
-   The following fields for use with --profile-info are available:
-   	SchemaVersion - the version of the profiles implementation.
-   	Target.InstallationDir - the installation directory of the requested profile.
-   	Target.CommandLineEnv - the environment variables specified via the command line when installing this profile target.
-   	Target.Env - the environment variables computed by the profile installation process for this target.
-   	Target.Command - a command that can be used to create this profile.
-   	Note: if no --target is specified then the requested field will be displayed for all targets.
-   	Profile.Description - description of the requested profile.
-   	Profile.Root - the root directory of the requested profile.
-   	Profile.Versions - the set of supported versions for this profile.
-   	Profile.DefaultVersion - the default version of the requested profile.
-   	Profile.LatestVersion - the latest version available for the requested profile.
-   	Note: if no profiles are specified then the requested field will be displayed for all profiles.
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -show-profiles-db=false
-   print out the profiles database file
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri profile env - Display profile environment variables
-
-List profile specific and target specific environment variables. If the
-requested environment variable name ends in = then only the value will be
-printed, otherwise both name and value are printed, i.e. GOPATH="foo" vs just
-"foo".
-
-If no environment variable names are requested then all will be printed in
-<name>=<val> format.
-
-Usage:
-   jiri profile env [flags] [<environment variable names>]
-
-[<environment variable names>] is an optional list of environment variables to
-display
-
-The jiri profile env flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri profile install - Install the given profiles
-
-Install the given profiles.
-
-Usage:
-   jiri profile install [flags] <profiles>
-
-<profiles> is a list of profiles to install.
-
-The jiri profile install flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -force=false
-   force install the profile even if it is already installed
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -profiles-dir=.jiri_root/profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri profile uninstall - Uninstall the given profiles
-
-Uninstall the given profiles.
-
-Usage:
-   jiri profile uninstall [flags] <profiles>
-
-<profiles> is a list of profiles to uninstall.
-
-The jiri profile uninstall flags are:
- -all-targets=false
-   apply to all targets for the specified profile(s)
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -profiles-dir=.jiri_root/profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri profile update - Install the latest default version of the given profiles
-
-Install the latest default version of the given profiles.
-
-Usage:
-   jiri profile update [flags] <profiles>
-
-<profiles> is a list of profiles to update, if omitted all profiles are updated.
-
-The jiri profile update flags are:
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -profiles-dir=.jiri_root/profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri profile cleanup - Cleanup the locally installed profiles
-
-Cleanup the locally installed profiles. This is generally required when
-recovering from earlier bugs or when preparing for a subsequent change to the
-profiles implementation.
-
-Usage:
-   jiri profile cleanup [flags] <profiles>
-
-<profiles> is a list of profiles to cleanup, if omitted all profiles are
-cleaned.
-
-The jiri profile cleanup flags are:
- -gc=false
-   uninstall profile targets that are older than the current default
- -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
-   specify the profiles database directory or file.
- -profiles-dir=.jiri_root/profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -rewrite-profiles-db=false
-   rewrite the profiles database to use the latest schema version
- -rm-all=false
-   remove profiles database and all profile generated output files.
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri profile available - List the available profiles
-
-List the available profiles.
-
-Usage:
-   jiri profile available [flags]
-
-The jiri profile available flags are:
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri project - Manage the jiri projects
-
-Manage the jiri projects.
-
-Usage:
-   jiri project [flags] <command>
-
-The jiri project commands are:
-   clean        Restore jiri projects to their pristine state
-   list         List existing jiri projects and branches
-   shell-prompt Print a succinct status of projects suitable for shell prompts
-   poll         Poll existing jiri projects
-
-The jiri project flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri project clean - Restore jiri projects to their pristine state
-
-Restore jiri projects back to their master branches and get rid of all the local
-branches and changes.
-
-Usage:
-   jiri project clean [flags] <project ...>
-
-<project ...> is a list of projects to clean up.
-
-The jiri project clean flags are:
- -branches=false
-   Delete all non-master branches.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri project list - List existing jiri projects and branches
-
-Inspect the local filesystem and list the existing projects and branches.
-
-Usage:
-   jiri project list [flags]
-
-The jiri project list flags are:
- -branches=false
-   Show project branches.
- -nopristine=false
-   If true, omit pristine projects, i.e. projects with a clean master branch and
-   no other branches.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri project shell-prompt - Print a succinct status of projects suitable for shell prompts
-
-Reports current branches of jiri projects (repositories) as well as an
-indication of each project's status:
-  *  indicates that a repository contains uncommitted changes
-  %  indicates that a repository contains untracked files
-
-Usage:
-   jiri project shell-prompt [flags]
-
-The jiri project shell-prompt flags are:
- -check-dirty=true
-   If false, don't check for uncommitted changes or untracked files. Setting
-   this option to false is dangerous: dirty master branches will not appear in
-   the output.
- -show-name=false
-   Show the name of the current repo.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri project poll - Poll existing jiri projects
-
-Poll jiri projects that can affect the outcome of the given tests and report
-whether any new changes in these projects exist. If no tests are specified, all
-projects are polled by default.
-
-Usage:
-   jiri project poll [flags] <test ...>
-
-<test ...> is a list of tests that determine what projects to poll.
-
-The jiri project poll flags are:
- -manifest=
-   Name of the project manifest.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri rebuild - Rebuild all jiri tools
-
-Rebuilds all jiri tools and installs the resulting binaries into
-$JIRI_ROOT/.jiri_root/bin. This is similar to "jiri update", but does not update
-any projects before building the tools. The set of tools to rebuild is described
-in the manifest.
-
-Run "jiri help manifest" for details on manifests.
-
-Usage:
-   jiri rebuild [flags]
-
-The jiri rebuild flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri snapshot - Manage project snapshots
-
-The "jiri snapshot" command can be used to manage project snapshots. In
-particular, it can be used to create new snapshots and to list existing
-snapshots.
-
-Usage:
-   jiri snapshot [flags] <command>
-
-The jiri snapshot commands are:
-   checkout    Checkout a project snapshot
-   create      Create a new project snapshot
-   list        List existing project snapshots
-
-The jiri snapshot flags are:
- -dir=
-   Directory where snapshot are stored.  Defaults to $JIRI_ROOT/.snapshot.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri snapshot checkout - Checkout a project snapshot
-
-The "jiri snapshot checkout <snapshot>" command restores local project state to
-the state in the given snapshot manifest.
-
-Usage:
-   jiri snapshot checkout [flags] <snapshot>
-
-<snapshot> is the snapshot manifest file.
-
-The jiri snapshot checkout flags are:
- -gc=false
-   Garbage collect obsolete repositories.
-
- -color=true
-   Use color to format output.
- -dir=
-   Directory where snapshot are stored.  Defaults to $JIRI_ROOT/.snapshot.
- -v=false
-   Print verbose output.
-
-Jiri snapshot create - Create a new project snapshot
-
-The "jiri snapshot create <label>" command captures the current project state in
-a manifest.  If the -push-remote flag is provided, the snapshot is committed and
-pushed upstream.
-
-Internally, snapshots are organized as follows:
-
- <snapshot-dir>/
-   labels/
-     <label1>/
-       <label1-snapshot1>
-       <label1-snapshot2>
-       ...
-     <label2>/
-       <label2-snapshot1>
-       <label2-snapshot2>
-       ...
-     <label3>/
-     ...
-   <label1> # a symlink to the latest <label1-snapshot*>
-   <label2> # a symlink to the latest <label2-snapshot*>
-   ...
-
-NOTE: Unlike the jiri tool commands, the above internal organization is not an
-API. It is an implementation and can change without notice.
-
-Usage:
-   jiri snapshot create [flags] <label>
-
-<label> is the snapshot label.
-
-The jiri snapshot create flags are:
- -push-remote=false
-   Commit and push snapshot upstream.
- -time-format=2006-01-02T15:04:05Z07:00
-   Time format for snapshot file name.
-
- -color=true
-   Use color to format output.
- -dir=
-   Directory where snapshot are stored.  Defaults to $JIRI_ROOT/.snapshot.
- -v=false
-   Print verbose output.
-
-Jiri snapshot list - List existing project snapshots
-
-The "snapshot list" command lists existing snapshots of the labels specified as
-command-line arguments. If no arguments are provided, the command lists
-snapshots for all known labels.
-
-Usage:
-   jiri snapshot list [flags] <label ...>
-
-<label ...> is a list of snapshot labels.
-
-The jiri snapshot list flags are:
- -color=true
-   Use color to format output.
- -dir=
-   Directory where snapshot are stored.  Defaults to $JIRI_ROOT/.snapshot.
- -v=false
-   Print verbose output.
-
-Jiri update - Update all jiri tools and projects
-
-Updates all projects, builds the latest version of all tools, and installs the
-resulting binaries into $JIRI_ROOT/.jiri_root/bin. The sequence in which the
-individual updates happen guarantees that we end up with a consistent set of
-tools and source code. The set of projects and tools to update is described in
-the manifest.
-
-Run "jiri help manifest" for details on manifests.
-
-Usage:
-   jiri update [flags]
-
-The jiri update flags are:
- -attempts=1
-   Number of attempts before failing.
- -gc=false
-   Garbage collect obsolete repositories.
- -manifest=
-   Name of the project manifest.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri upgrade - Upgrade jiri to new-style manifests
-
-Upgrades jiri to use new-style manifests.
-
-The old (deprecated) behavior only allowed a single manifest repository, located
-in $JIRI_ROOT/.manifest.  The initial manifest file is located as follows:
-  1) Use -manifest flag, if non-empty.  If it's empty...
-  2) Use $JIRI_ROOT/.local_manifest file.  If it doesn't exist...
-  3) Use $JIRI_ROOT/.manifest/v2/default.
-
-The new behavior allows multiple manifest repositories, by allowing imports to
-specify project attributes describing the remote repository.  The -manifest flag
-is no longer allowed to be set; the initial manifest file is always located in
-$JIRI_ROOT/.jiri_manifest.  The .local_manifest file is ignored.
-
-During the transition phase, both old and new behaviors are supported.  The jiri
-tool uses the existence of the $JIRI_ROOT/.jiri_manifest file as the signal; if
-it exists we run the new behavior, otherwise we run the old behavior.
-
-The new behavior includes a "jiri import" command, which writes or updates the
-.jiri_manifest file.  The new bootstrap procedure runs "jiri import", and it is
-intended as a regular command to add imports to your jiri environment.
-
-This upgrade command eases the transition by writing an initial .jiri_manifest
-file for you.  If you have an existing .local_manifest file, its contents will
-be incorporated into the new .jiri_manifest file, and it will be renamed to
-.local_manifest.BACKUP.  The -revert flag deletes the .jiri_manifest file, and
-restores the .local_manifest file.
-
-Usage:
-   jiri upgrade [flags] <kind>
-
-<kind> specifies the kind of upgrade, one of "v23" or "fuchsia".
-
-The jiri upgrade flags are:
- -revert=false
-   Revert the upgrade by deleting the $JIRI_ROOT/.jiri_manifest file.
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri which - Show path to the jiri tool
-
-Which behaves similarly to the unix commandline tool.  It is useful in
-determining whether the jiri binary is being run directly, or run via the jiri
-shim script.
-
-If the binary is being run directly, the output looks like this:
-
-  # binary
-  /path/to/binary/jiri
-
-If the script is being run, the output looks like this:
-
-  # script
-  /path/to/script/jiri
-
-Usage:
-   jiri which [flags]
-
-The jiri which flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri help - Display help for commands or topics
-
-Help with no args displays the usage of the parent command.
-
-Help with args displays the usage of the specified sub-command or help topic.
-
-"help ..." recursively displays help for all commands and topics.
-
-Usage:
-   jiri help [flags] [command/topic ...]
-
-[command/topic ...] optionally identifies a specific sub-command or help topic.
-
-The jiri help flags are:
- -style=compact
-   The formatting style for help output:
-      compact   - Good for compact cmdline output.
-      full      - Good for cmdline output, shows all global flags.
-      godoc     - Good for godoc processing.
-      shortonly - Only output short description.
-   Override the default by setting the CMDLINE_STYLE environment variable.
- -width=<terminal width>
-   Format output to this target width in runes, or unlimited if width < 0.
-   Defaults to the terminal width if available.  Override the default by setting
-   the CMDLINE_WIDTH environment variable.
-
-Jiri filesystem - Description of jiri file system layout
-
-All data managed by the jiri tool is located in the file system under a root
-directory, colloquially called the jiri root directory.  The file system layout
-looks like this:
-
- [root]                              # root directory (name picked by user)
- [root]/.jiri_root                   # root metadata directory
- [root]/.jiri_root/bin               # contains tool binaries (jiri, etc.)
- [root]/.jiri_root/update_history    # contains history of update snapshots
- [root]/.manifest                    # contains jiri manifests
- [root]/[project1]                   # project directory (name picked by user)
- [root]/[project1]/.jiri             # project metadata directory
- [root]/[project1]/.jiri/metadata.v2 # project metadata file
- [root]/[project1]/.jiri/<<cls>>     # project per-cl metadata directories
- [root]/[project1]/<<files>>         # project files
- [root]/[project2]...
-
-The [root] and [projectN] directory names are picked by the user.  The <<cls>>
-are named via jiri cl new, and the <<files>> are named as the user adds files
-and directories to their project.  All other names above have special meaning to
-the jiri tool, and cannot be changed; you must ensure your path names don't
-collide with these special names.
-
-There are two ways to run the jiri tool:
-
-1) Shim script (recommended approach).  This is a shell script that looks for
-the [root] directory.  If the JIRI_ROOT environment variable is set, that is
-assumed to be the [root] directory.  Otherwise the script looks for the
-.jiri_root directory, starting in the current working directory and walking up
-the directory chain.  The search is terminated successfully when the .jiri_root
-directory is found; it fails after it reaches the root of the file system.  Thus
-the shim must be invoked from the [root] directory or one of its subdirectories.
-
-Once the [root] is found, the JIRI_ROOT environment variable is set to its
-location, and [root]/.jiri_root/bin/jiri is invoked.  That file contains the
-actual jiri binary.
-
-The point of the shim script is to make it easy to use the jiri tool with
-multiple [root] directories on your file system.  Keep in mind that when "jiri
-update" is run, the jiri tool itself is automatically updated along with all
-projects.  By using the shim script, you only need to remember to invoke the
-jiri tool from within the appropriate [root] directory, and the projects and
-tools under that [root] directory will be updated.
-
-The shim script is located at [root]/release/go/src/v.io/jiri/scripts/jiri
-
-2) Direct binary.  This is the jiri binary, containing all of the actual jiri
-tool logic.  The binary requires the JIRI_ROOT environment variable to point to
-the [root] directory.
-
-Note that if you have multiple [root] directories on your file system, you must
-remember to run the jiri binary corresponding to the setting of your JIRI_ROOT
-environment variable.  Things may fail if you mix things up, since the jiri
-binary is updated with each call to "jiri update", and you may encounter version
-mismatches between the jiri binary and the various metadata files or other
-logic.  This is the reason the shim script is recommended over running the
-binary directly.
-
-The binary is located at [root]/.jiri_root/bin/jiri
-
-Jiri manifest - Description of manifest files
-
-Jiri manifests are revisioned and stored in a "manifest" repository, that is
-available locally in $JIRI_ROOT/.manifest. The manifest uses the following XML
-schema:
-
- <manifest>
-   <imports>
-     <import name="default"/>
-     ...
-   </imports>
-   <projects>
-     <project name="release.go.jiri"
-              path="release/go/src/v.io/jiri"
-              protocol="git"
-              name="https://vanadium.googlesource.com/release.go.jiri"
-              revision="HEAD"/>
-     ...
-   </projects>
-   <tools>
-     <tool name="jiri" package="v.io/jiri"/>
-     ...
-   </tools>
- </manifest>
-
-The <import> element can be used to share settings across multiple manifests.
-Import names are interpreted relative to the $JIRI_ROOT/.manifest/v2 directory.
-Import cycles are not allowed and if a project or a tool is specified multiple
-times, the last specification takes effect. In particular, the elements <project
-name="foo" exclude="true"/> and <tool name="bar" exclude="true"/> can be used to
-exclude previously included projects and tools.
-
-The tool identifies which manifest to use using the following algorithm. If the
-$JIRI_ROOT/.local_manifest file exists, then it is used. Otherwise, the
-$JIRI_ROOT/.manifest/v2/<manifest>.xml file is used, where <manifest> is the
-value of the -manifest command-line flag, which defaults to "default".
-*/
-package main
diff --git a/gitutil/git.go b/gitutil/git.go
index c6a6675..985017f 100644
--- a/gitutil/git.go
+++ b/gitutil/git.go
@@ -212,7 +212,7 @@
 // Committers returns a list of committers for the current repository
 // along with the number of their commits.
 func (g *Git) Committers() ([]string, error) {
-	out, err := g.runOutputNoDryRun("shortlog", "-s", "-n", "-e")
+	out, err := g.runOutput("shortlog", "-s", "-n", "-e")
 	if err != nil {
 		return nil, err
 	}
@@ -227,7 +227,7 @@
 		args = append(args, "^"+base)
 	}
 	args = append(args, "--")
-	out, err := g.runOutputNoDryRun(args...)
+	out, err := g.runOutput(args...)
 	if err != nil {
 		return 0, err
 	}
@@ -260,7 +260,7 @@
 
 // CurrentBranchName returns the name of the current branch.
 func (g *Git) CurrentBranchName() (string, error) {
-	out, err := g.runOutputNoDryRun("rev-parse", "--abbrev-ref", "HEAD")
+	out, err := g.runOutput("rev-parse", "--abbrev-ref", "HEAD")
 	if err != nil {
 		return "", err
 	}
@@ -277,7 +277,7 @@
 
 // CurrentRevisionOfBranch returns the current revision of the given branch.
 func (g *Git) CurrentRevisionOfBranch(branch string) (string, error) {
-	out, err := g.runOutputNoDryRun("rev-parse", branch)
+	out, err := g.runOutput("rev-parse", branch)
 	if err != nil {
 		return "", err
 	}
@@ -336,7 +336,7 @@
 // (e.g. --merged).
 func (g *Git) GetBranches(args ...string) ([]string, string, error) {
 	args = append([]string{"branch"}, args...)
-	out, err := g.runOutputNoDryRun(args...)
+	out, err := g.runOutput(args...)
 	if err != nil {
 		return nil, "", err
 	}
@@ -555,7 +555,7 @@
 // RemoteUrl gets the url of the remote with the given name.
 func (g *Git) RemoteUrl(name string) (string, error) {
 	configKey := fmt.Sprintf("remote.%s.url", name)
-	out, err := g.runOutputNoDryRun("config", "--get", configKey)
+	out, err := g.runOutput("config", "--get", configKey)
 	if err != nil {
 		return "", err
 	}
@@ -630,7 +630,7 @@
 // TopLevel returns the top level path of the current repository.
 func (g *Git) TopLevel() (string, error) {
 	// TODO(sadovsky): If g.rootDir is set, perhaps simply return that?
-	out, err := g.runOutputNoDryRun("rev-parse", "--show-toplevel")
+	out, err := g.runOutput("rev-parse", "--show-toplevel")
 	if err != nil {
 		return "", err
 	}
@@ -657,7 +657,7 @@
 
 // Version returns the major and minor git version.
 func (g *Git) Version() (int, int, error) {
-	out, err := g.runOutputNoDryRun("version")
+	out, err := g.runOutput("version")
 	if err != nil {
 		return 0, 0, err
 	}
@@ -709,21 +709,6 @@
 	return trimOutput(stdout.String()), nil
 }
 
-func (g *Git) runOutputNoDryRun(args ...string) ([]string, error) {
-	var stdout, stderr bytes.Buffer
-	fn := func(s runutil.Sequence) runutil.Sequence {
-		dryrun, _ := s.RunOpts()
-		if dryrun {
-			return s.DryRun(false).Verbose(true).Capture(&stdout, &stderr)
-		}
-		return s.Capture(&stdout, &stderr)
-	}
-	if err := g.runWithFn(fn, args...); err != nil {
-		return nil, Error(stdout.String(), stderr.String(), args...)
-	}
-	return trimOutput(stdout.String()), nil
-}
-
 func (g *Git) runInteractive(args ...string) error {
 	var stderr bytes.Buffer
 	// In order for the editing to work correctly with
diff --git a/import.go b/import.go
deleted file mode 100644
index 7636f13..0000000
--- a/import.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"os"
-
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/runutil"
-	"v.io/x/lib/cmdline"
-)
-
-var (
-	// Flags for configuring project attributes for remote imports.
-	flagImportName, flagImportProtocol, flagImportRemoteBranch, flagImportRoot string
-	// Flags for controlling the behavior of the command.
-	flagImportOverwrite bool
-	flagImportOut       string
-)
-
-func init() {
-	cmdImport.Flags.StringVar(&flagImportName, "name", "", `The name of the remote manifest project, used to disambiguate manifest projects with the same remote.  Typically empty.`)
-	cmdImport.Flags.StringVar(&flagImportProtocol, "protocol", "git", `The version control protocol used by the remote manifest project.`)
-	cmdImport.Flags.StringVar(&flagImportRemoteBranch, "remote-branch", "master", `The branch of the remote manifest project to track, without the leading "origin/".`)
-	cmdImport.Flags.StringVar(&flagImportRoot, "root", "", `Root to store the manifest project locally.`)
-
-	cmdImport.Flags.BoolVar(&flagImportOverwrite, "overwrite", false, `Write a new .jiri_manifest file with the given specification.  If it already exists, the existing content will be ignored and the file will be overwritten.`)
-	cmdImport.Flags.StringVar(&flagImportOut, "out", "", `The output file.  Uses $JIRI_ROOT/.jiri_manifest if unspecified.  Uses stdout if set to "-".`)
-}
-
-var cmdImport = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runImport),
-	Name:   "import",
-	Short:  "Adds imports to .jiri_manifest file",
-	Long: `
-Command "import" adds imports to the $JIRI_ROOT/.jiri_manifest file, which
-specifies manifest information for the jiri tool.  The file is created if it
-doesn't already exist, otherwise additional imports are added to the existing
-file.
-
-An <import> element is added to the manifest representing a remote manifest
-import.  The manifest file path is relative to the root directory of the remote
-import repository.
-
-Example:
-  $ jiri import myfile https://foo.com/bar.git
-
-Run "jiri help manifest" for details on manifests.
-`,
-	ArgsName: "<manifest> <remote>",
-	ArgsLong: `
-<manifest> specifies the manifest file to use.
-
-<remote> specifies the remote manifest repository.
-`,
-}
-
-func runImport(jirix *jiri.X, args []string) error {
-	if len(args) != 2 {
-		return jirix.UsageErrorf("wrong number of arguments")
-	}
-	// Initialize manifest.
-	var manifest *project.Manifest
-	if !flagImportOverwrite {
-		m, err := project.ManifestFromFile(jirix, jirix.JiriManifestFile())
-		if err != nil && !runutil.IsNotExist(err) {
-			return err
-		}
-		manifest = m
-	}
-	if manifest == nil {
-		manifest = &project.Manifest{}
-	}
-	// There's not much error checking when writing the .jiri_manifest file;
-	// errors will be reported when "jiri update" is run.
-	manifest.Imports = append(manifest.Imports, project.Import{
-		Manifest:     args[0],
-		Name:         flagImportName,
-		Protocol:     flagImportProtocol,
-		Remote:       args[1],
-		RemoteBranch: flagImportRemoteBranch,
-		Root:         flagImportRoot,
-	})
-	// Write output to stdout or file.
-	outFile := flagImportOut
-	if outFile == "" {
-		outFile = jirix.JiriManifestFile()
-	}
-	if outFile == "-" {
-		bytes, err := manifest.ToBytes()
-		if err != nil {
-			return err
-		}
-		_, err = os.Stdout.Write(bytes)
-		return err
-	}
-	return manifest.ToFile(jirix, outFile)
-}
diff --git a/import_test.go b/import_test.go
deleted file mode 100644
index 7b13b97..0000000
--- a/import_test.go
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-	"testing"
-
-	"v.io/jiri/jiri"
-	"v.io/x/lib/gosh"
-)
-
-type importTestCase struct {
-	Args           []string
-	Filename       string
-	Exist, Want    string
-	Stdout, Stderr string
-}
-
-func TestImport(t *testing.T) {
-	tests := []importTestCase{
-		{
-			Stderr: `wrong number of arguments`,
-		},
-		{
-			Args:   []string{"a"},
-			Stderr: `wrong number of arguments`,
-		},
-		{
-			Args:   []string{"a", "b", "c"},
-			Stderr: `wrong number of arguments`,
-		},
-		// Remote imports, default append behavior
-		{
-			Args: []string{"-name=name", "-remote-branch=remotebranch", "-root=root", "foo", "https://github.com/new.git"},
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" name="name" remote="https://github.com/new.git" remotebranch="remotebranch" root="root"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"foo", "https://github.com/new.git"},
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args:     []string{"-out=file", "foo", "https://github.com/new.git"},
-			Filename: `file`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"-out=-", "foo", "https://github.com/new.git"},
-			Stdout: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"foo", "https://github.com/new.git"},
-			Exist: `<manifest>
-  <imports>
-    <import manifest="bar" remote="https://github.com/orig.git"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="bar" remote="https://github.com/orig.git"/>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		// Remote imports, explicit overwrite behavior
-		{
-			Args: []string{"-overwrite", "foo", "https://github.com/new.git"},
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args:     []string{"-overwrite", "-out=file", "foo", "https://github.com/new.git"},
-			Filename: `file`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"-overwrite", "-out=-", "foo", "https://github.com/new.git"},
-			Stdout: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"-overwrite", "foo", "https://github.com/new.git"},
-			Exist: `<manifest>
-  <imports>
-    <import manifest="bar" remote="https://github.com/orig.git"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="foo" remote="https://github.com/new.git"/>
-  </imports>
-</manifest>
-`,
-		},
-	}
-	opts := gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf}
-	sh := gosh.NewShell(opts)
-	defer sh.Cleanup()
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
-	for _, test := range tests {
-		if err := testImport(opts, jiriTool, test); err != nil {
-			t.Errorf("%v: %v", test.Args, err)
-		}
-	}
-}
-
-func testImport(opts gosh.Opts, jiriTool string, test importTestCase) error {
-	sh := gosh.NewShell(opts)
-	defer sh.Cleanup()
-	tmpDir := sh.MakeTempDir()
-	jiriRoot := filepath.Join(tmpDir, "root")
-	if err := os.Mkdir(jiriRoot, 0755); err != nil {
-		return err
-	}
-	sh.Pushd(jiriRoot)
-	filename := test.Filename
-	if filename == "" {
-		filename = ".jiri_manifest"
-	}
-	// Set up manfile for the local file import tests.  It should exist in both
-	// the tmpDir (for ../manfile tests) and jiriRoot.
-	for _, dir := range []string{tmpDir, jiriRoot} {
-		if err := ioutil.WriteFile(filepath.Join(dir, "manfile"), nil, 0644); err != nil {
-			return err
-		}
-	}
-	// Set up an existing file if it was specified.
-	if test.Exist != "" {
-		if err := ioutil.WriteFile(filename, []byte(test.Exist), 0644); err != nil {
-			return err
-		}
-	}
-	// Run import and check the error.
-	sh.Vars[jiri.RootEnv] = jiriRoot
-	cmd := sh.Cmd(jiriTool, append([]string{"import"}, test.Args...)...)
-	if test.Stderr != "" {
-		cmd.ExitErrorIsOk = true
-	}
-	stdout, stderr := cmd.StdoutStderr()
-	if got, want := stdout, test.Stdout; !strings.Contains(got, want) || (got != "" && want == "") {
-		return fmt.Errorf("stdout got %q, want substr %q", got, want)
-	}
-	if got, want := stderr, test.Stderr; !strings.Contains(got, want) || (got != "" && want == "") {
-		return fmt.Errorf("stderr got %q, want substr %q", got, want)
-	}
-	// Make sure the right file is generated.
-	if test.Want != "" {
-		data, err := ioutil.ReadFile(filename)
-		if err != nil {
-			return err
-		}
-		if got, want := string(data), test.Want; got != want {
-			return fmt.Errorf("GOT\n%s\nWANT\n%s", got, want)
-		}
-	}
-	return nil
-}
diff --git a/profile.go b/profile.go
deleted file mode 100644
index aa78d3f..0000000
--- a/profile.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"v.io/jiri/jiri"
-	"v.io/jiri/profiles/profilescmdline"
-	"v.io/x/lib/cmdline"
-)
-
-var cmdProfile = &cmdline.Command{
-	Name:  "profile",
-	Short: "Display information about installed profiles",
-	Long:  "Display information about installed profiles and their configuration.",
-}
-
-func init() {
-	profilescmdline.RegisterReaderCommands(cmdProfile, jiri.ProfilesDBDir)
-	profilescmdline.RegisterManagementCommands(cmdProfile, true, "", jiri.ProfilesDBDir, jiri.ProfilesRootDir)
-}
diff --git a/profiles/profilescmdline/internal/i1/doc.go b/profiles/profilescmdline/internal/i1/doc.go
new file mode 100644
index 0000000..07f6370
--- /dev/null
+++ b/profiles/profilescmdline/internal/i1/doc.go
@@ -0,0 +1,239 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Profiles are used to manage external sofware dependencies and offer a balance
+between providing no support at all and a full blown package manager. Profiles
+can be built natively as well as being cross compiled. A profile is a named
+collection of software required for a given system component or application.
+Current example profiles include 'syncbase' which consists of the leveldb and
+snappy libraries or 'android' which consists of all of the android components
+and downloads needed to build android applications. Profiles are built for
+specific targets.
+
+Targets
+
+Profiles generally refer to uncompiled source code that needs to be compiled for
+a specific "target". Targets hence represent compiled code and consist of:
+
+1. An 'architecture' that refers to the CPU to be generate code for
+
+2. An 'operating system' that refers to the operating system to generate code
+for
+
+3. A lexicographically orderd set of supported versions, one of which is
+designated as the default.
+
+4. An 'environment' which is a set of environment variables to use when
+compiling the profile
+
+Targets thus provide the basic support needed for cross compilation.
+
+Targets are versioned and multiple versions may be installed and used
+simultaneously. Versions are ordered lexicographically and each target specifies
+a 'default' version to be used when a specific version is not explicitly
+requested. A request to 'upgrade' the profile will result in the installation of
+the default version of the targets currently installed if that default version
+is not already installed.
+
+The Supported Commands
+
+Profiles, or more correctly, targets for specific profiles may be installed or
+removed. When doing so, the name of the profile is required, but the other
+components of the target are optional and will default to the values of the
+system that the commands are run on (so-called native builds) and the default
+version for that target. Once a profile is installed it may be referred to by
+its tag for subsequent removals.
+
+The are also update and cleanup commands. Update installs the default version of
+the requested profile or for all profiles for the already installed targets.
+Cleanup will uninstall targets whose version is older than the default.
+
+Finally, there are commands to list the available and installed profiles and to
+access the environment variables specified and stored in each profile
+installation and a command (recreate) to generate a list of commands that can be
+run to recreate the currently installed profiles.
+
+The Profiles Database
+
+The profiles packages manages a database that tracks the installed profiles and
+their configurations. Other command line tools and packages are expected to read
+information about the currently installed profiles from this database via the
+profiles package. The profile command line tools support displaying the database
+(via the list command) or for specifying an alternate version of the file (via
+the -profiles-db flag) which is generally useful for debugging.
+
+Adding Profiles
+
+Profiles are intended to be provided as go packages that register themselves
+with the profile command line tools via the *v.io/jiri/profiles* package. They
+must implement the interfaces defined by that package and be imported (e.g.
+import _ "myprofile") by the command line tools that are to use them.
+
+Usage:
+   jiri profile-i1 [flags] <command>
+
+The jiri profile-i1 commands are:
+   install     Install the given profiles
+   uninstall   Uninstall the given profiles
+   update      Install the latest default version of the given profiles
+   cleanup     Cleanup the locally installed profiles
+   available   List the available profiles
+   help        Display help for commands or topics
+
+The jiri profile-i1 flags are:
+ -color=true
+   Use color to format output.
+ -v=false
+   Print verbose output.
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -time=false
+   Dump timing information to stderr before exiting the program.
+
+Jiri profile-i1 install - Install the given profiles
+
+Install the given profiles.
+
+Usage:
+   jiri profile-i1 install [flags] <profiles>
+
+<profiles> is a list of profiles to install.
+
+The jiri profile-i1 install flags are:
+ -env=
+   specify an environment variable in the form: <var>=[<val>],...
+ -force=false
+   force install the profile even if it is already installed
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+
+ -color=true
+   Use color to format output.
+ -v=false
+   Print verbose output.
+
+Jiri profile-i1 uninstall - Uninstall the given profiles
+
+Uninstall the given profiles.
+
+Usage:
+   jiri profile-i1 uninstall [flags] <profiles>
+
+<profiles> is a list of profiles to uninstall.
+
+The jiri profile-i1 uninstall flags are:
+ -all-targets=false
+   apply to all targets for the specified profile(s)
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i1 update - Install the latest default version of the given profiles
+
+Install the latest default version of the given profiles.
+
+Usage:
+   jiri profile-i1 update [flags] <profiles>
+
+<profiles> is a list of profiles to update, if omitted all profiles are updated.
+
+The jiri profile-i1 update flags are:
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i1 cleanup - Cleanup the locally installed profiles
+
+Cleanup the locally installed profiles. This is generally required when
+recovering from earlier bugs or when preparing for a subsequent change to the
+profiles implementation.
+
+Usage:
+   jiri profile-i1 cleanup [flags] <profiles>
+
+<profiles> is a list of profiles to cleanup, if omitted all profiles are
+cleaned.
+
+The jiri profile-i1 cleanup flags are:
+ -gc=false
+   uninstall profile targets that are older than the current default
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -rewrite-profiles-db=false
+   rewrite the profiles database to use the latest schema version
+ -rm-all=false
+   remove profiles database and all profile generated output files.
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i1 available - List the available profiles
+
+List the available profiles.
+
+Usage:
+   jiri profile-i1 available [flags]
+
+The jiri profile-i1 available flags are:
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i1 help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   jiri profile-i1 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The jiri profile-i1 help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact   - Good for compact cmdline output.
+      full      - Good for cmdline output, shows all global flags.
+      godoc     - Good for godoc processing.
+      shortonly - Only output short description.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/profiles/profilescmdline/internal/i2/doc.go b/profiles/profilescmdline/internal/i2/doc.go
new file mode 100644
index 0000000..b202251
--- /dev/null
+++ b/profiles/profilescmdline/internal/i2/doc.go
@@ -0,0 +1,239 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Profiles are used to manage external sofware dependencies and offer a balance
+between providing no support at all and a full blown package manager. Profiles
+can be built natively as well as being cross compiled. A profile is a named
+collection of software required for a given system component or application.
+Current example profiles include 'syncbase' which consists of the leveldb and
+snappy libraries or 'android' which consists of all of the android components
+and downloads needed to build android applications. Profiles are built for
+specific targets.
+
+Targets
+
+Profiles generally refer to uncompiled source code that needs to be compiled for
+a specific "target". Targets hence represent compiled code and consist of:
+
+1. An 'architecture' that refers to the CPU to be generate code for
+
+2. An 'operating system' that refers to the operating system to generate code
+for
+
+3. A lexicographically orderd set of supported versions, one of which is
+designated as the default.
+
+4. An 'environment' which is a set of environment variables to use when
+compiling the profile
+
+Targets thus provide the basic support needed for cross compilation.
+
+Targets are versioned and multiple versions may be installed and used
+simultaneously. Versions are ordered lexicographically and each target specifies
+a 'default' version to be used when a specific version is not explicitly
+requested. A request to 'upgrade' the profile will result in the installation of
+the default version of the targets currently installed if that default version
+is not already installed.
+
+The Supported Commands
+
+Profiles, or more correctly, targets for specific profiles may be installed or
+removed. When doing so, the name of the profile is required, but the other
+components of the target are optional and will default to the values of the
+system that the commands are run on (so-called native builds) and the default
+version for that target. Once a profile is installed it may be referred to by
+its tag for subsequent removals.
+
+The are also update and cleanup commands. Update installs the default version of
+the requested profile or for all profiles for the already installed targets.
+Cleanup will uninstall targets whose version is older than the default.
+
+Finally, there are commands to list the available and installed profiles and to
+access the environment variables specified and stored in each profile
+installation and a command (recreate) to generate a list of commands that can be
+run to recreate the currently installed profiles.
+
+The Profiles Database
+
+The profiles packages manages a database that tracks the installed profiles and
+their configurations. Other command line tools and packages are expected to read
+information about the currently installed profiles from this database via the
+profiles package. The profile command line tools support displaying the database
+(via the list command) or for specifying an alternate version of the file (via
+the -profiles-db flag) which is generally useful for debugging.
+
+Adding Profiles
+
+Profiles are intended to be provided as go packages that register themselves
+with the profile command line tools via the *v.io/jiri/profiles* package. They
+must implement the interfaces defined by that package and be imported (e.g.
+import _ "myprofile") by the command line tools that are to use them.
+
+Usage:
+   jiri profile-i2 [flags] <command>
+
+The jiri profile-i2 commands are:
+   install     Install the given profiles
+   uninstall   Uninstall the given profiles
+   update      Install the latest default version of the given profiles
+   cleanup     Cleanup the locally installed profiles
+   available   List the available profiles
+   help        Display help for commands or topics
+
+The jiri profile-i2 flags are:
+ -color=true
+   Use color to format output.
+ -v=false
+   Print verbose output.
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -time=false
+   Dump timing information to stderr before exiting the program.
+
+Jiri profile-i2 install - Install the given profiles
+
+Install the given profiles.
+
+Usage:
+   jiri profile-i2 install [flags] <profiles>
+
+<profiles> is a list of profiles to install.
+
+The jiri profile-i2 install flags are:
+ -env=
+   specify an environment variable in the form: <var>=[<val>],...
+ -force=false
+   force install the profile even if it is already installed
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+
+ -color=true
+   Use color to format output.
+ -v=false
+   Print verbose output.
+
+Jiri profile-i2 uninstall - Uninstall the given profiles
+
+Uninstall the given profiles.
+
+Usage:
+   jiri profile-i2 uninstall [flags] <profiles>
+
+<profiles> is a list of profiles to uninstall.
+
+The jiri profile-i2 uninstall flags are:
+ -all-targets=false
+   apply to all targets for the specified profile(s)
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i2 update - Install the latest default version of the given profiles
+
+Install the latest default version of the given profiles.
+
+Usage:
+   jiri profile-i2 update [flags] <profiles>
+
+<profiles> is a list of profiles to update, if omitted all profiles are updated.
+
+The jiri profile-i2 update flags are:
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i2 cleanup - Cleanup the locally installed profiles
+
+Cleanup the locally installed profiles. This is generally required when
+recovering from earlier bugs or when preparing for a subsequent change to the
+profiles implementation.
+
+Usage:
+   jiri profile-i2 cleanup [flags] <profiles>
+
+<profiles> is a list of profiles to cleanup, if omitted all profiles are
+cleaned.
+
+The jiri profile-i2 cleanup flags are:
+ -gc=false
+   uninstall profile targets that are older than the current default
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   specify the profiles database directory or file.
+ -profiles-dir=.jiri_root/profiles
+   the directory, relative to JIRI_ROOT, that profiles are installed in
+ -rewrite-profiles-db=false
+   rewrite the profiles database to use the latest schema version
+ -rm-all=false
+   remove profiles database and all profile generated output files.
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i2 available - List the available profiles
+
+List the available profiles.
+
+Usage:
+   jiri profile-i2 available [flags]
+
+The jiri profile-i2 available flags are:
+ -v=false
+   print more detailed information
+
+ -color=true
+   Use color to format output.
+
+Jiri profile-i2 help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   jiri profile-i2 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The jiri profile-i2 help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact   - Good for compact cmdline output.
+      full      - Good for cmdline output, shows all global flags.
+      godoc     - Good for godoc processing.
+      shortonly - Only output short description.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/profiles/profilescmdline/manager_test.go b/profiles/profilescmdline/manager_test.go
index 38cd5d7..b00eccc 100644
--- a/profiles/profilescmdline/manager_test.go
+++ b/profiles/profilescmdline/manager_test.go
@@ -76,7 +76,7 @@
 	buildInstallersOnce.Do(func() {
 		sh := newShell(t)
 		prefix := "v.io/jiri/profiles/profilescmdline/internal/"
-		sh.BuildGoPkg("v.io/jiri", "-o", "jiri")
+		sh.BuildGoPkg("v.io/jiri/cmd/jiri", "-o", "jiri")
 		sh.BuildGoPkg(prefix+"i1", "-o", "jiri-profile-i1")
 		sh.BuildGoPkg(prefix+"i2", "-o", "jiri-profile-i2")
 		buildInstallersBindir = sh.Opts.BinDir
@@ -87,7 +87,7 @@
 func buildJiri(t *testing.T) string {
 	buildJiriOnce.Do(func() {
 		sh := newShell(t)
-		sh.BuildGoPkg("v.io/jiri", "-o", "jiri")
+		sh.BuildGoPkg("v.io/jiri/cmd/jiri", "-o", "jiri")
 		buildJiriBindir = sh.Opts.BinDir
 	})
 	return buildJiriBindir
diff --git a/project.go b/project.go
deleted file mode 100644
index ea94798..0000000
--- a/project.go
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"path/filepath"
-	"sort"
-	"strings"
-
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/tool"
-	"v.io/jiri/util"
-	"v.io/x/lib/cmdline"
-	"v.io/x/lib/set"
-)
-
-var (
-	branchesFlag        bool
-	cleanupBranchesFlag bool
-	noPristineFlag      bool
-	checkDirtyFlag      bool
-	showNameFlag        bool
-)
-
-func init() {
-	cmdProjectClean.Flags.BoolVar(&cleanupBranchesFlag, "branches", false, "Delete all non-master branches.")
-	cmdProjectList.Flags.BoolVar(&branchesFlag, "branches", false, "Show project branches.")
-	cmdProjectList.Flags.BoolVar(&noPristineFlag, "nopristine", false, "If true, omit pristine projects, i.e. projects with a clean master branch and no other branches.")
-	cmdProjectShellPrompt.Flags.BoolVar(&checkDirtyFlag, "check-dirty", true, "If false, don't check for uncommitted changes or untracked files. Setting this option to false is dangerous: dirty master branches will not appear in the output.")
-	cmdProjectShellPrompt.Flags.BoolVar(&showNameFlag, "show-name", false, "Show the name of the current repo.")
-
-	tool.InitializeProjectFlags(&cmdProjectPoll.Flags)
-
-}
-
-// cmdProject represents the "jiri project" command.
-var cmdProject = &cmdline.Command{
-	Name:     "project",
-	Short:    "Manage the jiri projects",
-	Long:     "Manage the jiri projects.",
-	Children: []*cmdline.Command{cmdProjectClean, cmdProjectList, cmdProjectShellPrompt, cmdProjectPoll},
-}
-
-// cmdProjectClean represents the "jiri project clean" command.
-var cmdProjectClean = &cmdline.Command{
-	Runner:   jiri.RunnerFunc(runProjectClean),
-	Name:     "clean",
-	Short:    "Restore jiri projects to their pristine state",
-	Long:     "Restore jiri projects back to their master branches and get rid of all the local branches and changes.",
-	ArgsName: "<project ...>",
-	ArgsLong: "<project ...> is a list of projects to clean up.",
-}
-
-func runProjectClean(jirix *jiri.X, args []string) (e error) {
-	localProjects, err := project.LocalProjects(jirix, project.FullScan)
-	if err != nil {
-		return err
-	}
-	var projects project.Projects
-	if len(args) > 0 {
-		for _, arg := range args {
-			p, err := localProjects.FindUnique(arg)
-			if err != nil {
-				fmt.Fprintf(jirix.Stderr(), "Error finding local project %q: %v.\n", p.Name, err)
-			} else {
-				projects[p.Key()] = p
-			}
-		}
-	} else {
-		projects = localProjects
-	}
-	if err := project.CleanupProjects(jirix, projects, cleanupBranchesFlag); err != nil {
-		return err
-	}
-	return nil
-}
-
-// cmdProjectList represents the "jiri project list" command.
-var cmdProjectList = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runProjectList),
-	Name:   "list",
-	Short:  "List existing jiri projects and branches",
-	Long:   "Inspect the local filesystem and list the existing projects and branches.",
-}
-
-// runProjectList generates a listing of local projects.
-func runProjectList(jirix *jiri.X, _ []string) error {
-	states, err := project.GetProjectStates(jirix, noPristineFlag)
-	if err != nil {
-		return err
-	}
-	var keys project.ProjectKeys
-	for key := range states {
-		keys = append(keys, key)
-	}
-	sort.Sort(keys)
-
-	for _, key := range keys {
-		state := states[key]
-		if noPristineFlag {
-			pristine := len(state.Branches) == 1 && state.CurrentBranch == "master" && !state.HasUncommitted && !state.HasUntracked
-			if pristine {
-				continue
-			}
-		}
-		fmt.Fprintf(jirix.Stdout(), "name=%q remote=%q path=%q\n", state.Project.Name, state.Project.Remote, state.Project.Path)
-		if branchesFlag {
-			for _, branch := range state.Branches {
-				s := "  "
-				if branch.Name == state.CurrentBranch {
-					s += "* "
-				}
-				s += branch.Name
-				if branch.HasGerritMessage {
-					s += " (exported to gerrit)"
-				}
-				fmt.Fprintf(jirix.Stdout(), "%v\n", s)
-			}
-		}
-	}
-	return nil
-}
-
-// cmdProjectShellPrompt represents the "jiri project shell-prompt" command.
-var cmdProjectShellPrompt = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runProjectShellPrompt),
-	Name:   "shell-prompt",
-	Short:  "Print a succinct status of projects suitable for shell prompts",
-	Long: `
-Reports current branches of jiri projects (repositories) as well as an
-indication of each project's status:
-  *  indicates that a repository contains uncommitted changes
-  %  indicates that a repository contains untracked files
-`,
-}
-
-func runProjectShellPrompt(jirix *jiri.X, args []string) error {
-	states, err := project.GetProjectStates(jirix, checkDirtyFlag)
-	if err != nil {
-		return err
-	}
-	var keys project.ProjectKeys
-	for key := range states {
-		keys = append(keys, key)
-	}
-	sort.Sort(keys)
-
-	// Get the key of the current project.
-	currentProjectKey, err := project.CurrentProjectKey(jirix)
-	if err != nil {
-		return err
-	}
-	var statuses []string
-	for _, key := range keys {
-		state := states[key]
-		status := ""
-		if checkDirtyFlag {
-			if state.HasUncommitted {
-				status += "*"
-			}
-			if state.HasUntracked {
-				status += "%"
-			}
-		}
-		short := state.CurrentBranch + status
-		long := filepath.Base(states[key].Project.Name) + ":" + short
-		if key == currentProjectKey {
-			if showNameFlag {
-				statuses = append([]string{long}, statuses...)
-			} else {
-				statuses = append([]string{short}, statuses...)
-			}
-		} else {
-			pristine := state.CurrentBranch == "master"
-			if checkDirtyFlag {
-				pristine = pristine && !state.HasUncommitted && !state.HasUntracked
-			}
-			if !pristine {
-				statuses = append(statuses, long)
-			}
-		}
-	}
-	fmt.Println(strings.Join(statuses, ","))
-	return nil
-}
-
-// cmdProjectPoll represents the "jiri project poll" command.
-var cmdProjectPoll = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runProjectPoll),
-	Name:   "poll",
-	Short:  "Poll existing jiri projects",
-	Long: `
-Poll jiri projects that can affect the outcome of the given tests
-and report whether any new changes in these projects exist. If no
-tests are specified, all projects are polled by default.
-`,
-	ArgsName: "<test ...>",
-	ArgsLong: "<test ...> is a list of tests that determine what projects to poll.",
-}
-
-// runProjectPoll generates a description of changes that exist
-// remotely but do not exist locally.
-func runProjectPoll(jirix *jiri.X, args []string) error {
-	projectSet := map[string]struct{}{}
-	if len(args) > 0 {
-		config, err := util.LoadConfig(jirix)
-		if err != nil {
-			return err
-		}
-		// Compute a map from tests to projects that can change the
-		// outcome of the test.
-		testProjects := map[string][]string{}
-		for _, project := range config.Projects() {
-			for _, test := range config.ProjectTests([]string{project}) {
-				testProjects[test] = append(testProjects[test], project)
-			}
-		}
-		for _, arg := range args {
-			projects, ok := testProjects[arg]
-			if !ok {
-				return fmt.Errorf("failed to find any projects for test %q", arg)
-			}
-			set.String.Union(projectSet, set.String.FromSlice(projects))
-		}
-	}
-	update, err := project.PollProjects(jirix, projectSet)
-	if err != nil {
-		return err
-	}
-
-	// Remove projects with empty changes.
-	for project := range update {
-		if changes := update[project]; len(changes) == 0 {
-			delete(update, project)
-		}
-	}
-
-	// Print update if it is not empty.
-	if len(update) > 0 {
-		bytes, err := json.MarshalIndent(update, "", "  ")
-		if err != nil {
-			return fmt.Errorf("MarshalIndent() failed: %v", err)
-		}
-		fmt.Fprintf(jirix.Stdout(), "%s\n", bytes)
-	}
-	return nil
-}
diff --git a/rebuild.go b/rebuild.go
deleted file mode 100644
index dbf30bc..0000000
--- a/rebuild.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-
-	"v.io/jiri/collect"
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/x/lib/cmdline"
-)
-
-// cmdRebuild represents the "jiri rebuild" command.
-var cmdRebuild = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runRebuild),
-	Name:   "rebuild",
-	Short:  "Rebuild all jiri tools",
-	Long: `
-Rebuilds all jiri tools and installs the resulting binaries into
-$JIRI_ROOT/.jiri_root/bin. This is similar to "jiri update", but does not update
-any projects before building the tools. The set of tools to rebuild is described
-in the manifest.
-
-Run "jiri help manifest" for details on manifests.
-`,
-}
-
-func runRebuild(jirix *jiri.X, args []string) (e error) {
-	projects, tools, err := project.LoadManifest(jirix)
-	if err != nil {
-		return err
-	}
-
-	// Create a temporary directory in which tools will be built.
-	tmpDir, err := jirix.NewSeq().TempDir("", "tmp-jiri-rebuild")
-	if err != nil {
-		return fmt.Errorf("TempDir() failed: %v", err)
-	}
-
-	// Make sure we cleanup the temp directory.
-	defer collect.Error(func() error { return jirix.NewSeq().RemoveAll(tmpDir).Done() }, &e)
-
-	// Paranoid sanity checking.
-	if _, ok := tools[project.JiriName]; !ok {
-		return fmt.Errorf("tool %q not found", project.JiriName)
-	}
-
-	// Build and install tools.
-	if err := project.BuildTools(jirix, projects, tools, tmpDir); err != nil {
-		return err
-	}
-	return project.InstallTools(jirix, tmpDir)
-}
diff --git a/runutil/.api b/runutil/.api
index e24cb58..4f9083e 100644
--- a/runutil/.api
+++ b/runutil/.api
@@ -20,7 +20,6 @@
 pkg runutil, method (Sequence) Create(string) (*os.File, error)
 pkg runutil, method (Sequence) Dir(string) Sequence
 pkg runutil, method (Sequence) Done() error
-pkg runutil, method (Sequence) DryRun(bool) Sequence
 pkg runutil, method (Sequence) Env(map[string]string) Sequence
 pkg runutil, method (Sequence) Error() error
 pkg runutil, method (Sequence) Fprintf(io.Writer, string, ...interface{}) Sequence
diff --git a/runutil/executor.go b/runutil/executor.go
index 69fd5a9..c8cebce 100644
--- a/runutil/executor.go
+++ b/runutil/executor.go
@@ -25,7 +25,6 @@
 type opts struct {
 	color   bool
 	dir     string
-	dryRun  bool
 	env     map[string]string
 	stdin   io.Reader
 	stdout  io.Writer
@@ -38,7 +37,7 @@
 	opts   opts
 }
 
-func newExecutor(env map[string]string, stdin io.Reader, stdout, stderr io.Writer, color, dryRun, verbose bool) *executor {
+func newExecutor(env map[string]string, stdin io.Reader, stdout, stderr io.Writer, color, verbose bool) *executor {
 	if color {
 		term := os.Getenv("TERM")
 		switch term {
@@ -50,7 +49,6 @@
 		indent: 0,
 		opts: opts{
 			color:   color,
-			dryRun:  dryRun,
 			env:     env,
 			stdin:   stdin,
 			stdout:  stdout,
@@ -115,28 +113,8 @@
 }
 
 // call executes the given Go standard library function,
-// encapsulated as a closure, respecting the "dry run" option.
+// encapsulated as a closure.
 func (e *executor) call(fn func() error, format string, args ...interface{}) error {
-	if opts := e.opts; opts.dryRun {
-		opts.verbose = true
-		return e.function(opts, func() error { return nil }, format, args...)
-	}
-	return e.function(e.opts, fn, format, args...)
-}
-
-// alwaysRun executes the given Go standard library function, encapsulated as a
-// closure, but translating "dry run" into "verbose" for this particular
-// command so that the command can execute and thus allow subsequent
-// commands to complete. It is generally used for testing/making files/directories
-// that affect subsequent behaviour.
-func (e *executor) alwaysRun(fn func() error, format string, args ...interface{}) error {
-	if opts := e.opts; opts.dryRun {
-		// Disable the dry run option as this function has no effect and
-		// doing so results in more informative "dry run" output.
-		opts.dryRun = false
-		opts.verbose = true
-		return e.function(opts, fn, format, args...)
-	}
 	return e.function(e.opts, fn, format, args...)
 }
 
@@ -168,7 +146,7 @@
 	command.Stdout = opts.stdout
 	command.Stderr = opts.stderr
 	command.Env = envvar.MapToSlice(opts.env)
-	if opts.verbose || opts.dryRun {
+	if opts.verbose {
 		args := []string{}
 		for _, arg := range command.Args {
 			// Quote any arguments that contain '"', ''', '|', or ' '.
@@ -180,10 +158,6 @@
 		}
 		e.printf(e.opts.stdout, strings.Replace(strings.Join(args, " "), "%", "%%", -1))
 	}
-	if opts.dryRun {
-		e.printf(e.opts.stdout, "OK")
-		return nil, nil
-	}
 
 	if wait {
 		if timeout == 0 {
diff --git a/runutil/executor_test.go b/runutil/executor_test.go
index c02ca43..634a647 100644
--- a/runutil/executor_test.go
+++ b/runutil/executor_test.go
@@ -40,7 +40,7 @@
 
 func TestCommandOK(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	if err := e.run(forever, e.opts, "go", "run", "./testdata/ok_hello.go"); err != nil {
 		t.Fatalf(`Command("go run ./testdata/ok_hello.go") failed: %v`, err)
 	}
@@ -51,7 +51,7 @@
 
 func TestCommandFail(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	if err := e.run(forever, e.opts, "go", "run", "./testdata/fail_hello.go"); err == nil {
 		t.Fatalf(`Command("go run ./testdata/fail_hello.go") did not fail when it should`)
 	}
@@ -62,7 +62,7 @@
 
 func TestCommandWithOptsOK(t *testing.T) {
 	var cmdOut, runOut bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, true)
 	opts := e.opts
 	opts.stdout = &cmdOut
 	if err := e.run(forever, opts, "go", "run", "./testdata/ok_hello.go"); err != nil {
@@ -78,7 +78,7 @@
 
 func TestCommandWithOptsFail(t *testing.T) {
 	var cmdOut, runOut bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, true)
 	opts := e.opts
 	opts.stdout = &cmdOut
 	if err := e.run(forever, opts, "go", "run", "./testdata/fail_hello.go"); err == nil {
@@ -94,7 +94,7 @@
 
 func TestTimedCommandOK(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	if err := e.run(10*time.Second, e.opts, "go", "run", "./testdata/fast_hello.go"); err != nil {
 		t.Fatalf(`TimedCommand("go run ./testdata/fast_hello.go") failed: %v`, err)
 	}
@@ -105,7 +105,7 @@
 
 func TestTimedCommandFail(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	bin, err := buildTestProgram(e, "slow_hello")
 	if bin != "" {
 		defer os.RemoveAll(filepath.Dir(bin))
@@ -125,7 +125,7 @@
 
 func TestTimedCommandWithOptsOK(t *testing.T) {
 	var cmdOut, runOut bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, true)
 	opts := e.opts
 	opts.stdout = &cmdOut
 	if err := e.run(10*time.Second, opts, "go", "run", "./testdata/fast_hello.go"); err != nil {
@@ -141,7 +141,7 @@
 
 func TestTimedCommandWithOptsFail(t *testing.T) {
 	var cmdOut, runOut bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &runOut, ioutil.Discard, false, true)
 	bin, err := buildTestProgram(e, "slow_hello")
 	if bin != "" {
 		defer os.RemoveAll(filepath.Dir(bin))
@@ -166,7 +166,7 @@
 
 func TestFunctionOK(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	fn := func() error {
 		cmd := exec.Command("go", "run", "./testdata/ok_hello.go")
 		cmd.Stdout = &out
@@ -182,7 +182,7 @@
 
 func TestFunctionFail(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	fn := func() error {
 		cmd := exec.Command("go", "run", "./testdata/fail_hello.go")
 		cmd.Stdout = &out
@@ -201,7 +201,7 @@
 
 func TestFunctionWithOptsOK(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, false)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false)
 	opts := e.opts
 	opts.verbose = true
 	fn := func() error {
@@ -222,7 +222,7 @@
 
 func TestFunctionWithOptsFail(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, false)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false)
 	opts := e.opts
 	opts.verbose = true
 	fn := func() error {
@@ -243,7 +243,7 @@
 
 func TestOutput(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	e.output(e.opts, []string{"hello", "world"})
 	if got, want := removeTimestamps(t, &out), ">> hello\n>> world\n"; got != want {
 		t.Fatalf("unexpected output:\ngot\n%v\nwant\n%v", got, want)
@@ -252,7 +252,7 @@
 
 func TestOutputWithOpts(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, false)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false)
 	opts := e.opts
 	opts.verbose = true
 	e.output(opts, []string{"hello", "world"})
@@ -263,7 +263,7 @@
 
 func TestNested(t *testing.T) {
 	var out bytes.Buffer
-	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, false, true)
+	e := newExecutor(nil, os.Stdin, &out, ioutil.Discard, false, true)
 	fn := func() error {
 		e.output(e.opts, []string{"hello", "world"})
 		return nil
diff --git a/runutil/sequence.go b/runutil/sequence.go
index 12d5014..3a930ae 100644
--- a/runutil/sequence.go
+++ b/runutil/sequence.go
@@ -94,7 +94,6 @@
 	defaultStdout, defaultStderr io.Writer
 	dirs                         []string
 	verbosity                    *bool
-	dryRun                       *bool
 	cmdDir                       string
 	timeout                      time.Duration
 	serializedWriterLock         sync.Mutex
@@ -110,7 +109,7 @@
 	}
 	s := Sequence{
 		&sequence{
-			r:            newExecutor(env, stdin, stdout, stderr, color, dryRun, verbose),
+			r:            newExecutor(env, stdin, stdout, stderr, color, verbose),
 			defaultStdin: stdin,
 		},
 	}
@@ -122,7 +121,7 @@
 // create this sequence.
 func (s Sequence) RunOpts() (dryRun bool, verbose bool) {
 	opts := s.getOpts()
-	return opts.dryRun, opts.verbose
+	return false, opts.verbose
 }
 
 // Capture arranges for the next call to Run or Last to write its stdout and
@@ -202,17 +201,6 @@
 	return s
 }
 
-// DryRun arranges for the next call to Run, Call, Start or Last to use the
-// specified dry run value. This will be cleared and not used for any calls
-// to Run, Call or Last beyond the next one.
-func (s Sequence) DryRun(dryRun bool) Sequence {
-	if s.err != nil {
-		return s
-	}
-	s.dryRun = &dryRun
-	return s
-}
-
 // internal getOpts that doesn't override stdin, stdout, stderr
 func (s Sequence) getOpts() opts {
 	var opts opts
@@ -334,7 +322,7 @@
 // reset all state except s.err
 func (s Sequence) reset() {
 	s.stdin, s.stdout, s.stderr, s.env = nil, nil, nil, nil
-	s.opts, s.verbosity, s.dryRun = nil, nil, nil
+	s.opts, s.verbosity = nil, nil
 	s.cmdDir = ""
 	s.reading = false
 	s.timeout = 0
@@ -421,9 +409,6 @@
 		if s.verbosity != nil {
 			opts.verbose = *s.verbosity
 		}
-		if s.dryRun != nil {
-			opts.dryRun = *s.dryRun
-		}
 		opts.dir = s.cmdDir
 		s.setOpts(opts)
 		if h != nil {
@@ -485,9 +470,6 @@
 	if s.verbosity != nil {
 		opts.verbose = *s.verbosity
 	}
-	if s.dryRun != nil {
-		opts.dryRun = *s.dryRun
-	}
 	opts.dir = s.cmdDir
 	s.setOpts(opts)
 	if h != nil {
@@ -640,7 +622,7 @@
 	if len(s.dirs) > 0 {
 		cwd := s.dirs[0]
 		s.dirs = nil
-		err := s.r.alwaysRun(func() error {
+		err := s.r.call(func() error {
 			return os.Chdir(cwd)
 		}, fmt.Sprintf("sequence done popd %q", cwd))
 		if err != nil {
@@ -668,7 +650,7 @@
 		return s
 	}
 	s.dirs = append(s.dirs, cwd)
-	err = s.r.alwaysRun(func() error {
+	err = s.r.call(func() error {
 		return os.Chdir(dir)
 	}, fmt.Sprintf("pushd %q", dir))
 	s.setError(err, "Pushd("+dir+")")
@@ -688,7 +670,7 @@
 	}
 	last := s.dirs[len(s.dirs)-1]
 	s.dirs = s.dirs[:len(s.dirs)-1]
-	err := s.r.alwaysRun(func() error {
+	err := s.r.call(func() error {
 		return os.Chdir(last)
 	}, fmt.Sprintf("popd %q", last))
 	s.setError(err, "Popd() -> "+last)
@@ -696,12 +678,12 @@
 }
 
 // Chdir is a wrapper around os.Chdir that handles options such as
-// "verbose" or "dry run".
+// "verbose".
 func (s Sequence) Chdir(dir string) Sequence {
 	if s.err != nil {
 		return s
 	}
-	err := s.r.alwaysRun(func() error {
+	err := s.r.call(func() error {
 		return os.Chdir(dir)
 	}, fmt.Sprintf("cd %q", dir))
 	s.setError(err, "Chdir("+dir+")")
@@ -710,7 +692,7 @@
 }
 
 // Chmod is a wrapper around os.Chmod that handles options such as
-// "verbose" or "dry run".
+// "verbose".
 func (s Sequence) Chmod(dir string, mode os.FileMode) Sequence {
 	if s.err != nil {
 		return s
@@ -722,7 +704,7 @@
 }
 
 // MkdirAll is a wrapper around os.MkdirAll that handles options such
-// as "verbose" or "dry run".
+// as "verbose".
 func (s Sequence) MkdirAll(dir string, mode os.FileMode) Sequence {
 	if s.err != nil {
 		return s
@@ -733,7 +715,7 @@
 }
 
 // RemoveAll is a wrapper around os.RemoveAll that handles options
-// such as "verbose" or "dry run".
+// such as "verbose".
 func (s Sequence) RemoveAll(dir string) Sequence {
 	if s.err != nil {
 		return s
@@ -744,7 +726,7 @@
 }
 
 // Remove is a wrapper around os.Remove that handles options
-// such as "verbose" or "dry run".
+// such as "verbose".
 func (s Sequence) Remove(file string) Sequence {
 	if s.err != nil {
 		return s
@@ -755,7 +737,7 @@
 }
 
 // Rename is a wrapper around os.Rename that handles options such as
-// "verbose" or "dry run".
+// "verbose".
 func (s Sequence) Rename(src, dst string) Sequence {
 	if s.err != nil {
 		return s
@@ -785,7 +767,7 @@
 }
 
 // Symlink is a wrapper around os.Symlink that handles options such as
-// "verbose" or "dry run".
+// "verbose".
 func (s Sequence) Symlink(src, dst string) Sequence {
 	if s.err != nil {
 		return s
@@ -796,7 +778,7 @@
 }
 
 // Open is a wrapper around os.Open that handles options such as
-// "verbose" or "dry run". Open is a terminating function.
+// "verbose". Open is a terminating function.
 func (s Sequence) Open(name string) (f *os.File, err error) {
 	if s.err != nil {
 		return nil, s.Done()
@@ -811,7 +793,7 @@
 }
 
 // OpenFile is a wrapper around os.OpenFile that handles options such as
-// "verbose" or "dry run". OpenFile is a terminating function.
+// "verbose". OpenFile is a terminating function.
 func (s Sequence) OpenFile(name string, flag int, perm os.FileMode) (f *os.File, err error) {
 	if s.err != nil {
 		return nil, s.Done()
@@ -826,7 +808,7 @@
 }
 
 // Create is a wrapper around os.Create that handles options such as "verbose"
-// or "dry run". Create is a terminating function.
+//. Create is a terminating function.
 func (s Sequence) Create(name string) (f *os.File, err error) {
 	if s.err != nil {
 		return nil, s.Done()
@@ -842,12 +824,12 @@
 }
 
 // ReadDir is a wrapper around ioutil.ReadDir that handles options
-// such as "verbose" or "dry run". ReadDir is a terminating function.
+// such as "verbose". ReadDir is a terminating function.
 func (s Sequence) ReadDir(dirname string) (fi []os.FileInfo, err error) {
 	if s.err != nil {
 		return nil, s.Done()
 	}
-	s.r.alwaysRun(func() error {
+	s.r.call(func() error {
 		fi, err = ioutil.ReadDir(dirname)
 		return err
 	}, fmt.Sprintf("ls %q", dirname))
@@ -857,13 +839,13 @@
 }
 
 // ReadFile is a wrapper around ioutil.ReadFile that handles options
-// such as "verbose" or "dry run". ReadFile is a terminating function.
+// such as "verbose". ReadFile is a terminating function.
 func (s Sequence) ReadFile(filename string) (bytes []byte, err error) {
 
 	if s.err != nil {
 		return nil, s.Done()
 	}
-	s.r.alwaysRun(func() error {
+	s.r.call(func() error {
 		bytes, err = ioutil.ReadFile(filename)
 		return err
 	}, fmt.Sprintf("read %q", filename))
@@ -873,7 +855,7 @@
 }
 
 // WriteFile is a wrapper around ioutil.WriteFile that handles options
-// such as "verbose" or "dry run".
+// such as "verbose".
 func (s Sequence) WriteFile(filename string, data []byte, perm os.FileMode) Sequence {
 	if s.err != nil {
 		return s
@@ -901,12 +883,12 @@
 }
 
 // Stat is a wrapper around os.Stat that handles options such as
-// "verbose" or "dry run". Stat is a terminating function.
+// "verbose". Stat is a terminating function.
 func (s Sequence) Stat(name string) (fi os.FileInfo, err error) {
 	if s.err != nil {
 		return nil, s.Done()
 	}
-	s.r.alwaysRun(func() error {
+	s.r.call(func() error {
 		fi, err = os.Stat(name)
 		return err
 	}, fmt.Sprintf("stat %q", name))
@@ -916,12 +898,12 @@
 }
 
 // Lstat is a wrapper around os.Lstat that handles options such as
-// "verbose" or "dry run". Lstat is a terminating function.
+// "verbose". Lstat is a terminating function.
 func (s Sequence) Lstat(name string) (fi os.FileInfo, err error) {
 	if s.err != nil {
 		return nil, s.Done()
 	}
-	s.r.alwaysRun(func() error {
+	s.r.call(func() error {
 		fi, err = os.Lstat(name)
 		return err
 	}, fmt.Sprintf("lstat %q", name))
@@ -931,12 +913,12 @@
 }
 
 // Readlink is a wrapper around os.Readlink that handles options such as
-// "verbose" or "dry run". Lstat is a terminating function.
+// "verbose". Lstat is a terminating function.
 func (s Sequence) Readlink(name string) (link string, err error) {
 	if s.err != nil {
 		return "", s.Done()
 	}
-	s.r.alwaysRun(func() error {
+	s.r.call(func() error {
 		link, err = os.Readlink(name)
 		return err
 	}, fmt.Sprintf("readlink %q", name))
@@ -946,7 +928,7 @@
 }
 
 // TempDir is a wrapper around ioutil.TempDir that handles options
-// such as "verbose" or "dry run". TempDir is a terminating function.
+// such as "verbose". TempDir is a terminating function.
 func (s Sequence) TempDir(dir, prefix string) (tmpDir string, err error) {
 	if s.err != nil {
 		return "", s.Done()
@@ -965,7 +947,7 @@
 }
 
 // TempFile is a wrapper around ioutil.TempFile that handles options
-// such as "verbose" or "dry run".
+// such as "verbose".
 func (s Sequence) TempFile(dir, prefix string) (f *os.File, err error) {
 	if s.err != nil {
 		return nil, s.Done()
@@ -991,7 +973,7 @@
 	}
 	var fileInfo os.FileInfo
 	var err error
-	err = s.r.alwaysRun(func() error {
+	err = s.r.call(func() error {
 		fileInfo, err = os.Stat(dirname)
 		return err
 	}, fmt.Sprintf("isdir %q", dirname))
@@ -1014,7 +996,7 @@
 	}
 	var fileInfo os.FileInfo
 	var err error
-	err = s.r.alwaysRun(func() error {
+	err = s.r.call(func() error {
 		fileInfo, err = os.Stat(file)
 		return err
 	}, fmt.Sprintf("isfile %q", file))
diff --git a/runutil/sequence_test.go b/runutil/sequence_test.go
index 806f3c7..3bb7421 100644
--- a/runutil/sequence_test.go
+++ b/runutil/sequence_test.go
@@ -358,24 +358,6 @@
 		}
 		stdout.Reset()
 	}
-	for _, dryRun := range []bool{false, true} {
-		err := seq.DryRun(dryRun).Last("sh", "-c", "echo hello")
-		if err != nil {
-			t.Fatal(err)
-		}
-		out := sanitizeTimestampsAndPaths(stdout.String())
-		want := `[hh:mm:ss.xx] >> sh -c "echo hello"
-[hh:mm:ss.xx] >> OK
-`
-		if !dryRun {
-			want += `hello
-`
-		}
-		if got, want := out, want; got != want {
-			t.Errorf("dryRun: %t, got %v, want %v", dryRun, got, want)
-		}
-		stdout.Reset()
-	}
 }
 
 func TestSequenceOutputOnError(t *testing.T) {
diff --git a/snapshot.go b/snapshot.go
deleted file mode 100644
index 21d572d..0000000
--- a/snapshot.go
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"time"
-
-	"v.io/jiri/collect"
-	"v.io/jiri/gitutil"
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/runutil"
-	"v.io/x/lib/cmdline"
-)
-
-const (
-	defaultSnapshotDir = ".snapshot"
-)
-
-var (
-	pushRemoteFlag  bool
-	snapshotDirFlag string
-	snapshotGcFlag  bool
-	timeFormatFlag  string
-)
-
-func init() {
-	cmdSnapshot.Flags.StringVar(&snapshotDirFlag, "dir", "", "Directory where snapshot are stored.  Defaults to $JIRI_ROOT/.snapshot.")
-	cmdSnapshotCheckout.Flags.BoolVar(&snapshotGcFlag, "gc", false, "Garbage collect obsolete repositories.")
-	cmdSnapshotCreate.Flags.BoolVar(&pushRemoteFlag, "push-remote", false, "Commit and push snapshot upstream.")
-	cmdSnapshotCreate.Flags.StringVar(&timeFormatFlag, "time-format", time.RFC3339, "Time format for snapshot file name.")
-}
-
-var cmdSnapshot = &cmdline.Command{
-	Name:  "snapshot",
-	Short: "Manage project snapshots",
-	Long: `
-The "jiri snapshot" command can be used to manage project snapshots.
-In particular, it can be used to create new snapshots and to list
-existing snapshots.
-`,
-	Children: []*cmdline.Command{cmdSnapshotCheckout, cmdSnapshotCreate, cmdSnapshotList},
-}
-
-// cmdSnapshotCreate represents the "jiri snapshot create" command.
-var cmdSnapshotCreate = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runSnapshotCreate),
-	Name:   "create",
-	Short:  "Create a new project snapshot",
-	Long: `
-The "jiri snapshot create <label>" command captures the current project state
-in a manifest.  If the -push-remote flag is provided, the snapshot is committed
-and pushed upstream.
-
-Internally, snapshots are organized as follows:
-
- <snapshot-dir>/
-   labels/
-     <label1>/
-       <label1-snapshot1>
-       <label1-snapshot2>
-       ...
-     <label2>/
-       <label2-snapshot1>
-       <label2-snapshot2>
-       ...
-     <label3>/
-     ...
-   <label1> # a symlink to the latest <label1-snapshot*>
-   <label2> # a symlink to the latest <label2-snapshot*>
-   ...
-
-NOTE: Unlike the jiri tool commands, the above internal organization
-is not an API. It is an implementation and can change without notice.
-`,
-	ArgsName: "<label>",
-	ArgsLong: "<label> is the snapshot label.",
-}
-
-func runSnapshotCreate(jirix *jiri.X, args []string) error {
-	if len(args) != 1 {
-		return jirix.UsageErrorf("unexpected number of arguments")
-	}
-	label := args[0]
-	snapshotDir, err := getSnapshotDir(jirix)
-	if err != nil {
-		return err
-	}
-	snapshotFile := filepath.Join(snapshotDir, "labels", label, time.Now().Format(timeFormatFlag))
-
-	if !pushRemoteFlag {
-		// No git operations necessary.  Just create the snapshot file.
-		return createSnapshot(jirix, snapshotDir, snapshotFile, label)
-	}
-
-	// Attempt to create a snapshot on a clean master branch.  If snapshot
-	// creation fails, return to the state we were in before.
-	createFn := func() error {
-		git := gitutil.New(jirix.NewSeq())
-		revision, err := git.CurrentRevision()
-		if err != nil {
-			return err
-		}
-		if err := createSnapshot(jirix, snapshotDir, snapshotFile, label); err != nil {
-			git.Reset(revision)
-			git.RemoveUntrackedFiles()
-			return err
-		}
-		return commitAndPushChanges(jirix, snapshotDir, snapshotFile, label)
-	}
-
-	// Execute the above function in the snapshot directory on a clean master branch.
-	p := project.Project{
-		Path:         snapshotDir,
-		Protocol:     "git",
-		RemoteBranch: "master",
-		Revision:     "HEAD",
-	}
-	return project.ApplyToLocalMaster(jirix, project.Projects{p.Key(): p}, createFn)
-}
-
-// getSnapshotDir returns the path to the snapshot directory, creating it if
-// necessary.
-func getSnapshotDir(jirix *jiri.X) (string, error) {
-	dir := snapshotDirFlag
-	if dir == "" {
-		dir = filepath.Join(jirix.Root, defaultSnapshotDir)
-	}
-
-	if !filepath.IsAbs(dir) {
-		cwd, err := os.Getwd()
-		if err != nil {
-			return "", err
-		}
-		dir = filepath.Join(cwd, dir)
-	}
-
-	// Make sure directory exists.
-	if err := jirix.NewSeq().MkdirAll(dir, 0755).Done(); err != nil {
-		return "", err
-	}
-	return dir, nil
-}
-
-func createSnapshot(jirix *jiri.X, snapshotDir, snapshotFile, label string) error {
-	// Create a snapshot that encodes the current state of master
-	// branches for all local projects.
-	if err := project.CreateSnapshot(jirix, snapshotFile, ""); err != nil {
-		return err
-	}
-
-	s := jirix.NewSeq()
-	// Update the symlink for this snapshot label to point to the
-	// latest snapshot.
-	symlink := filepath.Join(snapshotDir, label)
-	newSymlink := symlink + ".new"
-	relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator))
-	return s.RemoveAll(newSymlink).
-		Symlink(relativeSnapshotPath, newSymlink).
-		Rename(newSymlink, symlink).Done()
-}
-
-// commitAndPushChanges commits changes identified by the given manifest file
-// and label to the containing repository and pushes these changes to the
-// remote repository.
-func commitAndPushChanges(jirix *jiri.X, snapshotDir, snapshotFile, label string) (e error) {
-	cwd, err := os.Getwd()
-	if err != nil {
-		return err
-	}
-	defer collect.Error(func() error { return jirix.NewSeq().Chdir(cwd).Done() }, &e)
-	if err := jirix.NewSeq().Chdir(snapshotDir).Done(); err != nil {
-		return err
-	}
-	relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator))
-	git := gitutil.New(jirix.NewSeq())
-	// Pull from master so we are up-to-date.
-	if err := git.Pull("origin", "master"); err != nil {
-		return err
-	}
-	if err := git.Add(relativeSnapshotPath); err != nil {
-		return err
-	}
-	if err := git.Add(label); err != nil {
-		return err
-	}
-	name := strings.TrimPrefix(snapshotFile, snapshotDir)
-	if err := git.CommitNoVerify(fmt.Sprintf("adding snapshot %q for label %q", name, label)); err != nil {
-		return err
-	}
-	if err := git.Push("origin", "master", gitutil.VerifyOpt(false)); err != nil {
-		return err
-	}
-	return nil
-}
-
-// cmdSnapshotCheckout represents the "jiri snapshot checkout" command.
-var cmdSnapshotCheckout = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runSnapshotCheckout),
-	Name:   "checkout",
-	Short:  "Checkout a project snapshot",
-	Long: `
-The "jiri snapshot checkout <snapshot>" command restores local project state to
-the state in the given snapshot manifest.
-`,
-	ArgsName: "<snapshot>",
-	ArgsLong: "<snapshot> is the snapshot manifest file.",
-}
-
-func runSnapshotCheckout(jirix *jiri.X, args []string) error {
-	if len(args) != 1 {
-		return jirix.UsageErrorf("unexpected number of arguments")
-	}
-	return project.CheckoutSnapshot(jirix, args[0], snapshotGcFlag)
-}
-
-// cmdSnapshotList represents the "jiri snapshot list" command.
-var cmdSnapshotList = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runSnapshotList),
-	Name:   "list",
-	Short:  "List existing project snapshots",
-	Long: `
-The "snapshot list" command lists existing snapshots of the labels
-specified as command-line arguments. If no arguments are provided, the
-command lists snapshots for all known labels.
-`,
-	ArgsName: "<label ...>",
-	ArgsLong: "<label ...> is a list of snapshot labels.",
-}
-
-func runSnapshotList(jirix *jiri.X, args []string) error {
-	snapshotDir, err := getSnapshotDir(jirix)
-	if err != nil {
-		return err
-	}
-	if len(args) == 0 {
-		// Identify all known snapshot labels, using a
-		// heuristic that looks for all symbolic links <foo>
-		// in the snapshot directory that point to a file in
-		// the "labels/<foo>" subdirectory of the snapshot
-		// directory.
-		fileInfoList, err := ioutil.ReadDir(snapshotDir)
-		if err != nil {
-			return fmt.Errorf("ReadDir(%v) failed: %v", snapshotDir, err)
-		}
-		for _, fileInfo := range fileInfoList {
-			if fileInfo.Mode()&os.ModeSymlink != 0 {
-				path := filepath.Join(snapshotDir, fileInfo.Name())
-				dst, err := filepath.EvalSymlinks(path)
-				if err != nil {
-					return fmt.Errorf("EvalSymlinks(%v) failed: %v", path, err)
-				}
-				if strings.HasSuffix(filepath.Dir(dst), filepath.Join("labels", fileInfo.Name())) {
-					args = append(args, fileInfo.Name())
-				}
-			}
-		}
-	}
-
-	// Check that all labels exist.
-	var notexist []string
-	for _, label := range args {
-		labelDir := filepath.Join(snapshotDir, "labels", label)
-		switch _, err := jirix.NewSeq().Stat(labelDir); {
-		case runutil.IsNotExist(err):
-			notexist = append(notexist, label)
-		case err != nil:
-			return err
-		}
-	}
-	if len(notexist) > 0 {
-		return fmt.Errorf("snapshot labels %v not found", notexist)
-	}
-
-	// Print snapshots for all labels.
-	sort.Strings(args)
-	for _, label := range args {
-		// Scan the snapshot directory "labels/<label>" printing
-		// all snapshots.
-		labelDir := filepath.Join(snapshotDir, "labels", label)
-		fileInfoList, err := ioutil.ReadDir(labelDir)
-		if err != nil {
-			return fmt.Errorf("ReadDir(%v) failed: %v", labelDir, err)
-		}
-		fmt.Fprintf(jirix.Stdout(), "snapshots of label %q:\n", label)
-		for _, fileInfo := range fileInfoList {
-			fmt.Fprintf(jirix.Stdout(), "  %v\n", fileInfo.Name())
-		}
-	}
-	return nil
-}
diff --git a/snapshot_test.go b/snapshot_test.go
deleted file mode 100644
index d24b5a6..0000000
--- a/snapshot_test.go
+++ /dev/null
@@ -1,341 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"bytes"
-	"fmt"
-	"os"
-	"path/filepath"
-	"testing"
-
-	"v.io/jiri/gitutil"
-	"v.io/jiri/jiri"
-	"v.io/jiri/jiritest"
-	"v.io/jiri/project"
-	"v.io/jiri/tool"
-)
-
-func createLabelDir(t *testing.T, jirix *jiri.X, snapshotDir, name string, snapshots []string) {
-	if snapshotDir == "" {
-		snapshotDir = filepath.Join(jirix.Root, defaultSnapshotDir)
-	}
-	s := jirix.NewSeq()
-	labelDir, perm := filepath.Join(snapshotDir, "labels", name), os.FileMode(0700)
-	if err := s.MkdirAll(labelDir, perm).Done(); err != nil {
-		t.Fatalf("MkdirAll(%v, %v) failed: %v", labelDir, perm, err)
-	}
-	for i, snapshot := range snapshots {
-		path := filepath.Join(labelDir, snapshot)
-		_, err := os.Create(path)
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if i == 0 {
-			symlinkPath := filepath.Join(snapshotDir, name)
-			if err := s.Symlink(path, symlinkPath).Done(); err != nil {
-				t.Fatalf("Symlink(%v, %v) failed: %v", path, symlinkPath, err)
-			}
-		}
-	}
-}
-
-func generateOutput(labels []label) string {
-	output := ""
-	for _, label := range labels {
-		output += fmt.Sprintf("snapshots of label %q:\n", label.name)
-		for _, snapshot := range label.snapshots {
-			output += fmt.Sprintf("  %v\n", snapshot)
-		}
-	}
-	return output
-}
-
-type config struct {
-	remote bool
-	dir    string
-}
-
-type label struct {
-	name      string
-	snapshots []string
-}
-
-func TestList(t *testing.T) {
-	resetFlags()
-	fake, cleanup := jiritest.NewFakeJiriRoot(t)
-	defer cleanup()
-
-	snapshotDir1 := "" // Should use default dir.
-	snapshotDir2 := filepath.Join(fake.X.Root, "some/other/dir")
-
-	// Create a test suite.
-	tests := []config{
-		config{
-			dir: snapshotDir1,
-		},
-		config{
-			dir: snapshotDir2,
-		},
-	}
-	labels := []label{
-		label{
-			name:      "beta",
-			snapshots: []string{"beta-1", "beta-2", "beta-3"},
-		},
-		label{
-			name:      "stable",
-			snapshots: []string{"stable-1", "stable-2", "stable-3"},
-		},
-	}
-
-	for _, test := range tests {
-		snapshotDirFlag = test.dir
-		// Create the snapshots directory and populate it with the
-		// data specified by the test suite.
-		for _, label := range labels {
-			createLabelDir(t, fake.X, test.dir, label.name, label.snapshots)
-		}
-
-		// Check that running "jiri snapshot list" with no arguments
-		// returns the expected output.
-		var stdout bytes.Buffer
-		fake.X.Context = tool.NewContext(tool.ContextOpts{Stdout: &stdout})
-		if err := runSnapshotList(fake.X, nil); err != nil {
-			t.Fatalf("%v", err)
-		}
-		got, want := stdout.String(), generateOutput(labels)
-		if got != want {
-			t.Fatalf("unexpected output:\ngot\n%v\nwant\n%v\n", got, want)
-		}
-
-		// Check that running "jiri snapshot list" with one argument
-		// returns the expected output.
-		stdout.Reset()
-		if err := runSnapshotList(fake.X, []string{"stable"}); err != nil {
-			t.Fatalf("%v", err)
-		}
-		got, want = stdout.String(), generateOutput(labels[1:])
-		if got != want {
-			t.Fatalf("unexpected output:\ngot\n%v\nwant\n%v\n", got, want)
-		}
-
-		// Check that running "jiri snapshot list" with
-		// multiple arguments returns the expected output.
-		stdout.Reset()
-		if err := runSnapshotList(fake.X, []string{"beta", "stable"}); err != nil {
-			t.Fatalf("%v", err)
-		}
-		got, want = stdout.String(), generateOutput(labels)
-		if got != want {
-			t.Fatalf("unexpected output:\ngot\n%v\nwant\n%v\n", got, want)
-		}
-	}
-}
-
-func checkReadme(t *testing.T, jirix *jiri.X, project, message string) {
-	s := jirix.NewSeq()
-	if _, err := s.Stat(project); err != nil {
-		t.Fatalf("%v", err)
-	}
-	readmeFile := filepath.Join(project, "README")
-	data, err := s.ReadFile(readmeFile)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	if got, want := data, []byte(message); bytes.Compare(got, want) != 0 {
-		t.Fatalf("unexpected content %v:\ngot\n%s\nwant\n%s\n", project, got, want)
-	}
-}
-
-func localProjectName(i int) string {
-	return "test-local-project-" + fmt.Sprintf("%d", i+1)
-}
-
-func remoteProjectName(i int) string {
-	return "test-remote-project-" + fmt.Sprintf("%d", i+1)
-}
-
-func writeReadme(t *testing.T, jirix *jiri.X, projectDir, message string) {
-	s := jirix.NewSeq()
-	path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644)
-	if err := s.WriteFile(path, []byte(message), perm).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-	defer jirix.NewSeq().Chdir(cwd)
-	if err := s.Chdir(projectDir).Done(); err != nil {
-		t.Fatalf("%v", err)
-	}
-	if err := gitutil.New(jirix.NewSeq()).CommitFile(path, "creating README"); err != nil {
-		t.Fatalf("%v", err)
-	}
-}
-
-func resetFlags() {
-	snapshotDirFlag = ""
-	pushRemoteFlag = false
-}
-
-func TestGetSnapshotDir(t *testing.T) {
-	resetFlags()
-	defer resetFlags()
-	fake, cleanup := jiritest.NewFakeJiriRoot(t)
-	defer cleanup()
-
-	// With all flags at default values, snapshot dir should be default.
-	resetFlags()
-	got, err := getSnapshotDir(fake.X)
-	if err != nil {
-		t.Fatalf("getSnapshotDir() failed: %v\n", err)
-	}
-	if want := filepath.Join(fake.X.Root, defaultSnapshotDir); got != want {
-		t.Errorf("unexpected snapshot dir: got %v want %v", got, want)
-	}
-
-	// With dir flag set to absolute path, snapshot dir should be value of dir
-	// flag.
-	resetFlags()
-	tempDir, err := fake.X.NewSeq().TempDir("", "")
-	if err != nil {
-		t.Fatalf("TempDir() failed: %v", err)
-	}
-	defer fake.X.NewSeq().RemoveAll(tempDir).Done()
-	snapshotDirFlag = tempDir
-	got, err = getSnapshotDir(fake.X)
-	if err != nil {
-		t.Fatalf("getSnapshotDir() failed: %v\n", err)
-	}
-	if want := snapshotDirFlag; got != want {
-		t.Errorf("unexpected snapshot dir: got %v want %v", got, want)
-	}
-
-	// With dir flag set to relative path, snapshot dir should absolute path
-	// rooted at current working dir.
-	resetFlags()
-	snapshotDirFlag = "some/relative/path"
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("os.Getwd() failed: %v", err)
-	}
-	got, err = getSnapshotDir(fake.X)
-	if err != nil {
-		t.Fatalf("getSnapshotDir() failed: %v\n", err)
-	}
-	if want := filepath.Join(cwd, snapshotDirFlag); got != want {
-		t.Errorf("unexpected snapshot dir: got %v want %v", got, want)
-	}
-}
-
-// TestCreate tests creating and checking out a snapshot.
-func TestCreate(t *testing.T) {
-	resetFlags()
-	defer resetFlags()
-	fake, cleanup := jiritest.NewFakeJiriRoot(t)
-	defer cleanup()
-	s := fake.X.NewSeq()
-
-	// Setup the initial remote and local projects.
-	numProjects, remoteProjects := 2, []string{}
-	for i := 0; i < numProjects; i++ {
-		if err := fake.CreateRemoteProject(remoteProjectName(i)); err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := fake.AddProject(project.Project{
-			Name:   remoteProjectName(i),
-			Path:   localProjectName(i),
-			Remote: fake.Projects[remoteProjectName(i)],
-		}); err != nil {
-			t.Fatalf("%v", err)
-		}
-	}
-
-	// Create initial commits in the remote projects and use UpdateUniverse()
-	// to mirror them locally.
-	for i := 0; i < numProjects; i++ {
-		writeReadme(t, fake.X, fake.Projects[remoteProjectName(i)], "revision 1")
-	}
-	if err := project.UpdateUniverse(fake.X, true); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Create a snapshot.
-	var stdout bytes.Buffer
-	fake.X.Context = tool.NewContext(tool.ContextOpts{Stdout: &stdout})
-	if err := runSnapshotCreate(fake.X, []string{"test-local"}); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Remove the local project repositories.
-	for i, _ := range remoteProjects {
-		localProject := filepath.Join(fake.X.Root, localProjectName(i))
-		if err := s.RemoveAll(localProject).Done(); err != nil {
-			t.Fatalf("%v", err)
-		}
-	}
-
-	// Check that invoking the UpdateUniverse() with the snapshot restores the
-	// local repositories.
-	snapshotDir := filepath.Join(fake.X.Root, defaultSnapshotDir)
-	snapshotFile := filepath.Join(snapshotDir, "test-local")
-	localX := fake.X.Clone(tool.ContextOpts{
-		Manifest: &snapshotFile,
-	})
-	if err := project.UpdateUniverse(localX, true); err != nil {
-		t.Fatalf("%v", err)
-	}
-	for i, _ := range remoteProjects {
-		localProject := filepath.Join(fake.X.Root, localProjectName(i))
-		checkReadme(t, fake.X, localProject, "revision 1")
-	}
-}
-
-// TestCreatePushRemote checks that creating a snapshot with the -push-remote
-// flag causes the snapshot to be committed and pushed upstream.
-func TestCreatePushRemote(t *testing.T) {
-	resetFlags()
-	defer resetFlags()
-
-	fake, cleanup := jiritest.NewFakeJiriRoot(t)
-	defer cleanup()
-
-	fake.EnableRemoteManifestPush()
-	defer fake.DisableRemoteManifestPush()
-
-	manifestDir := filepath.Join(fake.X.Root, ".manifest")
-	snapshotDir := filepath.Join(manifestDir, "snapshot")
-	label := "test"
-
-	git := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(manifestDir))
-	commitCount, err := git.CountCommits("master", "")
-	if err != nil {
-		t.Fatalf("git.CountCommits(\"master\", \"\") failed: %v", err)
-	}
-
-	// Create snapshot with -push-remote flag set to true.
-	snapshotDirFlag = snapshotDir
-	pushRemoteFlag = true
-	if err := runSnapshotCreate(fake.X, []string{label}); err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	// Check that repo has one new commit.
-	newCommitCount, err := git.CountCommits("master", "")
-	if err != nil {
-		t.Fatalf("git.CountCommits(\"master\", \"\") failed: %v", err)
-	}
-	if got, want := newCommitCount, commitCount+1; got != want {
-		t.Errorf("unexpected commit count: got %v want %v", got, want)
-	}
-
-	// Check that new label is commited.
-	labelFile := filepath.Join(snapshotDir, "labels", label)
-	if !git.IsFileCommitted(labelFile) {
-		t.Errorf("expected file %v to be committed but it was not", labelFile)
-	}
-}
diff --git a/update.go b/update.go
deleted file mode 100644
index 9dccf97..0000000
--- a/update.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/retry"
-	"v.io/jiri/tool"
-	"v.io/x/lib/cmdline"
-)
-
-var (
-	gcFlag       bool
-	attemptsFlag int
-)
-
-func init() {
-	tool.InitializeProjectFlags(&cmdUpdate.Flags)
-
-	cmdUpdate.Flags.BoolVar(&gcFlag, "gc", false, "Garbage collect obsolete repositories.")
-	cmdUpdate.Flags.IntVar(&attemptsFlag, "attempts", 1, "Number of attempts before failing.")
-}
-
-// cmdUpdate represents the "jiri update" command.
-var cmdUpdate = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runUpdate),
-	Name:   "update",
-	Short:  "Update all jiri tools and projects",
-	Long: `
-Updates all projects, builds the latest version of all tools, and installs the
-resulting binaries into $JIRI_ROOT/.jiri_root/bin. The sequence in which the
-individual updates happen guarantees that we end up with a consistent set of
-tools and source code. The set of projects and tools to update is described in
-the manifest.
-
-Run "jiri help manifest" for details on manifests.
-`,
-}
-
-func runUpdate(jirix *jiri.X, _ []string) error {
-	seq := jirix.NewSeq()
-	// Create the $JIRI_ROOT/.jiri_root directory if it doesn't already exist.
-	//
-	// TODO(toddw): Remove this logic after the transition to .jiri_root is done.
-	// The bootstrapping logic should create this directory, and jiri should fail
-	// if the directory doesn't exist.
-	if err := seq.MkdirAll(jirix.RootMetaDir(), 0755).Done(); err != nil {
-		return err
-	}
-
-	// Update all projects to their latest version.
-	// Attempt <attemptsFlag> times before failing.
-	updateFn := func() error { return project.UpdateUniverse(jirix, gcFlag) }
-	if err := retry.Function(jirix.Context, updateFn, retry.AttemptsOpt(attemptsFlag)); err != nil {
-		return err
-	}
-	if err := project.WriteUpdateHistorySnapshot(jirix, ""); err != nil {
-		return err
-	}
-
-	// Only attempt the bin dir transition after the update has succeeded, to
-	// avoid messy partial states.
-	return project.TransitionBinDir(jirix)
-}
diff --git a/upgrade.go b/upgrade.go
deleted file mode 100644
index da6947f..0000000
--- a/upgrade.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"v.io/jiri/jiri"
-	"v.io/jiri/project"
-	"v.io/jiri/runutil"
-	"v.io/x/lib/cmdline"
-)
-
-// TODO(toddw): Remove the upgrade command after the transition to new-style
-// manifests is complete.
-
-var flagUpgradeRevert bool
-
-func init() {
-	cmdUpgrade.Flags.BoolVar(&flagUpgradeRevert, "revert", false, `Revert the upgrade by deleting the $JIRI_ROOT/.jiri_manifest file.`)
-}
-
-var cmdUpgrade = &cmdline.Command{
-	Runner: jiri.RunnerFunc(runUpgrade),
-	Name:   "upgrade",
-	Short:  "Upgrade jiri to new-style manifests",
-	Long: `
-Upgrades jiri to use new-style manifests.
-
-The old (deprecated) behavior only allowed a single manifest repository, located
-in $JIRI_ROOT/.manifest.  The initial manifest file is located as follows:
-  1) Use -manifest flag, if non-empty.  If it's empty...
-  2) Use $JIRI_ROOT/.local_manifest file.  If it doesn't exist...
-  3) Use $JIRI_ROOT/.manifest/v2/default.
-
-The new behavior allows multiple manifest repositories, by allowing imports to
-specify project attributes describing the remote repository.  The -manifest flag
-is no longer allowed to be set; the initial manifest file is always located in
-$JIRI_ROOT/.jiri_manifest.  The .local_manifest file is ignored.
-
-During the transition phase, both old and new behaviors are supported.  The jiri
-tool uses the existence of the $JIRI_ROOT/.jiri_manifest file as the signal; if
-it exists we run the new behavior, otherwise we run the old behavior.
-
-The new behavior includes a "jiri import" command, which writes or updates the
-.jiri_manifest file.  The new bootstrap procedure runs "jiri import", and it is
-intended as a regular command to add imports to your jiri environment.
-
-This upgrade command eases the transition by writing an initial .jiri_manifest
-file for you.  If you have an existing .local_manifest file, its contents will
-be incorporated into the new .jiri_manifest file, and it will be renamed to
-.local_manifest.BACKUP.  The -revert flag deletes the .jiri_manifest file, and
-restores the .local_manifest file.
-`,
-	ArgsName: "<kind>",
-	ArgsLong: `
-<kind> specifies the kind of upgrade, one of "v23" or "fuchsia".
-`,
-}
-
-func runUpgrade(jirix *jiri.X, args []string) error {
-	localFile := filepath.Join(jirix.Root, ".local_manifest")
-	backupFile := localFile + ".BACKUP"
-	if flagUpgradeRevert {
-		// Restore .local_manifest.BACKUP if it exists.
-		switch _, err := jirix.NewSeq().Stat(backupFile); {
-		case err != nil && !runutil.IsNotExist(err):
-			return err
-		case err == nil:
-			if err := jirix.NewSeq().Rename(backupFile, localFile).Done(); err != nil {
-				return fmt.Errorf("couldn't restore %v to %v: %v", backupFile, localFile, err)
-			}
-		}
-		// Deleting the .jiri_manifest file reverts to the old behavior.
-		return jirix.NewSeq().Remove(jirix.JiriManifestFile()).Done()
-	}
-	if len(args) != 1 {
-		return jirix.UsageErrorf("must specify upgrade kind")
-	}
-	kind := args[0]
-	var argRemote, argName, argManifest string
-	switch kind {
-	case "v23":
-		argRemote = "https://vanadium.googlesource.com/manifest"
-		argName, argManifest = "manifest", "public"
-	case "fuchsia":
-		argRemote = "https://fuchsia.googlesource.com/fnl-start"
-		argName, argManifest = "fnl-start", "manifest/fuchsia"
-	default:
-		return jirix.UsageErrorf("unknown upgrade kind %q", kind)
-	}
-	// Initialize manifest from .local_manifest.
-	hasLocalFile := true
-	manifest, err := project.ManifestFromFile(jirix, localFile)
-	if err != nil {
-		if !runutil.IsNotExist(err) {
-			return err
-		}
-		hasLocalFile = false
-		manifest = &project.Manifest{}
-	}
-	oldImports := manifest.Imports
-	manifest.Imports = nil
-	for _, oldImport := range oldImports {
-		if oldImport.Remote != "" {
-			// This is a new-style remote import, carry it over directly.
-			manifest.Imports = append(manifest.Imports, oldImport)
-			continue
-		}
-		// This is an old-style import, convert it to the new style.
-		oldName := oldImport.Name
-		switch {
-		case kind == "v23" && oldName == "default":
-			oldName = "public"
-		case kind == "fuchsia" && oldName == "default":
-			oldName = "manifest/fuchsia"
-		}
-		manifest.Imports = append(manifest.Imports, project.Import{
-			Manifest: oldName,
-			Name:     argName,
-			Remote:   argRemote,
-		})
-	}
-	if len(manifest.Imports) == 0 {
-		manifest.Imports = append(manifest.Imports, project.Import{
-			Manifest: argManifest,
-			Name:     argName,
-			Remote:   argRemote,
-		})
-	}
-	// Write output to .jiri_manifest file.
-	outFile := jirix.JiriManifestFile()
-	if _, err := os.Stat(outFile); err == nil {
-		return fmt.Errorf("%v already exists", outFile)
-	}
-	if err := manifest.ToFile(jirix, outFile); err != nil {
-		return err
-	}
-	// Backup .local_manifest file, if it exists.
-	if hasLocalFile {
-		if err := jirix.NewSeq().Rename(localFile, backupFile).Done(); err != nil {
-			return fmt.Errorf("couldn't backup %v to %v: %v", localFile, backupFile, err)
-		}
-	}
-	return nil
-}
diff --git a/upgrade_test.go b/upgrade_test.go
deleted file mode 100644
index a7c5eab..0000000
--- a/upgrade_test.go
+++ /dev/null
@@ -1,311 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"strings"
-	"testing"
-
-	"v.io/jiri/jiri"
-	"v.io/x/lib/gosh"
-)
-
-type upgradeTestCase struct {
-	Args        []string
-	Exist       bool
-	Local, Want string
-	Stderr      string
-}
-
-func TestUpgrade(t *testing.T) {
-	tests := []upgradeTestCase{
-		{
-			Stderr: `must specify upgrade kind`,
-		},
-		{
-			Args:   []string{"foo"},
-			Stderr: `unknown upgrade kind "foo"`,
-		},
-		// Test v23 upgrades.
-		{
-			Args:   []string{"v23"},
-			Exist:  true,
-			Stderr: `.jiri_manifest already exists`,
-		},
-		{
-			Args: []string{"v23"},
-			Want: `<manifest>
-  <imports>
-    <import manifest="public" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"v23"},
-			Local: `<manifest>
-  <imports>
-    <import name="default"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="public" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"v23"},
-			Local: `<manifest>
-  <imports>
-    <import name="private"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="private" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"v23"},
-			Local: `<manifest>
-  <imports>
-    <import name="private"/>
-    <import name="infrastructure"/>
-    <import name="default"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="private" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-    <import manifest="infrastructure" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-    <import manifest="public" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"v23"},
-			Local: `<manifest>
-  <imports>
-    <import name="default"/>
-    <import name="infrastructure"/>
-    <import name="private"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="public" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-    <import manifest="infrastructure" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-    <import manifest="private" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`,
-		},
-		// Test fuchsia upgrades.
-		{
-			Args:   []string{"fuchsia"},
-			Exist:  true,
-			Stderr: `.jiri_manifest already exists`,
-		},
-		{
-			Args: []string{"fuchsia"},
-			Want: `<manifest>
-  <imports>
-    <import manifest="manifest/fuchsia" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"fuchsia"},
-			Local: `<manifest>
-  <imports>
-    <import name="default"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="manifest/fuchsia" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"fuchsia"},
-			Local: `<manifest>
-  <imports>
-    <import name="private"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="private" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"fuchsia"},
-			Local: `<manifest>
-  <imports>
-    <import name="private"/>
-    <import name="infrastructure"/>
-    <import name="default"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="private" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-    <import manifest="infrastructure" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-    <import manifest="manifest/fuchsia" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-  </imports>
-</manifest>
-`,
-		},
-		{
-			Args: []string{"fuchsia"},
-			Local: `<manifest>
-  <imports>
-    <import name="default"/>
-    <import name="infrastructure"/>
-    <import name="private"/>
-  </imports>
-</manifest>
-`,
-			Want: `<manifest>
-  <imports>
-    <import manifest="manifest/fuchsia" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-    <import manifest="infrastructure" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-    <import manifest="private" name="fnl-start" remote="https://fuchsia.googlesource.com/fnl-start"/>
-  </imports>
-</manifest>
-`,
-		},
-	}
-	opts := gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf}
-	sh := gosh.NewShell(opts)
-	defer sh.Cleanup()
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
-	for _, test := range tests {
-		if err := testUpgrade(opts, jiriTool, test); err != nil {
-			t.Errorf("%v: %v", test.Args, err)
-		}
-	}
-}
-
-func testUpgrade(opts gosh.Opts, jiriTool string, test upgradeTestCase) error {
-	opts.PropagateChildOutput = true
-	sh := gosh.NewShell(opts)
-	defer sh.Cleanup()
-	jiriRoot := sh.MakeTempDir()
-	sh.Pushd(jiriRoot)
-	// Set up an existing file or local_manifest, if they were specified
-	if test.Exist {
-		if err := ioutil.WriteFile(".jiri_manifest", []byte("<manifest/>"), 0644); err != nil {
-			return err
-		}
-	}
-	if test.Local != "" {
-		if err := ioutil.WriteFile(".local_manifest", []byte(test.Local), 0644); err != nil {
-			return err
-		}
-	}
-	// Run upgrade and check the error.
-	sh.Vars[jiri.RootEnv] = jiriRoot
-	cmd := sh.Cmd(jiriTool, append([]string{"upgrade"}, test.Args...)...)
-	if test.Stderr != "" {
-		cmd.ExitErrorIsOk = true
-	}
-	_, stderr := cmd.StdoutStderr()
-	if got, want := stderr, test.Stderr; !strings.Contains(got, want) || (got != "" && want == "") {
-		return fmt.Errorf("stderr got %q, want substr %q", got, want)
-	}
-	// Make sure the right file is generated.
-	if test.Want != "" {
-		data, err := ioutil.ReadFile(".jiri_manifest")
-		if err != nil {
-			return err
-		}
-		if got, want := string(data), test.Want; got != want {
-			return fmt.Errorf("GOT\n%s\nWANT\n%s", got, want)
-		}
-	}
-	// Make sure the .local_manifest file is backed up.
-	if test.Local != "" && test.Stderr == "" {
-		data, err := ioutil.ReadFile(".local_manifest.BACKUP")
-		if err != nil {
-			return fmt.Errorf("local manifest backup got error: %v", err)
-		}
-		if got, want := string(data), test.Local; got != want {
-			return fmt.Errorf("local manifest backup GOT\n%s\nWANT\n%s", got, want)
-		}
-	}
-	return nil
-}
-
-func TestUpgradeRevert(t *testing.T) {
-	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf})
-	defer sh.Cleanup()
-	jiriRoot := sh.MakeTempDir()
-	sh.Pushd(jiriRoot)
-	jiriTool := sh.BuildGoPkg("v.io/jiri")
-	localData := `<manifest/>`
-	jiriData := `<manifest>
-  <imports>
-    <import manifest="public" name="manifest" remote="https://vanadium.googlesource.com/manifest"/>
-  </imports>
-</manifest>
-`
-	// Set up an existing local_manifest.
-	if err := ioutil.WriteFile(".local_manifest", []byte(localData), 0644); err != nil {
-		t.Errorf("couldn't write local manifest: %v", err)
-	}
-	// Run a regular upgrade first, and make sure files are as expected.
-	sh.Vars[jiri.RootEnv] = jiriRoot
-	sh.Cmd(jiriTool, "upgrade", "v23").Run()
-	gotJiri, err := ioutil.ReadFile(".jiri_manifest")
-	if err != nil {
-		t.Errorf("couldn't read jiri manifest: %v", err)
-	}
-	if got, want := string(gotJiri), jiriData; got != want {
-		t.Errorf("jiri manifest GOT\n%s\nWANT\n%s", got, want)
-	}
-	gotBackup, err := ioutil.ReadFile(".local_manifest.BACKUP")
-	if err != nil {
-		t.Errorf("couldn't read local manifest backup: %v", err)
-	}
-	if got, want := string(gotBackup), localData; got != want {
-		t.Errorf("local manifest backup GOT\n%s\nWANT\n%s", got, want)
-	}
-	// Now run a revert, and make sure files are as expected.
-	sh.Cmd(jiriTool, "upgrade", "-revert").Run()
-	if _, err := os.Stat(".jiri_manifest"); !os.IsNotExist(err) {
-		t.Errorf(".jiri_manifest still exists after revert: %v", err)
-	}
-	if _, err := os.Stat(".local_manifest.BACKUP"); !os.IsNotExist(err) {
-		t.Errorf(".local_manifest.BACKUP still exists after revert: %v", err)
-	}
-	gotLocal, err := ioutil.ReadFile(".local_manifest")
-	if err != nil {
-		t.Errorf("couldn't read local manifest: %v", err)
-	}
-	if got, want := string(gotLocal), localData; got != want {
-		t.Errorf("local manifest GOT\n%s\nWANT\n%s", got, want)
-	}
-}
diff --git a/which.go b/which.go
deleted file mode 100644
index 9dee05c..0000000
--- a/which.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-
-	"v.io/x/lib/cmdline"
-)
-
-var cmdWhich = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runWhich),
-	Name:   "which",
-	Short:  "Show path to the jiri tool",
-	Long: `
-Which behaves similarly to the unix commandline tool.  It is useful in
-determining whether the jiri binary is being run directly, or run via the jiri
-shim script.
-
-If the binary is being run directly, the output looks like this:
-
-  # binary
-  /path/to/binary/jiri
-
-If the script is being run, the output looks like this:
-
-  # script
-  /path/to/script/jiri
-`,
-}
-
-func runWhich(env *cmdline.Env, args []string) error {
-	if len(args) == 0 {
-		fmt.Fprintln(env.Stdout, "# binary")
-		path, err := exec.LookPath(os.Args[0])
-		if err != nil {
-			return err
-		}
-		abs, err := filepath.Abs(path)
-		if err != nil {
-			return err
-		}
-		fmt.Fprintln(env.Stdout, abs)
-		return nil
-	}
-	// TODO(toddw): Look up the path to each argument.  This will only be helpful
-	// after the profiles are moved back into the main jiri tool.
-	return fmt.Errorf("unexpected arguments")
-}
diff --git a/which_test.go b/which_test.go
deleted file mode 100644
index dbe46f1..0000000
--- a/which_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"fmt"
-	"path/filepath"
-	"testing"
-
-	"v.io/x/lib/gosh"
-)
-
-func TestWhich(t *testing.T) {
-	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf, PropagateChildOutput: true})
-	defer sh.Cleanup()
-
-	jiriBinary := sh.BuildGoPkg("v.io/jiri")
-	stdout, stderr := sh.Cmd(jiriBinary, []string{"which"}...).StdoutStderr()
-	if got, want := stdout, fmt.Sprintf("# binary\n%s\n", jiriBinary); got != want {
-		t.Errorf("stdout got %q, want %q", got, want)
-	}
-	if got, want := stderr, ""; got != want {
-		t.Errorf("stderr got %q, want %q", got, want)
-	}
-}
-
-// TestWhichScript tests the behavior of "jiri which" for the shim script.
-func TestWhichScript(t *testing.T) {
-	sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf, PropagateChildOutput: true})
-	defer sh.Cleanup()
-
-	jiriScript, err := filepath.Abs("./scripts/jiri")
-	if err != nil {
-		t.Fatalf("couldn't determine absolute path to jiri script")
-	}
-	stdout, stderr := sh.Cmd(jiriScript, "which").StdoutStderr()
-	if got, want := stdout, fmt.Sprintf("# script\n%s\n", jiriScript); got != want {
-		t.Errorf("stdout got %q, want %q", got, want)
-	}
-	if got, want := stderr, ""; got != want {
-		t.Errorf("stderr got %q, want %q", got, want)
-	}
-}