Merge "v.io/jiir/runutil: more cleanup"
diff --git a/cl.go b/cl.go
index c360baa..241d146 100644
--- a/cl.go
+++ b/cl.go
@@ -14,6 +14,7 @@
 	"v.io/jiri/collect"
 	"v.io/jiri/gerrit"
 	"v.io/jiri/gitutil"
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
 	"v.io/x/lib/cmdline"
@@ -75,28 +76,28 @@
 	cmdCLSync.Flags.StringVar(&remoteBranchFlag, "remote-branch", "master", "Name of the remote branch the CL pertains to.")
 }
 
-func getCommitMessageFileName(ctx *tool.Context, branch string) (string, error) {
-	topLevel, err := ctx.Git().TopLevel()
+func getCommitMessageFileName(jirix *jiri.X, branch string) (string, error) {
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return "", err
 	}
 	return filepath.Join(topLevel, project.MetadataDirName(), branch, commitMessageFileName), nil
 }
 
-func getDependencyPathFileName(ctx *tool.Context, branch string) (string, error) {
-	topLevel, err := ctx.Git().TopLevel()
+func getDependencyPathFileName(jirix *jiri.X, branch string) (string, error) {
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return "", err
 	}
 	return filepath.Join(topLevel, project.MetadataDirName(), branch, dependencyPathFileName), nil
 }
 
-func getDependentCLs(ctx *tool.Context, branch string) ([]string, error) {
-	file, err := getDependencyPathFileName(ctx, branch)
+func getDependentCLs(jirix *jiri.X, branch string) ([]string, error) {
+	file, err := getDependencyPathFileName(jirix, branch)
 	if err != nil {
 		return nil, err
 	}
-	data, err := ctx.Run().ReadFile(file)
+	data, err := jirix.Run().ReadFile(file)
 	var branches []string
 	if err != nil {
 		if !os.IsNotExist(err) {
@@ -124,7 +125,7 @@
 // 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: cmdline.RunnerFunc(runCLCleanup),
+	Runner: jiri.RunnerFunc(runCLCleanup),
 	Name:   "cleanup",
 	Short:  "Clean up changelists that have been merged",
 	Long: `
@@ -137,34 +138,34 @@
 	ArgsLong: "<branches> is a list of branches to cleanup.",
 }
 
-func cleanupCL(ctx *tool.Context, branches []string) (e error) {
-	originalBranch, err := ctx.Git().CurrentBranchName()
+func cleanupCL(jirix *jiri.X, branches []string) (e error) {
+	originalBranch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return err
 	}
-	stashed, err := ctx.Git().Stash()
+	stashed, err := jirix.Git().Stash()
 	if err != nil {
 		return err
 	}
 	if stashed {
-		defer collect.Error(func() error { return ctx.Git().StashPop() }, &e)
+		defer collect.Error(func() error { return jirix.Git().StashPop() }, &e)
 	}
-	if err := ctx.Git().CheckoutBranch(remoteBranchFlag); err != nil {
+	if err := jirix.Git().CheckoutBranch(remoteBranchFlag); err != nil {
 		return err
 	}
 	checkoutOriginalBranch := true
 	defer collect.Error(func() error {
 		if checkoutOriginalBranch {
-			return ctx.Git().CheckoutBranch(originalBranch)
+			return jirix.Git().CheckoutBranch(originalBranch)
 		}
 		return nil
 	}, &e)
-	if err := ctx.Git().FetchRefspec("origin", remoteBranchFlag); err != nil {
+	if err := jirix.Git().FetchRefspec("origin", remoteBranchFlag); err != nil {
 		return err
 	}
 	for _, branch := range branches {
-		cleanupFn := func() error { return cleanupBranch(ctx, branch) }
-		if err := ctx.Run().Function(cleanupFn, "Cleaning up branch %q", branch); err != nil {
+		cleanupFn := func() error { return cleanupBranch(jirix, branch) }
+		if err := jirix.Run().Function(cleanupFn, "Cleaning up branch %q", branch); err != nil {
 			return err
 		}
 		if branch == originalBranch {
@@ -174,16 +175,16 @@
 	return nil
 }
 
-func cleanupBranch(ctx *tool.Context, branch string) error {
-	if err := ctx.Git().CheckoutBranch(branch); err != nil {
+func cleanupBranch(jirix *jiri.X, branch string) error {
+	if err := jirix.Git().CheckoutBranch(branch); err != nil {
 		return err
 	}
 	if !forceFlag {
 		trackingBranch := "origin/" + remoteBranchFlag
-		if err := ctx.Git().Merge(trackingBranch); err != nil {
+		if err := jirix.Git().Merge(trackingBranch); err != nil {
 			return err
 		}
-		files, err := ctx.Git().ModifiedFiles(trackingBranch, branch)
+		files, err := jirix.Git().ModifiedFiles(trackingBranch, branch)
 		if err != nil {
 			return err
 		}
@@ -191,29 +192,29 @@
 			return fmt.Errorf("unmerged changes in\n%s", strings.Join(files, "\n"))
 		}
 	}
-	if err := ctx.Git().CheckoutBranch(remoteBranchFlag); err != nil {
+	if err := jirix.Git().CheckoutBranch(remoteBranchFlag); err != nil {
 		return err
 	}
-	if err := ctx.Git().DeleteBranch(branch, gitutil.ForceOpt(true)); err != nil {
+	if err := jirix.Git().DeleteBranch(branch, gitutil.ForceOpt(true)); err != nil {
 		return err
 	}
 	reviewBranch := branch + "-REVIEW"
-	if ctx.Git().BranchExists(reviewBranch) {
-		if err := ctx.Git().DeleteBranch(reviewBranch, gitutil.ForceOpt(true)); err != nil {
+	if jirix.Git().BranchExists(reviewBranch) {
+		if err := jirix.Git().DeleteBranch(reviewBranch, gitutil.ForceOpt(true)); err != nil {
 			return err
 		}
 	}
 	// Delete branch metadata.
-	topLevel, err := ctx.Git().TopLevel()
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
 	metadataDir := filepath.Join(topLevel, project.MetadataDirName())
-	if err := ctx.Run().RemoveAll(filepath.Join(metadataDir, branch)); err != nil {
+	if err := jirix.Run().RemoveAll(filepath.Join(metadataDir, branch)); err != nil {
 		return err
 	}
 	// Remove the branch from all dependency paths.
-	fileInfos, err := ctx.Run().ReadDir(metadataDir)
+	fileInfos, err := jirix.Run().ReadDir(metadataDir)
 	if err != nil {
 		return err
 	}
@@ -221,11 +222,11 @@
 		if !fileInfo.IsDir() {
 			continue
 		}
-		file, err := getDependencyPathFileName(ctx, fileInfo.Name())
+		file, err := getDependencyPathFileName(jirix, fileInfo.Name())
 		if err != nil {
 			return err
 		}
-		data, err := ctx.Run().ReadFile(file)
+		data, err := jirix.Run().ReadFile(file)
 		if err != nil {
 			if !os.IsNotExist(err) {
 				return err
@@ -236,7 +237,7 @@
 		for i, tmpBranch := range branches {
 			if branch == tmpBranch {
 				data := []byte(strings.Join(append(branches[:i], branches[i+1:]...), "\n"))
-				if err := ctx.Run().WriteFile(file, data, os.FileMode(0644)); err != nil {
+				if err := jirix.Run().WriteFile(file, data, os.FileMode(0644)); err != nil {
 					return err
 				}
 				break
@@ -246,17 +247,16 @@
 	return nil
 }
 
-func runCLCleanup(env *cmdline.Env, args []string) error {
+func runCLCleanup(jirix *jiri.X, args []string) error {
 	if len(args) == 0 {
-		return env.UsageErrorf("cleanup requires at least one argument")
+		return jirix.UsageErrorf("cleanup requires at least one argument")
 	}
-	ctx := tool.NewContextFromEnv(env)
-	return cleanupCL(ctx, args)
+	return cleanupCL(jirix, args)
 }
 
 // cmdCLMail represents the "jiri cl mail" command.
 var cmdCLMail = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runCLMail),
+	Runner: jiri.RunnerFunc(runCLMail),
 	Name:   "mail",
 	Short:  "Mail a changelist for review",
 	Long: `
@@ -331,26 +331,24 @@
 `
 
 // runCLMail is a wrapper that sets up and runs a review instance.
-func runCLMail(env *cmdline.Env, _ []string) error {
-	ctx := tool.NewContextFromEnv(env)
-
+func runCLMail(jirix *jiri.X, _ []string) error {
 	// Sanity checks for the <presubmitFlag> flag.
 	if !checkPresubmitFlag() {
-		return env.UsageErrorf("invalid value for the -presubmit flag. Valid values: %s.",
+		return jirix.UsageErrorf("invalid value for the -presubmit flag. Valid values: %s.",
 			strings.Join(gerrit.PresubmitTestTypes(), ","))
 	}
 
 	host := hostFlag
 	if host == "" {
 		var err error
-		if host, err = project.GerritHost(ctx); err != nil {
+		if host, err = project.GerritHost(jirix); err != nil {
 			return err
 		}
 	}
 
 	// Create and run the review.
 
-	review, err := newReview(ctx, gerrit.CLOpts{
+	review, err := newReview(jirix, gerrit.CLOpts{
 		Autosubmit:   autosubmitFlag,
 		Ccs:          parseEmails(ccsFlag),
 		Draft:        draftFlag,
@@ -394,21 +392,21 @@
 // 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(ctx *tool.Context) (e error) {
-	originalBranch, err := ctx.Git().CurrentBranchName()
+func checkDependents(jirix *jiri.X) (e error) {
+	originalBranch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return err
 	}
-	branches, err := getDependentCLs(ctx, originalBranch)
+	branches, err := getDependentCLs(jirix, originalBranch)
 	if err != nil {
 		return err
 	}
 	for i := 1; i < len(branches); i++ {
-		file, err := getCommitMessageFileName(ctx, branches[i])
+		file, err := getCommitMessageFileName(jirix, branches[i])
 		if err != nil {
 			return err
 		}
-		if _, err := ctx.Run().Stat(file); err != nil {
+		if _, err := jirix.Run().Stat(file); err != nil {
 			if !os.IsNotExist(err) {
 				return err
 			}
@@ -426,15 +424,15 @@
 }
 
 type review struct {
-	ctx          *tool.Context
+	jirix        *jiri.X
 	reviewBranch string
 	gerrit.CLOpts
 }
 
-func newReview(ctx *tool.Context, opts gerrit.CLOpts) (*review, error) {
+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(ctx); err != nil {
+	if err := syncCL(jirix); err != nil {
 		return nil, err
 	}
 
@@ -444,11 +442,11 @@
 	//
 	// NOTE: The alternative here is to prompt the user for multiple
 	// commit messages, which seems less user friendly.
-	if err := checkDependents(ctx); err != nil {
+	if err := checkDependents(jirix); err != nil {
 		return nil, err
 	}
 
-	branch, err := ctx.Git().CurrentBranchName()
+	branch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return nil, err
 	}
@@ -463,7 +461,7 @@
 		opts.RemoteBranch = "master" // use master as the default
 	}
 	return &review{
-		ctx:          ctx,
+		jirix:        jirix,
 		reviewBranch: branch + "-REVIEW",
 		CLOpts:       opts,
 	}, nil
@@ -481,11 +479,11 @@
 // confirmFlagChanges asks users to confirm if any of the
 // presubmit and autosubmit flags changes.
 func (review *review) confirmFlagChanges() (bool, error) {
-	file, err := getCommitMessageFileName(review.ctx, review.CLOpts.Branch)
+	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
 	if err != nil {
 		return false, err
 	}
-	bytes, err := review.ctx.Run().ReadFile(file)
+	bytes, err := review.jirix.Run().ReadFile(file)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return true, nil
@@ -525,16 +523,16 @@
 
 // cleanup cleans up after the review.
 func (review *review) cleanup(stashed bool) error {
-	if err := review.ctx.Git().CheckoutBranch(review.CLOpts.Branch); err != nil {
+	if err := review.jirix.Git().CheckoutBranch(review.CLOpts.Branch); err != nil {
 		return err
 	}
-	if review.ctx.Git().BranchExists(review.reviewBranch) {
-		if err := review.ctx.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
+	if review.jirix.Git().BranchExists(review.reviewBranch) {
+		if err := review.jirix.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
 			return err
 		}
 	}
 	if stashed {
-		if err := review.ctx.Git().StashPop(); err != nil {
+		if err := review.jirix.Git().StashPop(); err != nil {
 			return err
 		}
 	}
@@ -550,35 +548,35 @@
 // the commit message for the last commit.
 func (review *review) createReviewBranch(message string) (e error) {
 	// Create the review branch.
-	if err := review.ctx.Git().FetchRefspec("origin", review.CLOpts.RemoteBranch); err != nil {
+	if err := review.jirix.Git().FetchRefspec("origin", review.CLOpts.RemoteBranch); err != nil {
 		return err
 	}
-	if review.ctx.Git().BranchExists(review.reviewBranch) {
-		if err := review.ctx.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
+	if review.jirix.Git().BranchExists(review.reviewBranch) {
+		if err := review.jirix.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil {
 			return err
 		}
 	}
 	upstream := "origin/" + review.CLOpts.RemoteBranch
-	if err := review.ctx.Git().CreateBranchWithUpstream(review.reviewBranch, upstream); err != nil {
+	if err := review.jirix.Git().CreateBranchWithUpstream(review.reviewBranch, upstream); err != nil {
 		return err
 	}
-	if err := review.ctx.Git().CheckoutBranch(review.reviewBranch); err != nil {
+	if err := review.jirix.Git().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 review.ctx.Git().CheckoutBranch(review.CLOpts.Branch)
+			return review.jirix.Git().CheckoutBranch(review.CLOpts.Branch)
 		}
-		review.ctx.Git().CheckoutBranch(review.CLOpts.Branch, gitutil.ForceOpt(true))
-		review.ctx.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true))
+		review.jirix.Git().CheckoutBranch(review.CLOpts.Branch, gitutil.ForceOpt(true))
+		review.jirix.Git().DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true))
 		return nil
 	}, &e)
 
 	// Report an error if the CL is empty.
-	if !review.ctx.DryRun() {
-		hasDiff, err := review.ctx.Git().BranchesDiffer(review.CLOpts.Branch, review.reviewBranch)
+	if !review.jirix.DryRun() {
+		hasDiff, err := review.jirix.Git().BranchesDiffer(review.CLOpts.Branch, review.reviewBranch)
 		if err != nil {
 			return err
 		}
@@ -599,7 +597,7 @@
 	// 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.ctx, review.CLOpts.Branch)
+	branches, err := getDependentCLs(review.jirix, review.CLOpts.Branch)
 	if err != nil {
 		return err
 	}
@@ -627,7 +625,7 @@
 		// here is based on:
 		//
 		// http://stackoverflow.com/questions/173919/is-there-a-theirs-version-of-git-merge-s-ours
-		if err := review.ctx.Git().Merge(branches[i], gitutil.SquashOpt(true), gitutil.StrategyOpt("ours")); err != nil {
+		if err := review.jirix.Git().Merge(branches[i], gitutil.SquashOpt(true), gitutil.StrategyOpt("ours")); err != nil {
 			return changeConflictError{
 				localBranch:  branches[i],
 				remoteBranch: review.CLOpts.RemoteBranch,
@@ -641,7 +639,7 @@
 		// 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 := review.ctx.Git().Log(branches[i], branches[i]+"^", "%ad%n%cd")
+		output, err := review.jirix.Git().Log(branches[i], branches[i]+"^", "%ad%n%cd")
 		if err != nil {
 			return err
 		}
@@ -651,37 +649,37 @@
 		authorDate := tool.AuthorDateOpt(output[0][0])
 		committerDate := tool.CommitterDateOpt(output[0][1])
 		if i < len(branches)-1 {
-			file, err := getCommitMessageFileName(review.ctx, branches[i])
+			file, err := getCommitMessageFileName(review.jirix, branches[i])
 			if err != nil {
 				return err
 			}
-			message, err := review.ctx.Run().ReadFile(file)
+			message, err := review.jirix.Run().ReadFile(file)
 			if err != nil {
 				return err
 			}
-			if err := review.ctx.Git(authorDate, committerDate).CommitWithMessage(string(message)); err != nil {
+			if err := review.jirix.Git(authorDate, committerDate).CommitWithMessage(string(message)); err != nil {
 				return err
 			}
 		} else {
-			committer := review.ctx.Git(authorDate, committerDate).NewCommitter(review.CLOpts.Edit)
+			committer := review.jirix.Git(authorDate, committerDate).NewCommitter(review.CLOpts.Edit)
 			if err := committer.Commit(message); err != nil {
 				return err
 			}
 		}
 		tmpBranch := review.reviewBranch + "-" + branches[i] + "-TMP"
-		if err := review.ctx.Git().CreateBranch(tmpBranch); err != nil {
+		if err := review.jirix.Git().CreateBranch(tmpBranch); err != nil {
 			return err
 		}
 		defer collect.Error(func() error {
-			return review.ctx.Git().DeleteBranch(tmpBranch, gitutil.ForceOpt(true))
+			return review.jirix.Git().DeleteBranch(tmpBranch, gitutil.ForceOpt(true))
 		}, &e)
-		if err := review.ctx.Git().Reset(branches[i]); err != nil {
+		if err := review.jirix.Git().Reset(branches[i]); err != nil {
 			return err
 		}
-		if err := review.ctx.Git().Reset(tmpBranch, gitutil.ModeOpt("soft")); err != nil {
+		if err := review.jirix.Git().Reset(tmpBranch, gitutil.ModeOpt("soft")); err != nil {
 			return err
 		}
-		if err := review.ctx.Git(authorDate, committerDate).CommitAmend(); err != nil {
+		if err := review.jirix.Git(authorDate, committerDate).CommitAmend(); err != nil {
 			return err
 		}
 	}
@@ -691,7 +689,7 @@
 // defaultCommitMessage creates the default commit message from the
 // list of commits on the branch.
 func (review *review) defaultCommitMessage() (string, error) {
-	commitMessages, err := review.ctx.Git().CommitMessages(review.CLOpts.Branch, review.reviewBranch)
+	commitMessages, err := review.jirix.Git().CommitMessages(review.CLOpts.Branch, review.reviewBranch)
 	if err != nil {
 		return "", err
 	}
@@ -706,7 +704,7 @@
 // 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 := review.ctx.Git().LatestCommitMessage()
+	latestCommitMessage, err := review.jirix.Git().LatestCommitMessage()
 	if err != nil {
 		return err
 	}
@@ -746,7 +744,7 @@
 // and then mails it to Gerrit.
 func (review *review) run() (e error) {
 	if uncommittedFlag {
-		changes, err := review.ctx.Git().FilesWithUncommittedChanges()
+		changes, err := review.jirix.Git().FilesWithUncommittedChanges()
 		if err != nil {
 			return err
 		}
@@ -757,7 +755,7 @@
 	if review.CLOpts.Branch == remoteBranchFlag {
 		return fmt.Errorf("cannot do a review from the %q branch.", remoteBranchFlag)
 	}
-	stashed, err := review.ctx.Git().Stash()
+	stashed, err := review.jirix.Git().Stash()
 	if err != nil {
 		return err
 	}
@@ -765,23 +763,23 @@
 	if err != nil {
 		return fmt.Errorf("Getwd() failed: %v", err)
 	}
-	defer collect.Error(func() error { return review.ctx.Run().Chdir(wd) }, &e)
-	topLevel, err := review.ctx.Git().TopLevel()
+	defer collect.Error(func() error { return review.jirix.Run().Chdir(wd) }, &e)
+	topLevel, err := review.jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
-	if err := review.ctx.Run().Chdir(topLevel); err != nil {
+	if err := review.jirix.Run().Chdir(topLevel); err != nil {
 		return err
 	}
 	defer collect.Error(func() error { return review.cleanup(stashed) }, &e)
-	file, err := getCommitMessageFileName(review.ctx, review.CLOpts.Branch)
+	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 := review.ctx.Run().ReadFile(file)
+		data, err := review.jirix.Run().ReadFile(file)
 		if err != nil {
 			if !os.IsNotExist(err) {
 				return err
@@ -820,12 +818,12 @@
 
 // send mails the current branch out for review.
 func (review *review) send() error {
-	if !review.ctx.DryRun() {
+	if !review.jirix.DryRun() {
 		if err := review.ensureChangeID(); err != nil {
 			return err
 		}
 	}
-	if err := gerrit.Push(review.ctx.Run(), review.CLOpts); err != nil {
+	if err := gerrit.Push(review.jirix.Run(), review.CLOpts); err != nil {
 		return gerritError(err.Error())
 	}
 	return nil
@@ -833,11 +831,11 @@
 
 // getChangeID reads the commit message and extracts the change-Id.
 func (review *review) getChangeID() (string, error) {
-	file, err := getCommitMessageFileName(review.ctx, review.CLOpts.Branch)
+	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
 	if err != nil {
 		return "", err
 	}
-	bytes, err := review.ctx.Run().ReadFile(file)
+	bytes, err := review.jirix.Run().ReadFile(file)
 	if err != nil {
 		return "", err
 	}
@@ -855,7 +853,7 @@
 	if err != nil {
 		return err
 	}
-	if err := review.ctx.Gerrit(review.CLOpts.Host).SetTopic(changeID, review.CLOpts); err != nil {
+	if err := review.jirix.Gerrit(review.CLOpts.Host).SetTopic(changeID, review.CLOpts); err != nil {
 		return err
 	}
 	return nil
@@ -863,10 +861,10 @@
 
 // updateReviewMessage writes the commit message to the given file.
 func (review *review) updateReviewMessage(file string) error {
-	if err := review.ctx.Git().CheckoutBranch(review.reviewBranch); err != nil {
+	if err := review.jirix.Git().CheckoutBranch(review.reviewBranch); err != nil {
 		return err
 	}
-	newMessage, err := review.ctx.Git().LatestCommitMessage()
+	newMessage, err := review.jirix.Git().LatestCommitMessage()
 	if err != nil {
 		return err
 	}
@@ -875,25 +873,25 @@
 	//
 	// This behavior is consistent with how Change-ID is added for the
 	// initial commit so we don't confuse users.
-	if _, err := review.ctx.Run().Stat(file); err != nil {
+	if _, err := review.jirix.Run().Stat(file); err != nil {
 		if os.IsNotExist(err) {
 			newMessage = review.processLabels(newMessage)
-			if err := review.ctx.Git().CommitAmendWithMessage(newMessage); err != nil {
+			if err := review.jirix.Git().CommitAmendWithMessage(newMessage); err != nil {
 				return err
 			}
 		} else {
 			return err
 		}
 	}
-	topLevel, err := review.ctx.Git().TopLevel()
+	topLevel, err := review.jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
 	newMetadataDir := filepath.Join(topLevel, project.MetadataDirName(), review.CLOpts.Branch)
-	if err := review.ctx.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
+	if err := review.jirix.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
 		return err
 	}
-	if err := review.ctx.Run().WriteFile(file, []byte(newMessage), 0644); err != nil {
+	if err := review.jirix.Run().WriteFile(file, []byte(newMessage), 0644); err != nil {
 		return err
 	}
 	return nil
@@ -901,7 +899,7 @@
 
 // cmdCLNew represents the "jiri cl new" command.
 var cmdCLNew = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runCLNew),
+	Runner: jiri.RunnerFunc(runCLNew),
 	Name:   "new",
 	Short:  "Create a new local branch for a changelist",
 	Long: fmt.Sprintf(`
@@ -916,27 +914,26 @@
 	ArgsLong: "<name> is the changelist name.",
 }
 
-func runCLNew(env *cmdline.Env, args []string) error {
+func runCLNew(jirix *jiri.X, args []string) error {
 	if got, want := len(args), 1; got != want {
-		return env.UsageErrorf("unexpected number of arguments: got %v, want %v", got, want)
+		return jirix.UsageErrorf("unexpected number of arguments: got %v, want %v", got, want)
 	}
-	ctx := tool.NewContextFromEnv(env)
-	return newCL(ctx, args)
+	return newCL(jirix, args)
 }
 
-func newCL(ctx *tool.Context, args []string) error {
-	topLevel, err := ctx.Git().TopLevel()
+func newCL(jirix *jiri.X, args []string) error {
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
-	originalBranch, err := ctx.Git().CurrentBranchName()
+	originalBranch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return err
 	}
 
 	// Create a new branch using the current branch.
 	newBranch := args[0]
-	if err := ctx.Git().CreateAndCheckoutBranch(newBranch); err != nil {
+	if err := jirix.Git().CreateAndCheckoutBranch(newBranch); err != nil {
 		return err
 	}
 
@@ -944,28 +941,28 @@
 	cleanup := true
 	defer func() {
 		if cleanup {
-			ctx.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
-			ctx.Git().DeleteBranch(newBranch, gitutil.ForceOpt(true))
+			jirix.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
+			jirix.Git().DeleteBranch(newBranch, gitutil.ForceOpt(true))
 		}
 	}()
 
 	// 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(ctx, originalBranch)
+	branches, err := getDependentCLs(jirix, originalBranch)
 	if err != nil {
 		return err
 	}
 	branches = append(branches, originalBranch)
 	newMetadataDir := filepath.Join(topLevel, project.MetadataDirName(), newBranch)
-	if err := ctx.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
+	if err := jirix.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
 		return err
 	}
-	file, err := getDependencyPathFileName(ctx, newBranch)
+	file, err := getDependencyPathFileName(jirix, newBranch)
 	if err != nil {
 		return err
 	}
-	if err := ctx.Run().WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)); err != nil {
+	if err := jirix.Run().WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)); err != nil {
 		return err
 	}
 
@@ -975,7 +972,7 @@
 
 // cmdCLSync represents the "jiri cl sync" command.
 var cmdCLSync = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runCLSync),
+	Runner: jiri.RunnerFunc(runCLSync),
 	Name:   "sync",
 	Short:  "Bring a changelist up to date",
 	Long: fmt.Sprintf(`
@@ -995,23 +992,22 @@
 `, project.MetadataDirName()),
 }
 
-func runCLSync(env *cmdline.Env, _ []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	return syncCL(ctx)
+func runCLSync(jirix *jiri.X, _ []string) error {
+	return syncCL(jirix)
 }
 
-func syncCL(ctx *tool.Context) (e error) {
-	stashed, err := ctx.Git().Stash()
+func syncCL(jirix *jiri.X) (e error) {
+	stashed, err := jirix.Git().Stash()
 	if err != nil {
 		return err
 	}
 	if stashed {
-		defer collect.Error(func() error { return ctx.Git().StashPop() }, &e)
+		defer collect.Error(func() error { return jirix.Git().StashPop() }, &e)
 	}
 
 	// Register a cleanup handler in case of subsequent errors.
 	forceOriginalBranch := true
-	originalBranch, err := ctx.Git().CurrentBranchName()
+	originalBranch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return err
 	}
@@ -1022,43 +1018,43 @@
 
 	defer func() {
 		if forceOriginalBranch {
-			ctx.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
+			jirix.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
 		}
-		ctx.Run().Chdir(originalWd)
+		jirix.Run().Chdir(originalWd)
 	}()
 
 	// Switch to an existing directory in master so we can run commands.
-	topLevel, err := ctx.Git().TopLevel()
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
-	if err := ctx.Run().Chdir(topLevel); err != nil {
+	if err := jirix.Run().Chdir(topLevel); err != nil {
 		return err
 	}
 
 	// Identify the dependents CLs leading to (and including) the
 	// current branch.
-	branches, err := getDependentCLs(ctx, originalBranch)
+	branches, err := getDependentCLs(jirix, originalBranch)
 	if err != nil {
 		return err
 	}
 	branches = append(branches, originalBranch)
 
 	// Sync from upstream.
-	if err := ctx.Git().CheckoutBranch(branches[0]); err != nil {
+	if err := jirix.Git().CheckoutBranch(branches[0]); err != nil {
 		return err
 	}
-	if err := ctx.Git().Pull("origin", branches[0]); err != nil {
+	if err := jirix.Git().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 := ctx.Git().CheckoutBranch(branches[i]); err != nil {
+		if err := jirix.Git().CheckoutBranch(branches[i]); err != nil {
 			return err
 		}
-		if err := ctx.Git().Merge(branches[i-1]); err != nil {
+		if err := jirix.Git().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
diff --git a/cl_test.go b/cl_test.go
index 9e998b6..586aef3 100644
--- a/cl_test.go
+++ b/cl_test.go
@@ -15,14 +15,14 @@
 
 	"v.io/jiri/gerrit"
 	"v.io/jiri/gitutil"
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
-	"v.io/jiri/tool"
 )
 
 // assertCommitCount asserts that the commit count between two
 // branches matches the expectedCount.
-func assertCommitCount(t *testing.T, ctx *tool.Context, branch, baseBranch string, expectedCount int) {
-	got, err := ctx.Git().CountCommits(branch, baseBranch)
+func assertCommitCount(t *testing.T, jirix *jiri.X, branch, baseBranch string, expectedCount int) {
+	got, err := jirix.Git().CountCommits(branch, baseBranch)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -33,8 +33,8 @@
 
 // assertFileContent asserts that the content of the given file
 // matches the expected content.
-func assertFileContent(t *testing.T, ctx *tool.Context, file, want string) {
-	got, err := ctx.Run().ReadFile(file)
+func assertFileContent(t *testing.T, jirix *jiri.X, file, want string) {
+	got, err := jirix.Run().ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -44,9 +44,9 @@
 }
 
 // assertFilesExist asserts that the files exist.
-func assertFilesExist(t *testing.T, ctx *tool.Context, files []string) {
+func assertFilesExist(t *testing.T, jirix *jiri.X, files []string) {
 	for _, file := range files {
-		if _, err := ctx.Run().Stat(file); err != nil {
+		if _, err := jirix.Run().Stat(file); err != nil {
 			if os.IsNotExist(err) {
 				t.Fatalf("expected file %v to exist but it did not", file)
 			}
@@ -56,9 +56,9 @@
 }
 
 // assertFilesDoNotExist asserts that the files do not exist.
-func assertFilesDoNotExist(t *testing.T, ctx *tool.Context, files []string) {
+func assertFilesDoNotExist(t *testing.T, jirix *jiri.X, files []string) {
 	for _, file := range files {
-		if _, err := ctx.Run().Stat(file); err != nil && !os.IsNotExist(err) {
+		if _, err := jirix.Run().Stat(file); err != nil && !os.IsNotExist(err) {
 			t.Fatalf("%v", err)
 		} else if err == nil {
 			t.Fatalf("expected file %v to not exist but it did", file)
@@ -68,10 +68,10 @@
 
 // assertFilesCommitted asserts that the files exist and are committed
 // in the current branch.
-func assertFilesCommitted(t *testing.T, ctx *tool.Context, files []string) {
-	assertFilesExist(t, ctx, files)
+func assertFilesCommitted(t *testing.T, jirix *jiri.X, files []string) {
+	assertFilesExist(t, jirix, files)
 	for _, file := range files {
-		if !ctx.Git().IsFileCommitted(file) {
+		if !jirix.Git().IsFileCommitted(file) {
 			t.Fatalf("expected file %v to be committed but it is not", file)
 		}
 	}
@@ -79,10 +79,10 @@
 
 // assertFilesNotCommitted asserts that the files exist and are *not*
 // committed in the current branch.
-func assertFilesNotCommitted(t *testing.T, ctx *tool.Context, files []string) {
-	assertFilesExist(t, ctx, files)
+func assertFilesNotCommitted(t *testing.T, jirix *jiri.X, files []string) {
+	assertFilesExist(t, jirix, files)
 	for _, file := range files {
-		if ctx.Git().IsFileCommitted(file) {
+		if jirix.Git().IsFileCommitted(file) {
 			t.Fatalf("expected file %v not to be committed but it is", file)
 		}
 	}
@@ -90,20 +90,20 @@
 
 // assertFilesPushedToRef asserts that the given files have been
 // pushed to the given remote repository reference.
-func assertFilesPushedToRef(t *testing.T, ctx *tool.Context, repoPath, gerritPath, pushedRef string, files []string) {
-	chdir(t, ctx, gerritPath)
-	assertCommitCount(t, ctx, pushedRef, "master", 1)
-	if err := ctx.Git().CheckoutBranch(pushedRef); err != nil {
+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 := jirix.Git().CheckoutBranch(pushedRef); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertFilesCommitted(t, ctx, files)
-	chdir(t, ctx, repoPath)
+	assertFilesCommitted(t, jirix, files)
+	chdir(t, jirix, repoPath)
 }
 
 // assertStashSize asserts that the stash size matches the expected
 // size.
-func assertStashSize(t *testing.T, ctx *tool.Context, want int) {
-	got, err := ctx.Git().StashSize()
+func assertStashSize(t *testing.T, jirix *jiri.X, want int) {
+	got, err := jirix.Git().StashSize()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -113,38 +113,38 @@
 }
 
 // commitFile commits a file with the specified content into a branch
-func commitFile(t *testing.T, ctx *tool.Context, filename string, content string) {
-	if err := ctx.Run().WriteFile(filename, []byte(content), 0644); err != nil {
+func commitFile(t *testing.T, jirix *jiri.X, filename string, content string) {
+	if err := jirix.Run().WriteFile(filename, []byte(content), 0644); err != nil {
 		t.Fatalf("%v", err)
 	}
 	commitMessage := "Commit " + filename
-	if err := ctx.Git().CommitFile(filename, commitMessage); err != nil {
+	if err := jirix.Git().CommitFile(filename, commitMessage); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
 // commitFiles commits the given files into to current branch.
-func commitFiles(t *testing.T, ctx *tool.Context, filenames []string) {
+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, ctx, filename, content)
+		commitFile(t, jirix, filename, content)
 	}
 }
 
 // createRepo creates a new repository in the given working directory.
-func createRepo(t *testing.T, ctx *tool.Context, workingDir, prefix string) string {
-	repoPath, err := ctx.Run().TempDir(workingDir, "repo-"+prefix)
+func createRepo(t *testing.T, jirix *jiri.X, workingDir, prefix string) string {
+	repoPath, err := jirix.Run().TempDir(workingDir, "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 := ctx.Git().Init(repoPath); err != nil {
+	if err := jirix.Git().Init(repoPath); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Run().MkdirAll(filepath.Join(repoPath, project.MetadataDirName()), os.FileMode(0755)); err != nil {
+	if err := jirix.Run().MkdirAll(filepath.Join(repoPath, project.MetadataDirName()), os.FileMode(0755)); err != nil {
 		t.Fatalf("%v", err)
 	}
 	return repoPath
@@ -157,29 +157,29 @@
 `
 
 // installCommitMsgHook links the gerrit commit-msg hook into a different repo.
-func installCommitMsgHook(t *testing.T, ctx *tool.Context, repoPath string) {
+func installCommitMsgHook(t *testing.T, jirix *jiri.X, repoPath string) {
 	hookLocation := path.Join(repoPath, ".git/hooks/commit-msg")
-	if err := ctx.Run().WriteFile(hookLocation, []byte(commitMsgHook), 0755); err != nil {
+	if err := jirix.Run().WriteFile(hookLocation, []byte(commitMsgHook), 0755); 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, ctx *tool.Context, path string) {
-	if err := ctx.Run().Chdir(path); err != nil {
+func chdir(t *testing.T, jirix *jiri.X, path string) {
+	if err := jirix.Run().Chdir(path); 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, ctx *tool.Context, workingDir string, subpath string, originPath string) string {
-	repoPath := createRepo(t, ctx, workingDir, subpath)
-	chdir(t, ctx, repoPath)
-	if err := ctx.Git().AddRemote("origin", originPath); err != nil {
+func createRepoFromOrigin(t *testing.T, jirix *jiri.X, workingDir string, subpath string, originPath string) string {
+	repoPath := createRepo(t, jirix, workingDir, subpath)
+	chdir(t, jirix, repoPath)
+	if err := jirix.Git().AddRemote("origin", originPath); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Pull("origin", "master"); err != nil {
+	if err := jirix.Git().Pull("origin", "master"); err != nil {
 		t.Fatalf("%v", err)
 	}
 	return repoPath
@@ -188,94 +188,92 @@
 // 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, ctx *tool.Context, workingDir string) (string, string, string) {
+func createTestRepos(t *testing.T, jirix *jiri.X, workingDir string) (string, string, string) {
 	// Create origin.
-	originPath := createRepo(t, ctx, workingDir, "origin")
-	chdir(t, ctx, originPath)
-	if err := ctx.Git().CommitWithMessage("initial commit"); err != nil {
+	originPath := createRepo(t, jirix, workingDir, "origin")
+	chdir(t, jirix, originPath)
+	if err := jirix.Git().CommitWithMessage("initial commit"); err != nil {
 		t.Fatalf("%v", err)
 	}
 	// Create test repo.
-	repoPath := createRepoFromOrigin(t, ctx, workingDir, "test", originPath)
+	repoPath := createRepoFromOrigin(t, jirix, workingDir, "test", originPath)
 	// Add Gerrit remote.
-	gerritPath := createRepoFromOrigin(t, ctx, workingDir, "gerrit", originPath)
+	gerritPath := createRepoFromOrigin(t, jirix, workingDir, "gerrit", originPath)
 	// Switch back to test repo.
-	chdir(t, ctx, repoPath)
+	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, ctx *tool.Context, originPath string, gerritPath string, review *review) {
+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, ctx, originPath)
+	chdir(t, jirix, originPath)
 	expectedRef := gerrit.Reference(review.CLOpts)
-	if err := ctx.Git().Pull(gerritPath, expectedRef); err != nil {
+	if err := jirix.Git().Pull(gerritPath, expectedRef); err != nil {
 		t.Fatalf("Pull gerrit to origin failed: %v", err)
 	}
-	chdir(t, ctx, cwd)
+	chdir(t, jirix, cwd)
 }
 
 // setupTest creates a setup for testing the review tool.
-func setupTest(t *testing.T, installHook bool) (ctx *tool.Context, cwd string, root *project.FakeJiriRoot, repoPath, originPath, gerritPath string) {
-	ctx = tool.NewDefaultContext()
-	cwd, err := os.Getwd()
-	if err != nil {
+func setupTest(t *testing.T, installHook bool) (cwd string, root *project.FakeJiriRoot, repoPath, originPath, gerritPath string) {
+	var err error
+	if cwd, err = os.Getwd(); err != nil {
 		t.Fatalf("Getwd() failed: %v", err)
 	}
-	root, err = project.NewFakeJiriRoot(ctx)
-	if err != nil {
+	if root, err = project.NewFakeJiriRoot(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	repoPath, originPath, gerritPath = createTestRepos(t, ctx, root.Dir)
+	repoPath, originPath, gerritPath = createTestRepos(t, root.X, root.Dir)
 	if installHook == true {
 		for _, path := range []string{repoPath, originPath, gerritPath} {
-			installCommitMsgHook(t, ctx, path)
+			installCommitMsgHook(t, root.X, path)
 		}
 	}
-	chdir(t, ctx, repoPath)
+	chdir(t, root.X, repoPath)
 	return
 }
 
 // teardownTest cleans up the setup for testing the review tool.
-func teardownTest(t *testing.T, ctx *tool.Context, oldWorkDir string, root *project.FakeJiriRoot) {
-	chdir(t, ctx, oldWorkDir)
-	if err := root.Cleanup(ctx); err != nil {
+func teardownTest(t *testing.T, oldWorkDir string, root *project.FakeJiriRoot) {
+	chdir(t, root.X, oldWorkDir)
+	if err := root.Cleanup(); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func createCLWithFiles(t *testing.T, ctx *tool.Context, branch string, files ...string) {
-	if err := newCL(ctx, []string{branch}); err != nil {
+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, ctx, files)
+	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) {
-	ctx, cwd, root, repoPath, originPath, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, originPath, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
-	commitFiles(t, ctx, []string{"file1", "file2"})
-	if err := ctx.Git().CheckoutBranch("master"); err != nil {
+	commitFiles(t, root.X, []string{"file1", "file2"})
+	if err := root.X.Git().CheckoutBranch("master"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	chdir(t, ctx, originPath)
-	commitFiles(t, ctx, []string{"file1", "file2"})
-	chdir(t, ctx, repoPath)
-	if err := cleanupCL(ctx, []string{branch}); err != nil {
+	chdir(t, root.X, originPath)
+	commitFiles(t, root.X, []string{"file1", "file2"})
+	chdir(t, root.X, repoPath)
+	if err := cleanupCL(root.X, []string{branch}); err != nil {
 		t.Fatalf("cleanup() failed: %v", err)
 	}
-	if ctx.Git().BranchExists(branch) {
+	if root.X.Git().BranchExists(branch) {
 		t.Fatalf("cleanup failed to remove the feature branch")
 	}
 }
@@ -283,38 +281,38 @@
 // TestCleanupDirty checks that cleanup is a no-op if the branch to be
 // cleaned up has unmerged changes.
 func TestCleanupDirty(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	files := []string{"file1", "file2"}
-	commitFiles(t, ctx, files)
-	if err := ctx.Git().CheckoutBranch("master"); err != nil {
+	commitFiles(t, root.X, files)
+	if err := root.X.Git().CheckoutBranch("master"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := cleanupCL(ctx, []string{branch}); err == nil {
+	if err := cleanupCL(root.X, []string{branch}); err == nil {
 		t.Fatalf("cleanup did not fail when it should")
 	}
-	if err := ctx.Git().CheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertFilesCommitted(t, ctx, files)
+	assertFilesCommitted(t, root.X, files)
 }
 
 // TestCreateReviewBranch checks that the temporary review branch is
 // created correctly.
 func TestCreateReviewBranch(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, ctx, files)
-	review, err := newReview(ctx, gerrit.CLOpts{})
+	commitFiles(t, root.X, files)
+	review, err := newReview(root.X, gerrit.CLOpts{})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -326,27 +324,27 @@
 		t.Fatalf("%v", err)
 	}
 	// Verify that the branch exists.
-	if !ctx.Git().BranchExists(review.reviewBranch) {
+	if !root.X.Git().BranchExists(review.reviewBranch) {
 		t.Fatalf("review branch not found")
 	}
-	if err := ctx.Git().CheckoutBranch(review.reviewBranch); err != nil {
+	if err := root.X.Git().CheckoutBranch(review.reviewBranch); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertCommitCount(t, ctx, review.reviewBranch, "master", 1)
-	assertFilesCommitted(t, ctx, files)
+	assertCommitCount(t, root.X, review.reviewBranch, "master", 1)
+	assertFilesCommitted(t, root.X, files)
 }
 
 // TestCreateReviewBranchWithEmptyChange checks that running
 // createReviewBranch() on a branch with no changes will result in an
 // EmptyChangeError.
 func TestCreateReviewBranchWithEmptyChange(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
-	review, err := newReview(ctx, gerrit.CLOpts{Remote: branch})
+	review, err := newReview(root.X, gerrit.CLOpts{Remote: branch})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -362,17 +360,17 @@
 
 // TestSendReview checks the various options for sending a review.
 func TestSendReview(t *testing.T) {
-	ctx, cwd, root, repoPath, _, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, _, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	files := []string{"file1"}
-	commitFiles(t, ctx, files)
+	commitFiles(t, root.X, files)
 	{
 		// Test with draft = false, no reviewiers, and no ccs.
-		review, err := newReview(ctx, gerrit.CLOpts{Remote: gerritPath})
+		review, err := newReview(root.X, gerrit.CLOpts{Remote: gerritPath})
 		if err != nil {
 			t.Fatalf("%v", err)
 		}
@@ -380,11 +378,11 @@
 			t.Fatalf("failed to send a review: %v", err)
 		}
 		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+		assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 	}
 	{
 		// Test with draft = true, no reviewers, and no ccs.
-		review, err := newReview(ctx, gerrit.CLOpts{
+		review, err := newReview(root.X, gerrit.CLOpts{
 			Draft:  true,
 			Remote: gerritPath,
 		})
@@ -395,11 +393,11 @@
 			t.Fatalf("failed to send a review: %v", err)
 		}
 		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+		assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 	}
 	{
 		// Test with draft = false, reviewers, and no ccs.
-		review, err := newReview(ctx, gerrit.CLOpts{
+		review, err := newReview(root.X, gerrit.CLOpts{
 			Remote:    gerritPath,
 			Reviewers: parseEmails("reviewer1,reviewer2@example.org"),
 		})
@@ -410,11 +408,11 @@
 			t.Fatalf("failed to send a review: %v", err)
 		}
 		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+		assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 	}
 	{
 		// Test with draft = true, reviewers, and ccs.
-		review, err := newReview(ctx, gerrit.CLOpts{
+		review, err := newReview(root.X, gerrit.CLOpts{
 			Ccs:       parseEmails("cc1@example.org,cc2"),
 			Draft:     true,
 			Remote:    gerritPath,
@@ -427,7 +425,7 @@
 			t.Fatalf("failed to send a review: %v", err)
 		}
 		expectedRef := gerrit.Reference(review.CLOpts)
-		assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+		assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 	}
 }
 
@@ -435,14 +433,14 @@
 // 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.
-	ctx, cwd, root, _, _, gerritPath := setupTest(t, false)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, gerritPath := setupTest(t, false)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
-	commitFiles(t, ctx, []string{"file1"})
-	review, err := newReview(ctx, gerrit.CLOpts{Remote: gerritPath})
+	commitFiles(t, root.X, []string{"file1"})
+	review, err := newReview(root.X, gerrit.CLOpts{Remote: gerritPath})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -457,15 +455,15 @@
 
 // TestEndToEnd checks the end-to-end functionality of the review tool.
 func TestEndToEnd(t *testing.T) {
-	ctx, cwd, root, repoPath, _, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, _, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, ctx, files)
-	review, err := newReview(ctx, gerrit.CLOpts{Remote: gerritPath})
+	commitFiles(t, root.X, files)
+	review, err := newReview(root.X, gerrit.CLOpts{Remote: gerritPath})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -474,7 +472,7 @@
 		t.Fatalf("run() failed: %v", err)
 	}
 	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+	assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 }
 
 // TestLabelsInCommitMessage checks the labels are correctly processed
@@ -492,17 +490,17 @@
 // fact that the reference name is a function of the reviewers and
 // uses different reviewers for different review runs.
 func TestLabelsInCommitMessage(t *testing.T) {
-	ctx, cwd, root, repoPath, _, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, _, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Test setting -presubmit=none and autosubmit.
 	files := []string{"file1", "file2", "file3"}
-	commitFiles(t, ctx, files)
-	review, err := newReview(ctx, gerrit.CLOpts{
+	commitFiles(t, root.X, files)
+	review, err := newReview(root.X, gerrit.CLOpts{
 		Autosubmit: true,
 		Presubmit:  gerrit.PresubmitTestTypeNone,
 		Remote:     gerritPath,
@@ -516,16 +514,16 @@
 		t.Fatalf("%v", err)
 	}
 	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+	assertFilesPushedToRef(t, root.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.ctx, review.CLOpts.Branch)
+	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	bytes, err := ctx.Run().ReadFile(file)
+	bytes, err := root.X.Run().ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -545,7 +543,7 @@
 	}
 
 	// Test setting -presubmit=all but keep autosubmit=true.
-	review, err = newReview(ctx, gerrit.CLOpts{
+	review, err = newReview(root.X, gerrit.CLOpts{
 		Autosubmit: true,
 		Remote:     gerritPath,
 		Reviewers:  parseEmails("run2"),
@@ -557,8 +555,8 @@
 		t.Fatalf("%v", err)
 	}
 	expectedRef = gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
-	bytes, err = ctx.Run().ReadFile(file)
+	assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
+	bytes, err = root.X.Run().ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -574,7 +572,7 @@
 	}
 
 	// Test setting autosubmit=false.
-	review, err = newReview(ctx, gerrit.CLOpts{
+	review, err = newReview(root.X, gerrit.CLOpts{
 		Remote:    gerritPath,
 		Reviewers: parseEmails("run3"),
 	})
@@ -585,8 +583,8 @@
 		t.Fatalf("%v", err)
 	}
 	expectedRef = gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
-	bytes, err = ctx.Run().ReadFile(file)
+	assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
+	bytes, err = root.X.Run().ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -601,42 +599,42 @@
 // TestDirtyBranch checks that the tool correctly handles unstaged and
 // untracked changes in a working branch with stashed changes.
 func TestDirtyBranch(t *testing.T) {
-	ctx, cwd, root, _, _, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	files := []string{"file1", "file2"}
-	commitFiles(t, ctx, files)
-	assertStashSize(t, ctx, 0)
+	commitFiles(t, root.X, files)
+	assertStashSize(t, root.X, 0)
 	stashedFile, stashedFileContent := "stashed-file", "stashed-file content"
-	if err := ctx.Run().WriteFile(stashedFile, []byte(stashedFileContent), 0644); err != nil {
+	if err := root.X.Run().WriteFile(stashedFile, []byte(stashedFileContent), 0644); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", stashedFile, stashedFileContent, err)
 	}
-	if err := ctx.Git().Add(stashedFile); err != nil {
+	if err := root.X.Git().Add(stashedFile); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if _, err := ctx.Git().Stash(); err != nil {
+	if _, err := root.X.Git().Stash(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertStashSize(t, ctx, 1)
+	assertStashSize(t, root.X, 1)
 	modifiedFile, modifiedFileContent := "file1", "modified-file content"
-	if err := ctx.Run().WriteFile(modifiedFile, []byte(modifiedFileContent), 0644); err != nil {
+	if err := root.X.Run().WriteFile(modifiedFile, []byte(modifiedFileContent), 0644); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", modifiedFile, modifiedFileContent, err)
 	}
 	stagedFile, stagedFileContent := "file2", "staged-file content"
-	if err := ctx.Run().WriteFile(stagedFile, []byte(stagedFileContent), 0644); err != nil {
+	if err := root.X.Run().WriteFile(stagedFile, []byte(stagedFileContent), 0644); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", stagedFile, stagedFileContent, err)
 	}
-	if err := ctx.Git().Add(stagedFile); err != nil {
+	if err := root.X.Git().Add(stagedFile); err != nil {
 		t.Fatalf("%v", err)
 	}
 	untrackedFile, untrackedFileContent := "file3", "untracked-file content"
-	if err := ctx.Run().WriteFile(untrackedFile, []byte(untrackedFileContent), 0644); err != nil {
+	if err := root.X.Run().WriteFile(untrackedFile, []byte(untrackedFileContent), 0644); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", untrackedFile, untrackedFileContent, err)
 	}
-	review, err := newReview(ctx, gerrit.CLOpts{Remote: gerritPath})
+	review, err := newReview(root.X, gerrit.CLOpts{Remote: gerritPath})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -644,44 +642,44 @@
 	if err := review.run(); err == nil {
 		t.Fatalf("run() didn't fail when it should")
 	}
-	assertFilesNotCommitted(t, ctx, []string{stagedFile})
-	assertFilesNotCommitted(t, ctx, []string{untrackedFile})
-	assertFileContent(t, ctx, modifiedFile, modifiedFileContent)
-	assertFileContent(t, ctx, stagedFile, stagedFileContent)
-	assertFileContent(t, ctx, untrackedFile, untrackedFileContent)
+	assertFilesNotCommitted(t, root.X, []string{stagedFile})
+	assertFilesNotCommitted(t, root.X, []string{untrackedFile})
+	assertFileContent(t, root.X, modifiedFile, modifiedFileContent)
+	assertFileContent(t, root.X, stagedFile, stagedFileContent)
+	assertFileContent(t, root.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 := ctx.Git().Commit(); err != nil {
+	if err := root.X.Git().Commit(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertStashSize(t, ctx, 1)
-	if err := ctx.Git().StashPop(); err != nil {
+	assertStashSize(t, root.X, 1)
+	if err := root.X.Git().StashPop(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertStashSize(t, ctx, 0)
-	assertFilesNotCommitted(t, ctx, []string{stashedFile})
-	assertFileContent(t, ctx, stashedFile, stashedFileContent)
+	assertStashSize(t, root.X, 0)
+	assertFilesNotCommitted(t, root.X, []string{stashedFile})
+	assertFileContent(t, root.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) {
-	ctx, cwd, root, repoPath, _, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, _, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	branch := "my-branch"
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := root.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	subdir := "sub/directory"
 	subdirPerms := os.FileMode(0744)
-	if err := ctx.Run().MkdirAll(subdir, subdirPerms); err != nil {
+	if err := root.X.Run().MkdirAll(subdir, subdirPerms); err != nil {
 		t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err)
 	}
 	files := []string{path.Join(subdir, "file1")}
-	commitFiles(t, ctx, files)
-	chdir(t, ctx, subdir)
-	review, err := newReview(ctx, gerrit.CLOpts{Remote: gerritPath})
+	commitFiles(t, root.X, files)
+	chdir(t, root.X, subdir)
+	review, err := newReview(root.X, gerrit.CLOpts{Remote: gerritPath})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -706,13 +704,13 @@
 		t.Fatalf("unexpected working directory: got %v, want %v", got, want)
 	}
 	expectedRef := gerrit.Reference(review.CLOpts)
-	assertFilesPushedToRef(t, ctx, repoPath, gerritPath, expectedRef, files)
+	assertFilesPushedToRef(t, root.X, repoPath, gerritPath, expectedRef, files)
 }
 
 // TestProcessLabels checks that the processLabels function works as expected.
 func TestProcessLabels(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 	testCases := []struct {
 		autosubmit      bool
 		presubmitType   gerrit.PresubmitTestType
@@ -784,7 +782,7 @@
 		},
 	}
 	for _, test := range testCases {
-		review, err := newReview(ctx, gerrit.CLOpts{
+		review, err := newReview(root.X, gerrit.CLOpts{
 			Autosubmit: test.autosubmit,
 			Presubmit:  test.presubmitType,
 		})
@@ -799,14 +797,14 @@
 
 // TestCLNew checks the operation of the "jiri cl new" command.
 func TestCLNew(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 
 	// Create some dependent CLs.
-	if err := newCL(ctx, []string{"feature1"}); err != nil {
+	if err := newCL(root.X, []string{"feature1"}); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := newCL(ctx, []string{"feature2"}); err != nil {
+	if err := newCL(root.X, []string{"feature2"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 
@@ -825,11 +823,11 @@
 		},
 	}
 	for _, testCase := range testCases {
-		file, err := getDependencyPathFileName(ctx, testCase.branch)
+		file, err := getDependencyPathFileName(root.X, testCase.branch)
 		if err != nil {
 			t.Fatalf("%v", err)
 		}
-		data, err := ctx.Run().ReadFile(file)
+		data, err := root.X.Run().ReadFile(file)
 		if err != nil {
 			t.Fatalf("%v", err)
 		}
@@ -843,28 +841,28 @@
 // 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) {
-	ctx, cwd, root, repoPath, originPath, gerritPath := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
-	chdir(t, ctx, originPath)
-	commitFiles(t, ctx, []string{"A", "B"})
+	cwd, root, repoPath, originPath, gerritPath := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
+	chdir(t, root.X, originPath)
+	commitFiles(t, root.X, []string{"A", "B"})
 
-	chdir(t, ctx, repoPath)
-	if err := syncCL(ctx); err != nil {
+	chdir(t, root.X, repoPath)
+	if err := syncCL(root.X); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertFilesExist(t, ctx, []string{"A", "B"})
+	assertFilesExist(t, root.X, []string{"A", "B"})
 
-	createCLWithFiles(t, ctx, "editme", "C")
-	if err := ctx.Run().WriteFile("B", []byte("Will I dream?"), 0644); err != nil {
+	createCLWithFiles(t, root.X, "editme", "C")
+	if err := root.X.Run().WriteFile("B", []byte("Will I dream?"), 0644); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Add("B"); err != nil {
+	if err := root.X.Git().Add("B"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CommitWithMessage("editing stuff"); err != nil {
+	if err := root.X.Git().CommitWithMessage("editing stuff"); err != nil {
 		t.Fatalf("git commit failed: %v", err)
 	}
-	review, err := newReview(ctx, gerrit.CLOpts{
+	review, err := newReview(root.X, gerrit.CLOpts{
 		Remote:    gerritPath,
 		Reviewers: parseEmails("run1"), // See hack note about TestLabelsInCommitMessage
 	})
@@ -876,16 +874,16 @@
 		t.Fatalf("run() failed: %v", err)
 	}
 
-	if err := newCL(ctx, []string{"deleteme"}); err != nil {
+	if err := newCL(root.X, []string{"deleteme"}); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Remove("B", "C"); err != nil {
+	if err := root.X.Git().Remove("B", "C"); err != nil {
 		t.Fatalf("git rm B C failed: %v", err)
 	}
-	if err := ctx.Git().CommitWithMessage("deleting stuff"); err != nil {
+	if err := root.X.Git().CommitWithMessage("deleting stuff"); err != nil {
 		t.Fatalf("git commit failed: %v", err)
 	}
-	review, err = newReview(ctx, gerrit.CLOpts{
+	review, err = newReview(root.X, gerrit.CLOpts{
 		Remote:    gerritPath,
 		Reviewers: parseEmails("run2"),
 	})
@@ -896,35 +894,35 @@
 		t.Fatalf("run() failed: %v", err)
 	}
 
-	chdir(t, ctx, gerritPath)
+	chdir(t, root.X, gerritPath)
 	expectedRef := gerrit.Reference(review.CLOpts)
-	if err := ctx.Git().CheckoutBranch(expectedRef); err != nil {
+	if err := root.X.Git().CheckoutBranch(expectedRef); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertFilesExist(t, ctx, []string{"A"})
-	assertFilesDoNotExist(t, ctx, []string{"B", "C"})
+	assertFilesExist(t, root.X, []string{"A"})
+	assertFilesDoNotExist(t, root.X, []string{"B", "C"})
 }
 
 // TestParallelDev checks "jiri cl mail" behavior when parallel development has
 // been submitted upstream.
 func TestParallelDev(t *testing.T) {
-	ctx, cwd, root, repoPath, originPath, gerritAPath := setupTest(t, true)
-	gerritBPath := createRepoFromOrigin(t, ctx, root.Dir, "gerritB", originPath)
-	chdir(t, ctx, repoPath)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, repoPath, originPath, gerritAPath := setupTest(t, true)
+	gerritBPath := createRepoFromOrigin(t, root.X, root.Dir, "gerritB", originPath)
+	chdir(t, root.X, repoPath)
+	defer teardownTest(t, cwd, root)
 
 	// Create parallel branches with:
 	// * non-conflicting changes in different files
 	// * conflicting changes in a file
-	createCLWithFiles(t, ctx, "feature1-A", "A")
+	createCLWithFiles(t, root.X, "feature1-A", "A")
 
-	if err := ctx.Git().CheckoutBranch("master"); err != nil {
+	if err := root.X.Git().CheckoutBranch("master"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	createCLWithFiles(t, ctx, "feature1-B", "B")
-	commitFile(t, ctx, "A", "Don't tread on me.")
+	createCLWithFiles(t, root.X, "feature1-B", "B")
+	commitFile(t, root.X, "A", "Don't tread on me.")
 
-	reviewB, err := newReview(ctx, gerrit.CLOpts{Remote: gerritBPath})
+	reviewB, err := newReview(root.X, gerrit.CLOpts{Remote: gerritBPath})
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -934,48 +932,48 @@
 	}
 
 	// Submit B and verify A doesn't revert it.
-	submit(t, ctx, originPath, gerritBPath, reviewB)
+	submit(t, root.X, originPath, gerritBPath, reviewB)
 
 	// Assert files pushed to origin.
-	chdir(t, ctx, originPath)
-	assertFilesExist(t, ctx, []string{"A", "B"})
-	chdir(t, ctx, repoPath)
+	chdir(t, root.X, originPath)
+	assertFilesExist(t, root.X, []string{"A", "B"})
+	chdir(t, root.X, repoPath)
 
-	if err := ctx.Git().CheckoutBranch("feature1-A"); err != nil {
+	if err := root.X.Git().CheckoutBranch("feature1-A"); err != nil {
 		t.Fatalf("%v", err)
 	}
 
-	reviewA, err := newReview(ctx, gerrit.CLOpts{Remote: gerritAPath})
+	reviewA, err := newReview(root.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, ctx, "A", "This is file A")
-	assertFilesDoNotExist(t, ctx, []string{"B"})
+	assertFileContent(t, root.X, "A", "This is file A")
+	assertFilesDoNotExist(t, root.X, []string{"B"})
 
 	// Manual conflict resolution.
-	if err := ctx.Git().Merge("master", gitutil.ResetOnFailureOpt(false)); err == nil {
+	if err := root.X.Git().Merge("master", gitutil.ResetOnFailureOpt(false)); err == nil {
 		t.Fatalf("merge applied cleanly when it shouldn't")
 	}
-	assertFilesNotCommitted(t, ctx, []string{"A", "B"})
-	assertFileContent(t, ctx, "B", "This is file B")
+	assertFilesNotCommitted(t, root.X, []string{"A", "B"})
+	assertFileContent(t, root.X, "B", "This is file B")
 
-	if err := ctx.Run().WriteFile("A", []byte("This is file A. Don't tread on me."), 0644); err != nil {
+	if err := root.X.Run().WriteFile("A", []byte("This is file A. Don't tread on me."), 0644); err != nil {
 		t.Fatalf("%v", err)
 	}
 
-	if err := ctx.Git().Add("A"); err != nil {
+	if err := root.X.Git().Add("A"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Add("B"); err != nil {
+	if err := root.X.Git().Add("B"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CommitWithMessage("Conflict resolution"); err != nil {
+	if err := root.X.Git().CommitWithMessage("Conflict resolution"); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Retry review.
-	reviewA, err = newReview(ctx, gerrit.CLOpts{Remote: gerritAPath})
+	reviewA, err = newReview(root.X, gerrit.CLOpts{Remote: gerritAPath})
 	if err != nil {
 		t.Fatalf("review failed: %v", err)
 	}
@@ -984,46 +982,46 @@
 		t.Fatalf("run() failed: %v", err)
 	}
 
-	chdir(t, ctx, gerritAPath)
+	chdir(t, root.X, gerritAPath)
 	expectedRef := gerrit.Reference(reviewA.CLOpts)
-	if err := ctx.Git().CheckoutBranch(expectedRef); err != nil {
+	if err := root.X.Git().CheckoutBranch(expectedRef); err != nil {
 		t.Fatalf("%v", err)
 	}
-	assertFilesExist(t, ctx, []string{"B"})
+	assertFilesExist(t, root.X, []string{"B"})
 }
 
 // TestCLSync checks the operation of the "jiri cl sync" command.
 func TestCLSync(t *testing.T) {
-	ctx, cwd, root, _, _, _ := setupTest(t, true)
-	defer teardownTest(t, ctx, cwd, root)
+	cwd, root, _, _, _ := setupTest(t, true)
+	defer teardownTest(t, cwd, root)
 
 	// Create some dependent CLs.
-	if err := newCL(ctx, []string{"feature1"}); err != nil {
+	if err := newCL(root.X, []string{"feature1"}); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := newCL(ctx, []string{"feature2"}); err != nil {
+	if err := newCL(root.X, []string{"feature2"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Add the "test" file to the master.
-	if err := ctx.Git().CheckoutBranch("master"); err != nil {
+	if err := root.X.Git().CheckoutBranch("master"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	commitFiles(t, ctx, []string{"test"})
+	commitFiles(t, root.X, []string{"test"})
 
 	// Sync the dependent CLs.
-	if err := ctx.Git().CheckoutBranch("feature2"); err != nil {
+	if err := root.X.Git().CheckoutBranch("feature2"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := syncCL(ctx); err != nil {
+	if err := syncCL(root.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 := ctx.Git().CheckoutBranch(branch); err != nil {
+		if err := root.X.Git().CheckoutBranch(branch); err != nil {
 			t.Fatalf("%v", err)
 		}
-		assertFilesExist(t, ctx, []string{"test"})
+		assertFilesExist(t, root.X, []string{"test"})
 	}
 }
diff --git a/cmd.go b/cmd.go
index a3f6e93..0090f3b 100644
--- a/cmd.go
+++ b/cmd.go
@@ -8,7 +8,6 @@
 package main
 
 import (
-	"fmt"
 	"runtime"
 
 	"v.io/jiri/tool"
@@ -40,26 +39,12 @@
 		cmdRebuild,
 		cmdSnapshot,
 		cmdUpdate,
-		cmdVersion,
 	},
 	Topics: []cmdline.Topic{
 		topicManifest,
 	},
 }
 
-// cmdVersion represents the "jiri version" command.
-var cmdVersion = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runVersion),
-	Name:   "version",
-	Short:  "Print version",
-	Long:   "Print version of the jiri tool.",
-}
-
-func runVersion(env *cmdline.Env, _ []string) error {
-	fmt.Fprintf(env.Stdout, "jiri tool version %v\n", tool.Version)
-	return nil
-}
-
 var topicManifest = cmdline.Topic{
 	Name:  "manifest",
 	Short: "Description of manifest files",
diff --git a/contrib.go b/contrib.go
index 8210f33..066850a 100644
--- a/contrib.go
+++ b/contrib.go
@@ -14,6 +14,7 @@
 	"strings"
 
 	"v.io/jiri/collect"
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
 	"v.io/x/lib/cmdline"
@@ -36,7 +37,7 @@
 
 // cmdContributors represents the "jiri contributors" command.
 var cmdContributors = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runContributors),
+	Runner: jiri.RunnerFunc(runContributors),
 	Name:   "contributors",
 	Short:  "List project contributors",
 	Long: `
@@ -90,16 +91,16 @@
 	return canonicalEmail, canonicalName
 }
 
-func loadAliases(ctx *tool.Context) (*aliasMaps, error) {
+func loadAliases(jirix *jiri.X) (*aliasMaps, error) {
 	aliasesFile := aliasesFlag
 	if aliasesFile == "" {
-		dataDir, err := project.DataDirPath(ctx, tool.Name)
+		dataDir, err := project.DataDirPath(jirix, tool.Name)
 		if err != nil {
 			return nil, err
 		}
 		aliasesFile = filepath.Join(dataDir, aliasesFileName)
 	}
-	bytes, err := ctx.Run().ReadFile(aliasesFile)
+	bytes, err := jirix.Run().ReadFile(aliasesFile)
 	if err != nil {
 		return nil, err
 	}
@@ -124,10 +125,8 @@
 	return aliases, nil
 }
 
-func runContributors(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-
-	projects, err := project.LocalProjects(ctx, project.FastScan)
+func runContributors(jirix *jiri.X, args []string) error {
+	projects, err := project.LocalProjects(jirix, project.FastScan)
 	if err != nil {
 		return err
 	}
@@ -140,7 +139,7 @@
 		}
 	}
 
-	aliases, err := loadAliases(ctx)
+	aliases, err := loadAliases(jirix)
 	if err != nil {
 		return err
 	}
@@ -150,12 +149,12 @@
 		if !ok {
 			continue
 		}
-		if err := ctx.Run().Chdir(project.Path); err != nil {
+		if err := jirix.Run().Chdir(project.Path); err != nil {
 			return err
 		}
 		switch project.Protocol {
 		case "git":
-			lines, err := listCommitters(ctx)
+			lines, err := listCommitters(jirix)
 			if err != nil {
 				return err
 			}
@@ -193,28 +192,28 @@
 	for _, name := range names {
 		c := contributors[name]
 		if countFlag {
-			fmt.Fprintf(env.Stdout, "%4d ", c.count)
+			fmt.Fprintf(jirix.Stdout(), "%4d ", c.count)
 		}
-		fmt.Fprintf(env.Stdout, "%v <%v>\n", c.name, c.email)
+		fmt.Fprintf(jirix.Stdout(), "%v <%v>\n", c.name, c.email)
 	}
 	return nil
 }
 
-func listCommitters(ctx *tool.Context) (_ []string, e error) {
-	branch, err := ctx.Git().CurrentBranchName()
+func listCommitters(jirix *jiri.X) (_ []string, e error) {
+	branch, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return nil, err
 	}
-	stashed, err := ctx.Git().Stash()
+	stashed, err := jirix.Git().Stash()
 	if err != nil {
 		return nil, err
 	}
 	if stashed {
-		defer collect.Error(func() error { return ctx.Git().StashPop() }, &e)
+		defer collect.Error(func() error { return jirix.Git().StashPop() }, &e)
 	}
-	if err := ctx.Git().CheckoutBranch("master"); err != nil {
+	if err := jirix.Git().CheckoutBranch("master"); err != nil {
 		return nil, err
 	}
-	defer collect.Error(func() error { return ctx.Git().CheckoutBranch(branch) }, &e)
-	return ctx.Git().Committers()
+	defer collect.Error(func() error { return jirix.Git().CheckoutBranch(branch) }, &e)
+	return jirix.Git().Committers()
 }
diff --git a/doc.go b/doc.go
index 3940c48..b41ea96 100644
--- a/doc.go
+++ b/doc.go
@@ -18,7 +18,6 @@
    rebuild      Rebuild all jiri tools
    snapshot     Manage project snapshots
    update       Update all jiri tools and projects
-   version      Print version
    help         Display help for commands or topics
 
 The jiri additional help topics are:
@@ -339,13 +338,6 @@
  -manifest=
    Name of the project manifest.
 
-Jiri version - Print version
-
-Print version of the jiri tool.
-
-Usage:
-   jiri version
-
 Jiri help - Display help for commands or topics
 
 Help with no args displays the usage of the parent command.
diff --git a/jiri/jiritest/x.go b/jiri/jiritest/x.go
new file mode 100644
index 0000000..3f680b6
--- /dev/null
+++ b/jiri/jiritest/x.go
@@ -0,0 +1,32 @@
+// 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 jiritest provides utilities for testing jiri functionality.
+package jiritest
+
+import (
+	"os"
+	"testing"
+
+	"v.io/jiri/jiri"
+	"v.io/jiri/tool"
+)
+
+// NewX is similar to jiri.NewX, but is meant for usage in a testing environment.
+func NewX(t *testing.T) (*jiri.X, func()) {
+	ctx := tool.NewDefaultContext()
+	root, err := ctx.NewSeq().TempDir("", "")
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	oldEnv := os.Getenv("JIRI_ROOT")
+	if err := os.Setenv("JIRI_ROOT", root); err != nil {
+		t.Fatalf("Setenv(JIRI_ROOT) failed: %v", err)
+	}
+	cleanup := func() {
+		os.Setenv("JIRI_ROOT", oldEnv)
+		ctx.NewSeq().RemoveAll(root).Done()
+	}
+	return &jiri.X{Context: ctx, Root: root}, cleanup
+}
diff --git a/jiri/x.go b/jiri/x.go
new file mode 100644
index 0000000..5b5e0c9
--- /dev/null
+++ b/jiri/x.go
@@ -0,0 +1,104 @@
+// 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 jiri provides utilities used by the jiri tool and related tools.
+package jiri
+
+// TODO(toddw): Rename this package to v.io/jiri, and rename the tool itself to
+// v.io/jiri/cmd/jiri
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"v.io/jiri/tool"
+	"v.io/x/lib/cmdline"
+)
+
+const (
+	rootEnv = "JIRI_ROOT"
+)
+
+// X holds the execution environment for the jiri tool and related tools.  This
+// includes the jiri filesystem root directory.
+//
+// TODO(toddw): The Root is not currently used; we still use project.JiriRoot
+// everywhere.  Transition those uses gradually over to use this instead.
+//
+// TODO(toddw): Other jiri state should be transitioned to this struct,
+// including the manifest and related operations.
+type X struct {
+	*tool.Context
+	Root  string
+	Usage func(format string, args ...interface{}) error
+}
+
+// NewX returns a new execution environment, given a cmdline env.
+func NewX(env *cmdline.Env) (*X, error) {
+	ctx := tool.NewContextFromEnv(env)
+	root, err := findJiriRoot(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &X{
+		Context: ctx,
+		Root:    root,
+		Usage:   env.UsageErrorf,
+	}, nil
+}
+
+func findJiriRoot(ctx *tool.Context) (string, error) {
+	ctx.TimerPush("find JIRI_ROOT")
+	defer ctx.TimerPop()
+	if root := os.Getenv(rootEnv); root != "" {
+		// Always use JIRI_ROOT if it's set.
+		result, err := filepath.EvalSymlinks(root)
+		if err != nil {
+			return "", fmt.Errorf("%v EvalSymlinks(%v) failed: %v", rootEnv, root, err)
+		}
+		if !filepath.IsAbs(result) {
+			return "", fmt.Errorf("%v isn't an absolute path: %v", rootEnv, result)
+		}
+		return filepath.Clean(result), nil
+	}
+	// TODO(toddw): Try to find the root by walking up the filesystem.
+	return "", fmt.Errorf("%v is not set", rootEnv)
+}
+
+// Clone returns a clone of the environment.
+func (x *X) Clone(opts tool.ContextOpts) *X {
+	return &X{
+		Context: x.Context.Clone(opts),
+		Root:    x.Root,
+		Usage:   x.Usage,
+	}
+}
+
+// UsageErrorf prints the error message represented by the printf-style format
+// and args, followed by the usage output.  The implementation typically calls
+// cmdline.Env.UsageErrorf.
+func (x *X) UsageErrorf(format string, args ...interface{}) error {
+	if x.Usage != nil {
+		return x.Usage(format, args...)
+	}
+	return nil
+}
+
+// RunnerFunc is an adapter that turns regular functions into cmdline.Runner.
+// This is similar to cmdline.RunnerFunc, but the first function argument is
+// jiri.X, rather than cmdline.Env.
+func RunnerFunc(run func(*X, []string) error) cmdline.Runner {
+	return runner(run)
+}
+
+type runner func(*X, []string) error
+
+func (r runner) Run(env *cmdline.Env, args []string) error {
+	x, err := NewX(env)
+	if err != nil {
+		return err
+	}
+	return r(x, args)
+}
diff --git a/profiles/commandline/driver.go b/profiles/commandline/driver.go
index 6fa89ef..44f2c87 100644
--- a/profiles/commandline/driver.go
+++ b/profiles/commandline/driver.go
@@ -16,6 +16,7 @@
 	"strings"
 	"text/template"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/profiles"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
@@ -45,7 +46,7 @@
 
 // cmdInstall represents the "profile install" command.
 var cmdInstall = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runInstall),
+	Runner:   jiri.RunnerFunc(runInstall),
 	Name:     "install",
 	Short:    "Install the given profiles",
 	Long:     "Install the given profiles.",
@@ -55,7 +56,7 @@
 
 // cmdList represents the "profile list" command.
 var cmdList = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runList),
+	Runner:   jiri.RunnerFunc(runList),
 	Name:     "list",
 	Short:    "List available or installed profiles",
 	Long:     "List available or installed profiles.",
@@ -65,7 +66,7 @@
 
 // cmdEnv represents the "profile env" command.
 var cmdEnv = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runEnv),
+	Runner: jiri.RunnerFunc(runEnv),
 	Name:   "env",
 	Short:  "Display profile environment variables",
 	Long: `
@@ -83,7 +84,7 @@
 
 // cmdUpdate represents the "profile update" command.
 var cmdUpdate = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runUpdate),
+	Runner:   jiri.RunnerFunc(runUpdate),
 	Name:     "update",
 	Short:    "Install the latest default version of the given profiles",
 	Long:     "Install the latest default version of the given profiles.",
@@ -93,7 +94,7 @@
 
 // cmdCleanup represents the "profile Cleanup" command.
 var cmdCleanup = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runCleanup),
+	Runner:   jiri.RunnerFunc(runCleanup),
 	Name:     "cleanup",
 	Short:    "Cleanup the locally installed profiles",
 	Long:     "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.",
@@ -103,7 +104,7 @@
 
 // cmdUninstall represents the "profile uninstall" command.
 var cmdUninstall = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runUninstall),
+	Runner:   jiri.RunnerFunc(runUninstall),
 	Name:     "uninstall",
 	Short:    "Uninstall the given profiles",
 	Long:     "Uninstall the given profiles.",
@@ -201,33 +202,32 @@
 	cmdCleanup.Flags.BoolVar(&rewriteManifestFlag, "rewrite-profiles-manifest", false, "rewrite the profiles manifest file to use the latest schema version")
 }
 
-func runList(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
+func runList(jirix *jiri.X, args []string) error {
 	if showManifestFlag {
-		data, err := ctx.Run().ReadFile(manifestFlag)
+		data, err := jirix.Run().ReadFile(manifestFlag)
 		if err != nil {
 			return err
 		}
-		fmt.Fprintln(ctx.Stdout(), string(data))
+		fmt.Fprintln(jirix.Stdout(), string(data))
 		return nil
 	}
 	if verboseFlag {
-		fmt.Fprintf(ctx.Stdout(), "Manifest: %s\n", manifestFlag)
+		fmt.Fprintf(jirix.Stdout(), "Manifest: %s\n", manifestFlag)
 	}
 	if availableFlag {
 		if verboseFlag {
-			fmt.Fprintf(ctx.Stdout(), "Available Profiles:\n")
+			fmt.Fprintf(jirix.Stdout(), "Available Profiles:\n")
 			for _, name := range profiles.Managers() {
 				mgr := profiles.LookupManager(name)
 				vi := mgr.VersionInfo()
-				fmt.Fprintf(ctx.Stdout(), "%s: versions: %s\n", name, vi)
+				fmt.Fprintf(jirix.Stdout(), "%s: versions: %s\n", name, vi)
 			}
 		} else {
-			fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Managers(), ", "))
+			fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(profiles.Managers(), ", "))
 		}
 	}
-	if err := profiles.Read(ctx, manifestFlag); err != nil {
-		fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err)
+	if err := profiles.Read(jirix, manifestFlag); err != nil {
+		fmt.Fprintf(jirix.Stderr(), "Failed to read manifest: %v", err)
 		return err
 	}
 	profileNames := args
@@ -241,13 +241,13 @@
 		}
 	}
 	if verboseFlag {
-		fmt.Fprintf(ctx.Stdout(), "Installed Profiles: ")
-		fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Profiles(), ", "))
+		fmt.Fprintf(jirix.Stdout(), "Installed Profiles: ")
+		fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(profiles.Profiles(), ", "))
 		for _, name := range availableNames {
 			profile := profiles.LookupProfile(name)
-			fmt.Fprintf(ctx.Stdout(), "Profile: %s @ %s\n", profile.Name, profile.Root)
+			fmt.Fprintf(jirix.Stdout(), "Profile: %s @ %s\n", profile.Name, profile.Root)
 			for _, target := range profile.Targets() {
-				fmt.Fprintf(ctx.Stdout(), "\t%s\n", target.DebugString())
+				fmt.Fprintf(jirix.Stdout(), "\t%s\n", target.DebugString())
 			}
 		}
 	} else {
@@ -267,7 +267,7 @@
 					out.WriteString(fmtHeader(name, target))
 					out.WriteString(" ")
 				}
-				r, err := fmtInfo(ctx, mgr, profile, target)
+				r, err := fmtInfo(jirix, mgr, profile, target)
 				if err != nil {
 					return err
 				}
@@ -276,7 +276,7 @@
 					out.WriteString("\n")
 				}
 			}
-			fmt.Fprint(ctx.Stdout(), out.String())
+			fmt.Fprint(jirix.Stdout(), out.String())
 		}
 	}
 	return nil
@@ -322,7 +322,7 @@
 	Note: if no profiles are specified then the requested field will be displayed for all profiles.`
 }
 
-func fmtOutput(ctx *tool.Context, o string) string {
+func fmtOutput(jirix *jiri.X, o string) string {
 	_, width, err := textutil.TerminalSize()
 	if err != nil {
 		width = 80
@@ -345,7 +345,7 @@
 	return root.RootJoin(s).Expand()
 }
 
-func fmtInfo(ctx *tool.Context, mgr profiles.Manager, profile *profiles.Profile, target *profiles.Target) (string, error) {
+func fmtInfo(jirix *jiri.X, mgr profiles.Manager, profile *profiles.Profile, target *profiles.Target) (string, error) {
 	if len(infoFlag) > 0 {
 		// Populate an instance listInfo
 		info := &listInfo{}
@@ -353,7 +353,7 @@
 		if mgr != nil {
 			// Format the description on its own, without any preceeding
 			// text so that the line breaks work correctly.
-			info.Profile.Description = "\n" + fmtOutput(ctx, mgr.Info()) + "\n"
+			info.Profile.Description = "\n" + fmtOutput(jirix, mgr.Info()) + "\n"
 			vi := mgr.VersionInfo()
 			if supported := vi.Supported(); len(supported) > 0 {
 				info.Profile.Versions = supported
@@ -391,12 +391,11 @@
 	return "", nil
 }
 
-func runEnv(env *cmdline.Env, args []string) error {
+func runEnv(jirix *jiri.X, args []string) error {
 	if len(profilesFlag) == 0 {
 		return fmt.Errorf("no profiles were specified using --profiles")
 	}
-	ctx := tool.NewContextFromEnv(env)
-	ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, manifestFlag)
+	ch, err := profiles.NewConfigHelper(jirix, profiles.UseProfiles, manifestFlag)
 	if err != nil {
 		return err
 	}
@@ -407,7 +406,7 @@
 	ch.MergeEnvFromProfiles(mergePoliciesFlag, targetFlag, profileNames...)
 	out := fmtVars(ch.ToMap(), args)
 	if len(out) > 0 {
-		fmt.Fprintln(ctx.Stdout(), out)
+		fmt.Fprintln(jirix.Stdout(), out)
 	}
 	return nil
 }
@@ -439,7 +438,7 @@
 	return strings.TrimSuffix(buf.String(), " ")
 }
 
-func initCommand(ctx *tool.Context, args []string) error {
+func initCommand(jirix *jiri.X, args []string) error {
 	if len(args) == 0 {
 		return fmt.Errorf("no profiles specified")
 	}
@@ -448,19 +447,18 @@
 			return fmt.Errorf("profile %v is not available, use \"list --available\" to see the list of available profiles", n)
 		}
 	}
-	if err := profiles.Read(ctx, manifestFlag); err != nil {
-		fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err)
+	if err := profiles.Read(jirix, manifestFlag); err != nil {
+		fmt.Fprintf(jirix.Stderr(), "Failed to read manifest: %v", err)
 		return err
 	}
 	return nil
 }
 
-func runUpdate(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
+func runUpdate(jirix *jiri.X, args []string) error {
 	if len(args) == 0 {
 		args = profiles.Managers()
 	}
-	if err := initCommand(ctx, args); err != nil {
+	if err := initCommand(jirix, args); err != nil {
 		return err
 	}
 	for _, n := range args {
@@ -473,34 +471,34 @@
 		for _, target := range profile.Targets() {
 			if vi.IsTargetOlderThanDefault(target.Version()) {
 				if verboseFlag {
-					fmt.Fprintf(ctx.Stdout(), "Updating %s %s from %q to %s\n", n, target, target.Version(), vi)
+					fmt.Fprintf(jirix.Stdout(), "Updating %s %s from %q to %s\n", n, target, target.Version(), vi)
 				}
 				target.SetVersion(vi.Default())
-				err := mgr.Install(ctx, rootPath, *target)
-				logResult(ctx, "Update", mgr, *target, err)
+				err := mgr.Install(jirix, rootPath, *target)
+				logResult(jirix, "Update", mgr, *target, err)
 				if err != nil {
 					return err
 				}
 			} else {
 				if verboseFlag {
-					fmt.Fprintf(ctx.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi)
+					fmt.Fprintf(jirix.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi)
 				}
 			}
 
 		}
 	}
-	return profiles.Write(ctx, manifestFlag)
+	return profiles.Write(jirix, manifestFlag)
 }
 
-func runGC(ctx *tool.Context, args []string) error {
+func runGC(jirix *jiri.X, args []string) error {
 	for _, n := range args {
 		mgr := profiles.LookupManager(n)
 		vi := mgr.VersionInfo()
 		profile := profiles.LookupProfile(n)
 		for _, target := range profile.Targets() {
 			if vi.IsTargetOlderThanDefault(target.Version()) {
-				err := mgr.Uninstall(ctx, rootPath, *target)
-				logResult(ctx, "gc", mgr, *target, err)
+				err := mgr.Uninstall(jirix, rootPath, *target)
+				logResult(jirix, "gc", mgr, *target, err)
 				if err != nil {
 					return err
 				}
@@ -510,12 +508,12 @@
 	return nil
 }
 
-func runEnsureVersionsAreSet(ctx *tool.Context, args []string) error {
+func runEnsureVersionsAreSet(jirix *jiri.X, args []string) error {
 	for _, name := range args {
 		profile := profiles.LookupProfile(name)
 		mgr := profiles.LookupManager(name)
 		if mgr == nil {
-			fmt.Fprintf(ctx.Stderr(), "%s is not linked into this binary", name)
+			fmt.Fprintf(jirix.Stderr(), "%s is not linked into this binary", name)
 			continue
 		}
 		for _, target := range profile.Targets() {
@@ -531,7 +529,7 @@
 					return err
 				}
 				if verboseFlag {
-					fmt.Fprintf(ctx.Stdout(), "%s %s had no version, now set to: %s\n", name, prior, target)
+					fmt.Fprintf(jirix.Stdout(), "%s %s had no version, now set to: %s\n", name, prior, target)
 				}
 			}
 		}
@@ -539,8 +537,8 @@
 	return nil
 }
 
-func runRmAll(ctx *tool.Context) error {
-	s := ctx.NewSeq()
+func runRmAll(jirix *jiri.X) error {
+	s := jirix.NewSeq()
 	if exists, err := s.FileExists(manifestFlag); err != nil || exists {
 		if err := s.Remove(manifestFlag).Done(); err != nil {
 			return err
@@ -556,10 +554,9 @@
 	return nil
 }
 
-func runCleanup(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	if err := profiles.Read(ctx, manifestFlag); err != nil {
-		fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err)
+func runCleanup(jirix *jiri.X, args []string) error {
+	if err := profiles.Read(jirix, manifestFlag); err != nil {
+		fmt.Fprintf(jirix.Stderr(), "Failed to read manifest: %v", err)
 		return err
 	}
 	if len(args) == 0 {
@@ -568,27 +565,27 @@
 	dirty := false
 	if specificVersionsFlag {
 		if verboseFlag {
-			fmt.Fprintf(ctx.Stdout(), "Ensuring that all targets have a specific version set for %s\n", args)
+			fmt.Fprintf(jirix.Stdout(), "Ensuring that all targets have a specific version set for %s\n", args)
 		}
-		if err := runEnsureVersionsAreSet(ctx, args); err != nil {
+		if err := runEnsureVersionsAreSet(jirix, args); err != nil {
 			return fmt.Errorf("ensure-specific-versions-are-set: %v", err)
 		}
 		dirty = true
 	}
 	if cleanupFlag {
 		if verboseFlag {
-			fmt.Fprintf(ctx.Stdout(), "Removing targets older than the default version for %s\n", args)
+			fmt.Fprintf(jirix.Stdout(), "Removing targets older than the default version for %s\n", args)
 		}
-		if err := runGC(ctx, args); err != nil {
+		if err := runGC(jirix, args); err != nil {
 			return fmt.Errorf("gc: %v", err)
 		}
 		dirty = true
 	}
 	if rmAllFlag {
 		if verboseFlag {
-			fmt.Fprintf(ctx.Stdout(), "Removing profile manifest and all profile output files\n")
+			fmt.Fprintf(jirix.Stdout(), "Removing profile manifest and all profile output files\n")
 		}
-		if err := runRmAll(ctx); err != nil {
+		if err := runRmAll(jirix); err != nil {
 			return err
 		}
 		// Don't write out the profiles manifest file again.
@@ -600,19 +597,19 @@
 	if !dirty {
 		return fmt.Errorf("at least one option must be specified")
 	}
-	return profiles.Write(ctx, manifestFlag)
+	return profiles.Write(jirix, manifestFlag)
 }
 
-func logResult(ctx *tool.Context, action string, mgr profiles.Manager, target profiles.Target, err error) {
-	fmt.Fprintf(ctx.Stdout(), "%s: %s %s: ", action, mgr.Name(), target)
+func logResult(jirix *jiri.X, action string, mgr profiles.Manager, target profiles.Target, err error) {
+	fmt.Fprintf(jirix.Stdout(), "%s: %s %s: ", action, mgr.Name(), target)
 	if err == nil {
-		fmt.Fprintf(ctx.Stdout(), "success\n")
+		fmt.Fprintf(jirix.Stdout(), "success\n")
 	} else {
-		fmt.Fprintf(ctx.Stdout(), "%v\n", err)
+		fmt.Fprintf(jirix.Stdout(), "%v\n", err)
 	}
 }
 
-func applyCommand(names []string, env *cmdline.Env, ctx *tool.Context, target profiles.Target, fn func(profiles.Manager, *tool.Context, profiles.Target) error) error {
+func applyCommand(names []string, jirix *jiri.X, target profiles.Target, fn func(profiles.Manager, *jiri.X, profiles.Target) error) error {
 	for _, n := range names {
 		mgr := profiles.LookupManager(n)
 		version, err := mgr.VersionInfo().Select(target.Version())
@@ -620,16 +617,15 @@
 			return err
 		}
 		target.SetVersion(version)
-		if err := fn(mgr, ctx, target); err != nil {
+		if err := fn(mgr, jirix, target); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func runInstall(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	if err := initCommand(ctx, args); err != nil {
+func runInstall(jirix *jiri.X, args []string) error {
+	if err := initCommand(jirix, args); err != nil {
 		return err
 	}
 	names := []string{}
@@ -641,29 +637,28 @@
 	targetFlag.UseCommandLineEnv()
 	for _, name := range args {
 		if p := profiles.LookupProfileTarget(name, targetFlag); p != nil {
-			fmt.Fprintf(ctx.Stdout(), "%v %v is already installed as %v\n", name, targetFlag, p)
+			fmt.Fprintf(jirix.Stdout(), "%v %v is already installed as %v\n", name, targetFlag, p)
 			continue
 		}
 		names = append(names, name)
 	}
-	if err := applyCommand(names, env, ctx, targetFlag,
-		func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error {
-			err := mgr.Install(ctx, rootPath, target)
-			logResult(ctx, "Install:", mgr, target, err)
+	if err := applyCommand(names, jirix, targetFlag,
+		func(mgr profiles.Manager, jirix *jiri.X, target profiles.Target) error {
+			err := mgr.Install(jirix, rootPath, target)
+			logResult(jirix, "Install:", mgr, target, err)
 			return err
 		}); err != nil {
 		return err
 	}
-	return profiles.Write(ctx, manifestFlag)
+	return profiles.Write(jirix, manifestFlag)
 }
 
-func runUninstall(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	if err := initCommand(ctx, args); err != nil {
+func runUninstall(jirix *jiri.X, args []string) error {
+	if err := initCommand(jirix, args); err != nil {
 		return err
 	}
 	if allFlag && targetFlag.IsSet() {
-		fmt.Fprintf(ctx.Stdout(), "ignore target (%v) when used in conjunction with --all-targets\n", targetFlag)
+		fmt.Fprintf(jirix.Stdout(), "ignore target (%v) when used in conjunction with --all-targets\n", targetFlag)
 	}
 	if allFlag {
 		for _, name := range args {
@@ -673,20 +668,20 @@
 				continue
 			}
 			for _, target := range profile.Targets() {
-				if err := mgr.Uninstall(ctx, rootPath, *target); err != nil {
-					logResult(ctx, "Uninstall", mgr, *target, err)
+				if err := mgr.Uninstall(jirix, rootPath, *target); err != nil {
+					logResult(jirix, "Uninstall", mgr, *target, err)
 					return err
 				}
-				logResult(ctx, "Uninstall", mgr, *target, nil)
+				logResult(jirix, "Uninstall", mgr, *target, nil)
 			}
 		}
 	} else {
-		applyCommand(args, env, ctx, targetFlag,
-			func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error {
-				err := mgr.Uninstall(ctx, rootPath, target)
-				logResult(ctx, "Uninstall", mgr, target, err)
+		applyCommand(args, jirix, targetFlag,
+			func(mgr profiles.Manager, jirix *jiri.X, target profiles.Target) error {
+				err := mgr.Uninstall(jirix, rootPath, target)
+				logResult(jirix, "Uninstall", mgr, target, err)
 				return err
 			})
 	}
-	return profiles.Write(ctx, manifestFlag)
+	return profiles.Write(jirix, manifestFlag)
 }
diff --git a/profiles/env.go b/profiles/env.go
index 752d7b6..4f34cf8 100644
--- a/profiles/env.go
+++ b/profiles/env.go
@@ -13,8 +13,8 @@
 	"strconv"
 	"strings"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
-	"v.io/jiri/tool"
 	"v.io/jiri/util"
 	"v.io/x/lib/envvar"
 )
@@ -100,7 +100,7 @@
 	*envvar.Vars
 	profilesMode bool
 	root         string
-	ctx          *tool.Context
+	jirix        *jiri.X
 	config       *util.Config
 	projects     project.Projects
 	tools        project.Tools
@@ -110,26 +110,26 @@
 // length then that file will be read as a profiles manifest file, if not, the
 // existing, if any, in-memory profiles information will be used. If SkipProfiles
 // is specified for profilesMode, then no profiles are used.
-func NewConfigHelper(ctx *tool.Context, profilesMode ProfilesMode, filename string) (*ConfigHelper, error) {
+func NewConfigHelper(jirix *jiri.X, profilesMode ProfilesMode, filename string) (*ConfigHelper, error) {
 	root, err := project.JiriRoot()
 	if err != nil {
 		return nil, err
 	}
-	config, err := util.LoadConfig(ctx)
+	config, err := util.LoadConfig(jirix)
 	if err != nil {
 		return nil, err
 	}
-	projects, tools, err := project.ReadManifest(ctx)
+	projects, tools, err := project.ReadManifest(jirix)
 	if err != nil {
 		return nil, err
 	}
 	if profilesMode == UseProfiles && len(filename) > 0 {
-		if err := Read(ctx, filename); err != nil {
+		if err := Read(jirix, filename); err != nil {
 			return nil, err
 		}
 	}
 	ch := &ConfigHelper{
-		ctx:          ctx,
+		jirix:        jirix,
 		root:         root,
 		config:       config,
 		projects:     projects,
@@ -223,19 +223,19 @@
 // GoPath computes and returns the GOPATH environment variable based on the
 // current jiri configuration.
 func (ch *ConfigHelper) GoPath() string {
-	path := pathHelper(ch.ctx, ch.root, ch.projects, ch.config.GoWorkspaces(), "")
+	path := pathHelper(ch.jirix, ch.root, ch.projects, ch.config.GoWorkspaces(), "")
 	return "GOPATH=" + envvar.JoinTokens(path, ":")
 }
 
 // VDLPath computes and returns the VDLPATH environment variable based on the
 // current jiri configuration.
 func (ch *ConfigHelper) VDLPath() string {
-	path := pathHelper(ch.ctx, ch.root, ch.projects, ch.config.VDLWorkspaces(), "src")
+	path := pathHelper(ch.jirix, ch.root, ch.projects, ch.config.VDLWorkspaces(), "src")
 	return "VDLPATH=" + envvar.JoinTokens(path, ":")
 }
 
 // pathHelper is a utility function for determining paths for project workspaces.
-func pathHelper(ctx *tool.Context, root string, projects project.Projects, workspaces []string, suffix string) []string {
+func pathHelper(jirix *jiri.X, root string, projects project.Projects, workspaces []string, suffix string) []string {
 	path := []string{}
 	for _, workspace := range workspaces {
 		absWorkspace := filepath.Join(root, workspace, suffix)
@@ -250,7 +250,7 @@
 			// account for Go workspaces that span multiple jiri projects,
 			// such as: $JIRI_ROOT/release/go.
 			if strings.HasPrefix(absWorkspace, project.Path) || strings.HasPrefix(project.Path, absWorkspace) {
-				if _, err := ctx.Run().Stat(filepath.Join(absWorkspace)); err == nil {
+				if _, err := jirix.Run().Stat(filepath.Join(absWorkspace)); err == nil {
 					path = append(path, absWorkspace)
 					break
 				}
diff --git a/profiles/env_test.go b/profiles/env_test.go
index f057c3c..e0c3423 100644
--- a/profiles/env_test.go
+++ b/profiles/env_test.go
@@ -13,6 +13,7 @@
 	"sort"
 	"testing"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/profiles"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
@@ -21,12 +22,12 @@
 )
 
 func TestConfigHelper(t *testing.T) {
-	ctx := tool.NewDefaultContext()
 	root, err := project.JiriRoot()
 	if err != nil {
 		t.Fatal(err)
 	}
-	ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, filepath.Join(root, "release/go/src/v.io/jiri/profiles/testdata/m2.xml"))
+	jirix := &jiri.X{Context: tool.NewDefaultContext(), Root: root}
+	ch, err := profiles.NewConfigHelper(jirix, profiles.UseProfiles, filepath.Join(root, "release/go/src/v.io/jiri/profiles/testdata/m2.xml"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -44,8 +45,11 @@
 
 func TestEnvFromTarget(t *testing.T) {
 	profiles.Clear()
-	root, _ := project.JiriRoot()
-	ctx := tool.NewDefaultContext()
+	root, err := project.JiriRoot()
+	if err != nil {
+		t.Fatal(err)
+	}
+	jirix := &jiri.X{Context: tool.NewDefaultContext(), Root: root}
 	profiles.InstallProfile("a", "root")
 	profiles.InstallProfile("b", "root")
 	t1, t2 := &profiles.Target{}, &profiles.Target{}
@@ -60,11 +64,11 @@
 		t.Fatal(err)
 	}
 	filename := filepath.Join(root, "release", "go", "src", "v.io", "jiri", "profiles", tmpdir, "manifest")
-	if err := profiles.Write(ctx, filename); err != nil {
+	if err := profiles.Write(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(tmpdir)
-	ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, filename)
+	ch, err := profiles.NewConfigHelper(jirix, profiles.UseProfiles, filename)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -145,31 +149,29 @@
 
 func testSetPathHelper(t *testing.T, name string) {
 	profiles.Clear()
-	ctx := tool.NewDefaultContext()
-
 	// Setup a fake JIRI_ROOT.
-	root, err := project.NewFakeJiriRoot(ctx)
+	root, err := project.NewFakeJiriRoot()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	defer func() {
-		if err := root.Cleanup(ctx); err != nil {
+		if err := root.Cleanup(); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}()
 
 	// Create a test project and identify it as a Go workspace.
-	if err := root.CreateRemoteProject(ctx, "test"); err != nil {
+	if err := root.CreateRemoteProject("test"); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := root.AddProject(ctx, project.Project{
+	if err := root.AddProject(project.Project{
 		Name:   "test",
 		Path:   "test",
 		Remote: root.Projects["test"],
 	}); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := root.UpdateUniverse(ctx, false); err != nil {
+	if err := root.UpdateUniverse(false); err != nil {
 		t.Fatalf("%v", err)
 	}
 	var config *util.Config
@@ -186,11 +188,11 @@
 	}
 	defer os.Setenv("JIRI_ROOT", oldRoot)
 
-	if err := profiles.Write(ctx, filepath.Join(root.Dir, "profiles-manifest")); err != nil {
+	if err := profiles.Write(root.X, filepath.Join(root.Dir, "profiles-manifest")); err != nil {
 		t.Fatal(err)
 	}
 
-	if err := util.SaveConfig(ctx, config); err != nil {
+	if err := util.SaveConfig(root.X, config); err != nil {
 		t.Fatalf("%v", err)
 	}
 
@@ -200,7 +202,7 @@
 		t.Fatalf("%v", err)
 	}
 
-	ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, filepath.Join(jiriRoot, "profiles-manifest"))
+	ch, err := profiles.NewConfigHelper(root.X, profiles.UseProfiles, filepath.Join(jiriRoot, "profiles-manifest"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -213,7 +215,7 @@
 	case "VDLPATH":
 		// Make a fake src directory.
 		want = filepath.Join(jiriRoot, "test", "src")
-		if err := ctx.Run().MkdirAll(want, 0755); err != nil {
+		if err := root.X.Run().MkdirAll(want, 0755); err != nil {
 			t.Fatalf("%v", err)
 		}
 		want = "VDLPATH=" + want
diff --git a/profiles/manager.go b/profiles/manager.go
index 4f60cfb..65635ed 100644
--- a/profiles/manager.go
+++ b/profiles/manager.go
@@ -41,7 +41,7 @@
 	"strings"
 	"sync"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
 	"v.io/x/lib/envvar"
 )
 
@@ -177,9 +177,9 @@
 	// is its name and version.
 	String() string
 	// Install installs the profile for the specified build target.
-	Install(ctx *tool.Context, root RelativePath, target Target) error
+	Install(jirix *jiri.X, root RelativePath, target Target) error
 	// Uninstall uninstalls the profile for the specified build target. When
 	// the last target for any given profile is uninstalled, then the profile
 	// itself (i.e. the source code) will be uninstalled.
-	Uninstall(ctx *tool.Context, root RelativePath, target Target) error
+	Uninstall(jirix *jiri.X, root RelativePath, target Target) error
 }
diff --git a/profiles/manager_test.go b/profiles/manager_test.go
index 5e83747..b613330 100644
--- a/profiles/manager_test.go
+++ b/profiles/manager_test.go
@@ -11,6 +11,7 @@
 	"path/filepath"
 	"testing"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/profiles"
 	"v.io/jiri/tool"
 )
@@ -81,13 +82,13 @@
 func (p *myNewProfile) AddFlags(*flag.FlagSet, profiles.Action) {
 }
 
-func (p *myNewProfile) Install(ctx *tool.Context, root profiles.RelativePath, target profiles.Target) error {
+func (p *myNewProfile) Install(jirix *jiri.X, root profiles.RelativePath, target profiles.Target) error {
 	p.status = "installed"
 	profiles.AddProfileTarget(p.name, target)
 	return nil
 }
 
-func (p *myNewProfile) Uninstall(ctx *tool.Context, root profiles.RelativePath, target profiles.Target) error {
+func (p *myNewProfile) Uninstall(jirix *jiri.X, root profiles.RelativePath, target profiles.Target) error {
 	profiles.RemoveProfileTarget(p.name, target)
 	if profiles.LookupProfile(p.name) == nil {
 		p.status = "uninstalled"
@@ -113,9 +114,9 @@
 		panic("manager not found for: " + myProfile)
 	}
 
-	ctx := tool.NewDefaultContext()
+	jirix := &jiri.X{Context: tool.NewDefaultContext()}
 	// Install myNewProfile for target.
-	if err := mgr.Install(ctx, rootPath, target); err != nil {
+	if err := mgr.Install(jirix, rootPath, target); err != nil {
 		panic("failed to find manager for: " + myProfile)
 	}
 
@@ -124,7 +125,7 @@
 	filename := tmpFile()
 	defer os.RemoveAll(filepath.Dir(filename))
 
-	if err := profiles.Write(ctx, filename); err != nil {
+	if err := profiles.Write(jirix, filename); err != nil {
 		panic(err)
 	}
 
@@ -133,7 +134,7 @@
 	profiles.Clear()
 
 	// Read the profile manifest.
-	profiles.Read(ctx, filename)
+	profiles.Read(jirix, filename)
 
 	mgr = profiles.LookupManager(myProfile)
 	if mgr == nil {
@@ -141,7 +142,7 @@
 	}
 
 	fmt.Println(mgr.String())
-	mgr.Uninstall(ctx, rootPath, target)
+	mgr.Uninstall(jirix, rootPath, target)
 	fmt.Println(mgr.String())
 	fmt.Println(mgr.VersionInfo().Supported())
 	fmt.Println(mgr.VersionInfo().Default())
diff --git a/profiles/manifest.go b/profiles/manifest.go
index cb1c505..02395ab 100644
--- a/profiles/manifest.go
+++ b/profiles/manifest.go
@@ -12,7 +12,7 @@
 	"sync"
 	"time"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
 )
 
 const (
@@ -145,14 +145,14 @@
 
 // Read reads the specified manifest file to obtain the current set of
 // installed profiles.
-func Read(ctx *tool.Context, filename string) error {
-	return db.read(ctx, filename)
+func Read(jirix *jiri.X, filename string) error {
+	return db.read(jirix, filename)
 }
 
 // Write writes the current set of installed profiles to the specified manifest
 // file.
-func Write(ctx *tool.Context, filename string) error {
-	return db.write(ctx, filename)
+func Write(jirix *jiri.X, filename string) error {
+	return db.write(jirix, filename)
 }
 
 func (pdb *profileDB) installProfile(name, root string) {
@@ -237,15 +237,15 @@
 	return pdb.db[name]
 }
 
-func (pdb *profileDB) read(ctx *tool.Context, filename string) error {
+func (pdb *profileDB) read(jirix *jiri.X, filename string) error {
 	pdb.Lock()
 	defer pdb.Unlock()
 	pdb.db = make(map[string]*Profile)
 
-	data, err := ctx.Run().ReadFile(filename)
+	data, err := jirix.Run().ReadFile(filename)
 	if err != nil {
 		if os.IsNotExist(err) {
-			fmt.Fprintf(ctx.Stderr(), "WARNING: %v doesn't exist\n", filename)
+			fmt.Fprintf(jirix.Stderr(), "WARNING: %v doesn't exist\n", filename)
 			return nil
 		}
 		return err
@@ -278,7 +278,7 @@
 	return nil
 }
 
-func (pdb *profileDB) write(ctx *tool.Context, filename string) error {
+func (pdb *profileDB) write(jirix *jiri.X, filename string) error {
 	pdb.Lock()
 	defer pdb.Unlock()
 
@@ -318,17 +318,17 @@
 	oldName := filename + ".prev"
 	newName := filename + fmt.Sprintf(".%d", time.Now().UnixNano())
 
-	if err := ctx.Run().WriteFile(newName, data, defaultFileMode); err != nil {
+	if err := jirix.Run().WriteFile(newName, data, defaultFileMode); err != nil {
 		return err
 	}
 
-	if ctx.Run().FileExists(filename) {
-		if err := ctx.Run().Rename(filename, oldName); err != nil {
+	if jirix.Run().FileExists(filename) {
+		if err := jirix.Run().Rename(filename, oldName); err != nil {
 			return err
 		}
 	}
 
-	if err := ctx.Run().Rename(newName, filename); err != nil {
+	if err := jirix.Run().Rename(newName, filename); err != nil {
 		return err
 	}
 
diff --git a/profiles/manifest_test.go b/profiles/manifest_test.go
index aa6ef33..99a2a86 100644
--- a/profiles/manifest_test.go
+++ b/profiles/manifest_test.go
@@ -15,6 +15,7 @@
 	"strings"
 	"testing"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/profiles"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
@@ -55,21 +56,21 @@
 	profiles.Clear()
 	filename := tmpFile()
 	defer os.RemoveAll(filepath.Dir(filename))
-	ctx := tool.NewDefaultContext()
+	jirix := &jiri.X{Context: tool.NewDefaultContext()}
 
 	// test for no version being set.
 	t1, _ := profiles.NewTargetWithEnv("cpu1-os1", "A=B,C=D")
 	if err := profiles.AddProfileTarget("b", t1); err != nil {
 		t.Fatal(err)
 	}
-	if err := profiles.Write(ctx, filename); err == nil || !strings.HasPrefix(err.Error(), "missing version for profile") {
+	if err := profiles.Write(jirix, filename); err == nil || !strings.HasPrefix(err.Error(), "missing version for profile") {
 		t.Fatalf("was expecing a missing version error, but got %v", err)
 	}
 	profiles.RemoveProfileTarget("b", t1)
 
 	addProfileAndTargets(t, "b")
 	addProfileAndTargets(t, "a")
-	if err := profiles.Write(ctx, filename); err != nil {
+	if err := profiles.Write(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 
@@ -82,8 +83,8 @@
 
 func TestRead(t *testing.T) {
 	profiles.Clear()
-	ctx := tool.NewDefaultContext()
-	if err := profiles.Read(ctx, "./testdata/m1.xml"); err != nil {
+	jirix := &jiri.X{Context: tool.NewDefaultContext()}
+	if err := profiles.Read(jirix, "./testdata/m1.xml"); err != nil {
 		t.Fatal(err)
 	}
 
@@ -135,8 +136,8 @@
 		return db
 	}
 
-	ctx := tool.NewDefaultContext()
-	if err := profiles.Read(ctx, "./testdata/legacy.xml"); err != nil {
+	jirix := &jiri.X{Context: tool.NewDefaultContext()}
+	if err := profiles.Read(jirix, "./testdata/legacy.xml"); err != nil {
 		t.Fatal(err)
 	}
 
@@ -155,11 +156,11 @@
 	t1.Set("cpu-os@1")
 	profiles.AddProfileTarget("__first", t1)
 
-	if err := profiles.Write(ctx, filename); err != nil {
+	if err := profiles.Write(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 
-	if err := profiles.Read(ctx, filename); err != nil {
+	if err := profiles.Read(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 
@@ -191,11 +192,11 @@
 }
 
 func TestReadingV3AndV4(t *testing.T) {
-	ctx := tool.NewDefaultContext()
 	root, err := project.JiriRoot()
 	if err != nil {
 		t.Fatal(err)
 	}
+	jirix := &jiri.X{Context: tool.NewDefaultContext(), Root: root}
 	for i, c := range []struct {
 		filename, prefix, variable string
 		version                    profiles.Version
@@ -203,7 +204,7 @@
 		{"v3.xml", "", "", profiles.V3},
 		{"v4.xml", root, "${JIRI_ROOT}", profiles.V4},
 	} {
-		ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, filepath.Join("testdata", c.filename))
+		ch, err := profiles.NewConfigHelper(jirix, profiles.UseProfiles, filepath.Join("testdata", c.filename))
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/profiles/util.go b/profiles/util.go
index f12d3c1..170f897 100644
--- a/profiles/util.go
+++ b/profiles/util.go
@@ -16,6 +16,7 @@
 	"runtime"
 	"strings"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/runutil"
 	"v.io/jiri/tool"
@@ -95,10 +96,10 @@
 // are not successfully logged therein after deleting the entire contents of the
 // dir parameter. Consequently it does not make sense to apply AtomicAction to
 // the same directory in sequence.
-func AtomicAction(ctx *tool.Context, installFn func() error, dir, message string) error {
+func AtomicAction(jirix *jiri.X, installFn func() error, dir, message string) error {
 	atomicFn := func() error {
 		completionLogPath := filepath.Join(dir, ".complete")
-		s := ctx.NewSeq()
+		s := jirix.NewSeq()
 		if dir != "" {
 			if exists, _ := s.DirectoryExists(dir); exists {
 				// If the dir exists but the completionLogPath doesn't, then it
@@ -107,8 +108,8 @@
 				if exists, _ := s.FileExists(completionLogPath); !exists {
 					s.RemoveAll(dir).Done()
 				} else {
-					if ctx.Verbose() {
-						fmt.Fprintf(ctx.Stdout(), "AtomicAction: %s already completed in %s\n", message, dir)
+					if jirix.Verbose() {
+						fmt.Fprintf(jirix.Stdout(), "AtomicAction: %s already completed in %s\n", message, dir)
 					}
 					return nil
 				}
@@ -122,14 +123,14 @@
 		}
 		return s.WriteFile(completionLogPath, []byte("completed"), DefaultFilePerm).Done()
 	}
-	return ctx.NewSeq().Call(atomicFn, message).Done()
+	return jirix.NewSeq().Call(atomicFn, message).Done()
 }
 
-func brewList(ctx *tool.Context) (map[string]bool, error) {
+func brewList(jirix *jiri.X) (map[string]bool, error) {
 	var out bytes.Buffer
-	err := ctx.NewSeq().Capture(&out, &out).Last("brew", "list")
+	err := jirix.NewSeq().Capture(&out, &out).Last("brew", "list")
 	if err != nil || tool.VerboseFlag {
-		fmt.Fprintf(ctx.Stdout(), "%s", out.String())
+		fmt.Fprintf(jirix.Stdout(), "%s", out.String())
 	}
 	scanner := bufio.NewScanner(&out)
 	pkgs := map[string]bool{}
@@ -141,14 +142,14 @@
 
 // InstallPackages identifies the packages that need to be installed
 // and installs them using the OS-specific package manager.
-func InstallPackages(ctx *tool.Context, pkgs []string) error {
+func InstallPackages(jirix *jiri.X, pkgs []string) error {
 	installDepsFn := func() error {
-		s := ctx.NewSeq()
+		s := jirix.NewSeq()
 		switch runtime.GOOS {
 		case "linux":
 			if runutil.IsFNLHost() {
-				fmt.Fprintf(ctx.Stdout(), "skipping installation of %v on FNL host", pkgs)
-				fmt.Fprintf(ctx.Stdout(), "success\n")
+				fmt.Fprintf(jirix.Stdout(), "skipping installation of %v on FNL host", pkgs)
+				fmt.Fprintf(jirix.Stdout(), "success\n")
 				break
 			}
 			installPkgs := []string{}
@@ -159,16 +160,16 @@
 			}
 			if len(installPkgs) > 0 {
 				args := append([]string{"apt-get", "install", "-y"}, installPkgs...)
-				fmt.Fprintf(ctx.Stdout(), "Running: sudo %s: ", strings.Join(args, " "))
+				fmt.Fprintf(jirix.Stdout(), "Running: sudo %s: ", strings.Join(args, " "))
 				if err := s.Last("sudo", args...); err != nil {
-					fmt.Fprintf(ctx.Stdout(), "%v\n", err)
+					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
 					return err
 				}
-				fmt.Fprintf(ctx.Stdout(), "success\n")
+				fmt.Fprintf(jirix.Stdout(), "success\n")
 			}
 		case "darwin":
 			installPkgs := []string{}
-			installedPkgs, err := brewList(ctx)
+			installedPkgs, err := brewList(jirix)
 			if err != nil {
 				return err
 			}
@@ -179,22 +180,22 @@
 			}
 			if len(installPkgs) > 0 {
 				args := append([]string{"install"}, installPkgs...)
-				fmt.Fprintf(ctx.Stdout(), "Running: brew %s: ", strings.Join(args, " "))
+				fmt.Fprintf(jirix.Stdout(), "Running: brew %s: ", strings.Join(args, " "))
 				if err := s.Last("brew", args...); err != nil {
-					fmt.Fprintf(ctx.Stdout(), "%v\n", err)
+					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
 					return err
 				}
-				fmt.Fprintf(ctx.Stdout(), "success\n")
+				fmt.Fprintf(jirix.Stdout(), "success\n")
 			}
 		}
 		return nil
 	}
-	return ctx.NewSeq().Call(installDepsFn, "Install dependencies").Done()
+	return jirix.NewSeq().Call(installDepsFn, "Install dependencies").Done()
 }
 
 // ensureAction ensures that the requested profile and target
 // is installed/uninstalled, installing/uninstalling it if only if necessary.
-func ensureAction(ctx *tool.Context, action Action, profile string, root RelativePath, target Target) error {
+func ensureAction(jirix *jiri.X, action Action, profile string, root RelativePath, target Target) error {
 	verb := ""
 	switch action {
 	case Install:
@@ -205,8 +206,8 @@
 		return fmt.Errorf("unrecognised action %v", action)
 	}
 	if t := LookupProfileTarget(profile, target); t != nil {
-		if ctx.Verbose() {
-			fmt.Fprintf(ctx.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
+		if jirix.Verbose() {
+			fmt.Fprintf(jirix.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
 		}
 		return nil
 	}
@@ -219,32 +220,32 @@
 		return err
 	}
 	target.SetVersion(version)
-	if ctx.Verbose() || ctx.DryRun() {
-		fmt.Fprintf(ctx.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
+	if jirix.Verbose() || jirix.DryRun() {
+		fmt.Fprintf(jirix.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
 	}
 	if action == Install {
-		return mgr.Install(ctx, root, target)
+		return mgr.Install(jirix, root, target)
 	}
-	return mgr.Uninstall(ctx, root, target)
+	return mgr.Uninstall(jirix, root, target)
 }
 
 // EnsureProfileTargetIsInstalled ensures that the requested profile and target
 // is installed, installing it if only if necessary.
-func EnsureProfileTargetIsInstalled(ctx *tool.Context, profile string, root RelativePath, target Target) error {
-	return ensureAction(ctx, Install, profile, root, target)
+func EnsureProfileTargetIsInstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
+	return ensureAction(jirix, Install, profile, root, target)
 }
 
 // EnsureProfileTargetIsUninstalled ensures that the requested profile and target
 // are no longer installed.
-func EnsureProfileTargetIsUninstalled(ctx *tool.Context, profile string, root RelativePath, target Target) error {
-	return ensureAction(ctx, Uninstall, profile, root, target)
+func EnsureProfileTargetIsUninstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
+	return ensureAction(jirix, Uninstall, profile, root, target)
 }
 
 // Fetch downloads the specified url and saves it to dst.
 // TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
 // so it does not pollute the profile package namespace.
-func Fetch(ctx *tool.Context, dst, url string) error {
-	s := ctx.NewSeq()
+func Fetch(jirix *jiri.X, dst, url string) error {
+	s := jirix.NewSeq()
 	s.Output([]string{"fetching " + url})
 	resp, err := http.Get(url)
 	if err != nil {
@@ -265,7 +266,7 @@
 // Unzip unzips the file in srcFile and puts resulting files in directory dstDir.
 // TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
 // so it does not pollute the profile package namespace.
-func Unzip(ctx *tool.Context, srcFile, dstDir string) error {
+func Unzip(jirix *jiri.X, srcFile, dstDir string) error {
 	r, err := zip.OpenReader(srcFile)
 	if err != nil {
 		return err
@@ -279,7 +280,7 @@
 		}
 		defer rc.Close()
 
-		s := ctx.NewSeq()
+		s := jirix.NewSeq()
 		fileDst := filepath.Join(dstDir, zFile.Name)
 		if zFile.FileInfo().IsDir() {
 			return s.MkdirAll(fileDst, zFile.Mode()).Done()
@@ -299,7 +300,7 @@
 		_, err = s.Copy(file, rc)
 		return err
 	}
-	s := ctx.NewSeq()
+	s := jirix.NewSeq()
 	s.Output([]string{"unzipping " + srcFile})
 	for _, zFile := range r.File {
 		s.Output([]string{"extracting " + zFile.Name})
diff --git a/project.go b/project.go
index ac11219..f59871a 100644
--- a/project.go
+++ b/project.go
@@ -12,6 +12,7 @@
 	"sort"
 	"strings"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
 	"v.io/jiri/util"
@@ -48,7 +49,7 @@
 
 // cmdProjectClean represents the "jiri project clean" command.
 var cmdProjectClean = &cmdline.Command{
-	Runner:   cmdline.RunnerFunc(runProjectClean),
+	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.",
@@ -56,9 +57,8 @@
 	ArgsLong: "<project ...> is a list of projects to clean up.",
 }
 
-func runProjectClean(env *cmdline.Env, args []string) (e error) {
-	ctx := tool.NewContextFromEnv(env)
-	localProjects, err := project.LocalProjects(ctx, project.FullScan)
+func runProjectClean(jirix *jiri.X, args []string) (e error) {
+	localProjects, err := project.LocalProjects(jirix, project.FullScan)
 	if err != nil {
 		return err
 	}
@@ -68,13 +68,13 @@
 			if p, ok := localProjects[arg]; ok {
 				projects[p.Name] = p
 			} else {
-				fmt.Fprintf(ctx.Stderr(), "Local project %q not found.\n", p.Name)
+				fmt.Fprintf(jirix.Stderr(), "Local project %q not found.\n", p.Name)
 			}
 		}
 	} else {
 		projects = localProjects
 	}
-	if err := project.CleanupProjects(ctx, projects, cleanupBranchesFlag); err != nil {
+	if err := project.CleanupProjects(jirix, projects, cleanupBranchesFlag); err != nil {
 		return err
 	}
 	return nil
@@ -82,16 +82,15 @@
 
 // cmdProjectList represents the "jiri project list" command.
 var cmdProjectList = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runProjectList),
+	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(env *cmdline.Env, _ []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	states, err := project.GetProjectStates(ctx, noPristineFlag)
+func runProjectList(jirix *jiri.X, _ []string) error {
+	states, err := project.GetProjectStates(jirix, noPristineFlag)
 	if err != nil {
 		return err
 	}
@@ -109,7 +108,7 @@
 				continue
 			}
 		}
-		fmt.Fprintf(ctx.Stdout(), "project=%q path=%q\n", path.Base(name), state.Project.Path)
+		fmt.Fprintf(jirix.Stdout(), "project=%q path=%q\n", path.Base(name), state.Project.Path)
 		if branchesFlag {
 			for _, branch := range state.Branches {
 				s := "  "
@@ -120,7 +119,7 @@
 				if branch.HasGerritMessage {
 					s += " (exported to gerrit)"
 				}
-				fmt.Fprintf(ctx.Stdout(), "%v\n", s)
+				fmt.Fprintf(jirix.Stdout(), "%v\n", s)
 			}
 		}
 	}
@@ -129,7 +128,7 @@
 
 // cmdProjectShellPrompt represents the "jiri project shell-prompt" command.
 var cmdProjectShellPrompt = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runProjectShellPrompt),
+	Runner: jiri.RunnerFunc(runProjectShellPrompt),
 	Name:   "shell-prompt",
 	Short:  "Print a succinct status of projects suitable for shell prompts",
 	Long: `
@@ -140,10 +139,8 @@
 `,
 }
 
-func runProjectShellPrompt(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-
-	states, err := project.GetProjectStates(ctx, checkDirtyFlag)
+func runProjectShellPrompt(jirix *jiri.X, args []string) error {
+	states, err := project.GetProjectStates(jirix, checkDirtyFlag)
 	if err != nil {
 		return err
 	}
@@ -154,7 +151,7 @@
 	sort.Strings(names)
 
 	// Get the name of the current project.
-	currentProjectName, err := project.CurrentProjectName(ctx)
+	currentProjectName, err := project.CurrentProjectName(jirix)
 	if err != nil {
 		return err
 	}
@@ -194,7 +191,7 @@
 
 // cmdProjectPoll represents the "jiri project poll" command.
 var cmdProjectPoll = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runProjectPoll),
+	Runner: jiri.RunnerFunc(runProjectPoll),
 	Name:   "poll",
 	Short:  "Poll existing jiri projects",
 	Long: `
@@ -208,11 +205,10 @@
 
 // runProjectPoll generates a description of changes that exist
 // remotely but do not exist locally.
-func runProjectPoll(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
+func runProjectPoll(jirix *jiri.X, args []string) error {
 	projectSet := map[string]struct{}{}
 	if len(args) > 0 {
-		config, err := util.LoadConfig(ctx)
+		config, err := util.LoadConfig(jirix)
 		if err != nil {
 			return err
 		}
@@ -232,7 +228,7 @@
 			set.String.Union(projectSet, set.String.FromSlice(projects))
 		}
 	}
-	update, err := project.PollProjects(ctx, projectSet)
+	update, err := project.PollProjects(jirix, projectSet)
 	if err != nil {
 		return err
 	}
@@ -250,7 +246,7 @@
 		if err != nil {
 			return fmt.Errorf("MarshalIndent() failed: %v", err)
 		}
-		fmt.Fprintf(env.Stdout, "%s\n", bytes)
+		fmt.Fprintf(jirix.Stdout(), "%s\n", bytes)
 	}
 	return nil
 }
diff --git a/project/fake.go b/project/fake.go
index 5e1e125..fcbf5c7 100644
--- a/project/fake.go
+++ b/project/fake.go
@@ -11,10 +11,12 @@
 	"path/filepath"
 
 	"v.io/jiri/collect"
+	"v.io/jiri/jiri"
 	"v.io/jiri/tool"
 )
 
 type FakeJiriRoot struct {
+	X        *jiri.X
 	Dir      string
 	Projects map[string]string
 	remote   string
@@ -29,47 +31,48 @@
 )
 
 // NewFakeJiriRoot is the FakeJiriRoot factory.
-func NewFakeJiriRoot(ctx *tool.Context) (*FakeJiriRoot, error) {
+func NewFakeJiriRoot() (*FakeJiriRoot, error) {
+	// Create a fake JIRI_ROOT.
+	ctx := tool.NewDefaultContext()
+	rootDir, err := ctx.Run().TempDir("", "")
+	if err != nil {
+		return nil, err
+	}
+	jirix := &jiri.X{Context: ctx, Root: rootDir}
 	root := &FakeJiriRoot{
+		X:        jirix,
+		Dir:      rootDir,
 		Projects: map[string]string{},
 	}
 
 	// Create fake remote manifest and tools projects.
-	remoteDir, err := ctx.Run().TempDir("", "")
+	remoteDir, err := jirix.Run().TempDir("", "")
 	if err != nil {
 		return nil, err
 	}
 	doCleanup := true
 	defer func() {
 		if doCleanup {
-			root.Cleanup(ctx)
+			root.Cleanup()
 		}
 	}()
-
 	root.remote = remoteDir
-	if err := root.CreateRemoteProject(ctx, manifestProject); err != nil {
+	if err := root.CreateRemoteProject(manifestProject); err != nil {
 		return nil, err
 	}
-	if err := root.CreateRemoteProject(ctx, toolsProject); err != nil {
+	if err := root.CreateRemoteProject(toolsProject); err != nil {
 		return nil, err
 	}
 
 	// Create a fake manifest.
 	manifestDir := filepath.Join(remoteDir, manifestProject, manifestVersion)
-	if err := ctx.Run().MkdirAll(manifestDir, os.FileMode(0700)); err != nil {
+	if err := jirix.Run().MkdirAll(manifestDir, os.FileMode(0700)); err != nil {
 		return nil, err
 	}
-	if err := root.WriteRemoteManifest(ctx, &Manifest{}); err != nil {
+	if err := root.WriteRemoteManifest(&Manifest{}); err != nil {
 		return nil, err
 	}
-
-	// Create a fake JIRI_ROOT.
-	rootDir, err := ctx.Run().TempDir("", "")
-	if err != nil {
-		return nil, err
-	}
-	root.Dir = rootDir
-	if err := ctx.Git().Clone(root.Projects[manifestProject], filepath.Join(root.Dir, manifestProject)); err != nil {
+	if err := jirix.Git().Clone(root.Projects[manifestProject], filepath.Join(root.Dir, manifestProject)); err != nil {
 		return nil, err
 	}
 
@@ -77,14 +80,14 @@
 	// manifests. This is necessary to make sure that the commonly
 	// invoked DataDirPath() function, which uses the "jiri" tool
 	// configuration for its default, works.
-	if err := root.AddProject(ctx, Project{
+	if err := root.AddProject(Project{
 		Name:   toolsProject,
 		Path:   toolsProject,
 		Remote: root.Projects[toolsProject],
 	}); err != nil {
 		return nil, err
 	}
-	if err := root.AddTool(ctx, Tool{
+	if err := root.AddTool(Tool{
 		Name:    "jiri",
 		Data:    defaultDataDir,
 		Project: toolsProject,
@@ -94,13 +97,13 @@
 
 	// Add "gerrit" and "git" hosts to the manifest, as required by the "jiri"
 	// tool.
-	if err := root.AddHost(ctx, Host{
+	if err := root.AddHost(Host{
 		Name:     "gerrit",
 		Location: "git://example.com/gerrit",
 	}); err != nil {
 		return nil, err
 	}
-	if err := root.AddHost(ctx, Host{
+	if err := root.AddHost(Host{
 		Name:     "git",
 		Location: "git://example.com/git",
 	}); err != nil {
@@ -109,7 +112,7 @@
 
 	// Update the contents of the fake JIRI_ROOT instance based on
 	// the information recorded in the remote manifest.
-	if err := root.UpdateUniverse(ctx, false); err != nil {
+	if err := root.UpdateUniverse(false); err != nil {
 		return nil, err
 	}
 
@@ -118,15 +121,15 @@
 }
 
 // Cleanup cleans up the given Vanadium root fake.
-func (root FakeJiriRoot) Cleanup(ctx *tool.Context) error {
+func (root FakeJiriRoot) Cleanup() error {
 	var errs []error
 	collect.Errors(func() error {
 		if root.Dir == "" {
 			return nil
 		}
-		return ctx.Run().RemoveAll(root.Dir)
+		return root.X.Run().RemoveAll(root.Dir)
 	}, &errs)
-	collect.Errors(func() error { return ctx.Run().RemoveAll(root.remote) }, &errs)
+	collect.Errors(func() error { return root.X.Run().RemoveAll(root.remote) }, &errs)
 	if len(errs) != 0 {
 		return fmt.Errorf("Cleanup() failed: %v", errs)
 	}
@@ -134,39 +137,39 @@
 }
 
 // AddHost adds the given host to a remote manifest.
-func (root FakeJiriRoot) AddHost(ctx *tool.Context, host Host) error {
-	manifest, err := root.ReadRemoteManifest(ctx)
+func (root FakeJiriRoot) AddHost(host Host) error {
+	manifest, err := root.ReadRemoteManifest()
 	if err != nil {
 		return err
 	}
 	manifest.Hosts = append(manifest.Hosts, host)
-	if err := root.WriteRemoteManifest(ctx, manifest); err != nil {
+	if err := root.WriteRemoteManifest(manifest); err != nil {
 		return err
 	}
 	return nil
 }
 
 // AddProject adds the given project to a remote manifest.
-func (root FakeJiriRoot) AddProject(ctx *tool.Context, project Project) error {
-	manifest, err := root.ReadRemoteManifest(ctx)
+func (root FakeJiriRoot) AddProject(project Project) error {
+	manifest, err := root.ReadRemoteManifest()
 	if err != nil {
 		return err
 	}
 	manifest.Projects = append(manifest.Projects, project)
-	if err := root.WriteRemoteManifest(ctx, manifest); err != nil {
+	if err := root.WriteRemoteManifest(manifest); err != nil {
 		return err
 	}
 	return nil
 }
 
 // AddTool adds the given tool to a remote manifest.
-func (root FakeJiriRoot) AddTool(ctx *tool.Context, tool Tool) error {
-	manifest, err := root.ReadRemoteManifest(ctx)
+func (root FakeJiriRoot) AddTool(tool Tool) error {
+	manifest, err := root.ReadRemoteManifest()
 	if err != nil {
 		return err
 	}
 	manifest.Tools = append(manifest.Tools, tool)
-	if err := root.WriteRemoteManifest(ctx, manifest); err != nil {
+	if err := root.WriteRemoteManifest(manifest); err != nil {
 		return err
 	}
 	return nil
@@ -174,9 +177,9 @@
 
 // DisableRemoteManifestPush disables pushes to the remote manifest
 // repository.
-func (root FakeJiriRoot) DisableRemoteManifestPush(ctx *tool.Context) error {
+func (root FakeJiriRoot) DisableRemoteManifestPush() error {
 	dir := tool.RootDirOpt(filepath.Join(root.remote, manifestProject))
-	if err := ctx.Git(dir).CheckoutBranch("master"); err != nil {
+	if err := root.X.Git(dir).CheckoutBranch("master"); err != nil {
 		return err
 	}
 	return nil
@@ -184,37 +187,37 @@
 
 // EnableRemoteManifestPush enables pushes to the remote manifest
 // repository.
-func (root FakeJiriRoot) EnableRemoteManifestPush(ctx *tool.Context) error {
+func (root FakeJiriRoot) EnableRemoteManifestPush() error {
 	dir := tool.RootDirOpt(filepath.Join(root.remote, manifestProject))
-	if !ctx.Git(dir).BranchExists("non-master") {
-		if err := ctx.Git(dir).CreateBranch("non-master"); err != nil {
+	if !root.X.Git(dir).BranchExists("non-master") {
+		if err := root.X.Git(dir).CreateBranch("non-master"); err != nil {
 			return err
 		}
 	}
-	if err := ctx.Git(dir).CheckoutBranch("non-master"); err != nil {
+	if err := root.X.Git(dir).CheckoutBranch("non-master"); err != nil {
 		return err
 	}
 	return nil
 }
 
 // CreateRemoteProject creates a new remote project.
-func (root FakeJiriRoot) CreateRemoteProject(ctx *tool.Context, name string) error {
+func (root FakeJiriRoot) CreateRemoteProject(name string) error {
 	projectDir := filepath.Join(root.remote, name)
-	if err := ctx.Run().MkdirAll(projectDir, os.FileMode(0700)); err != nil {
+	if err := root.X.Run().MkdirAll(projectDir, os.FileMode(0700)); err != nil {
 		return err
 	}
-	if err := ctx.Git().Init(projectDir); err != nil {
+	if err := root.X.Git().Init(projectDir); err != nil {
 		return err
 	}
-	if err := ctx.Git(tool.RootDirOpt(projectDir)).CommitWithMessage("initial commit"); err != nil {
+	if err := root.X.Git(tool.RootDirOpt(projectDir)).CommitWithMessage("initial commit"); err != nil {
 		return err
 	}
 	root.Projects[name] = projectDir
 	return nil
 }
 
-func getManifest(ctx *tool.Context) string {
-	manifest := ctx.Manifest()
+func getManifest(jirix *jiri.X) string {
+	manifest := jirix.Manifest()
 	if manifest != "" {
 		return manifest
 	}
@@ -222,19 +225,19 @@
 }
 
 // ReadLocalManifest read a manifest from the local manifest project.
-func (root FakeJiriRoot) ReadLocalManifest(ctx *tool.Context) (*Manifest, error) {
-	path := filepath.Join(root.Dir, manifestProject, manifestVersion, getManifest(ctx))
-	return root.readManifest(ctx, path)
+func (root FakeJiriRoot) ReadLocalManifest() (*Manifest, error) {
+	path := filepath.Join(root.Dir, manifestProject, manifestVersion, getManifest(root.X))
+	return root.readManifest(path)
 }
 
 // ReadRemoteManifest read a manifest from the remote manifest project.
-func (root FakeJiriRoot) ReadRemoteManifest(ctx *tool.Context) (*Manifest, error) {
-	path := filepath.Join(root.remote, manifestProject, manifestVersion, getManifest(ctx))
-	return root.readManifest(ctx, path)
+func (root FakeJiriRoot) ReadRemoteManifest() (*Manifest, error) {
+	path := filepath.Join(root.remote, manifestProject, manifestVersion, getManifest(root.X))
+	return root.readManifest(path)
 }
 
-func (root FakeJiriRoot) readManifest(ctx *tool.Context, path string) (*Manifest, error) {
-	bytes, err := ctx.Run().ReadFile(path)
+func (root FakeJiriRoot) readManifest(path string) (*Manifest, error) {
+	bytes, err := root.X.Run().ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -247,13 +250,13 @@
 
 // UpdateUniverse synchronizes the content of the Vanadium root based
 // on the content of the remote manifest.
-func (root FakeJiriRoot) UpdateUniverse(ctx *tool.Context, gc bool) error {
+func (root FakeJiriRoot) UpdateUniverse(gc bool) error {
 	oldRoot := os.Getenv("JIRI_ROOT")
 	if err := os.Setenv("JIRI_ROOT", root.Dir); err != nil {
 		return fmt.Errorf("Setenv() failed: %v", err)
 	}
 	defer os.Setenv("JIRI_ROOT", oldRoot)
-	if err := UpdateUniverse(ctx, gc); err != nil {
+	if err := UpdateUniverse(root.X, gc); err != nil {
 		return err
 	}
 	return nil
@@ -261,32 +264,32 @@
 
 // WriteLocalManifest writes the given manifest to the local
 // manifest project.
-func (root FakeJiriRoot) WriteLocalManifest(ctx *tool.Context, manifest *Manifest) error {
+func (root FakeJiriRoot) WriteLocalManifest(manifest *Manifest) error {
 	dir := filepath.Join(root.Dir, manifestProject)
-	path := filepath.Join(dir, manifestVersion, getManifest(ctx))
-	return root.writeManifest(ctx, manifest, dir, path)
+	path := filepath.Join(dir, manifestVersion, getManifest(root.X))
+	return root.writeManifest(manifest, dir, path)
 }
 
 // WriteRemoteManifest writes the given manifest to the remote
 // manifest project.
-func (root FakeJiriRoot) WriteRemoteManifest(ctx *tool.Context, manifest *Manifest) error {
+func (root FakeJiriRoot) WriteRemoteManifest(manifest *Manifest) error {
 	dir := filepath.Join(root.remote, manifestProject)
-	path := filepath.Join(dir, manifestVersion, getManifest(ctx))
-	return root.writeManifest(ctx, manifest, dir, path)
+	path := filepath.Join(dir, manifestVersion, getManifest(root.X))
+	return root.writeManifest(manifest, dir, path)
 }
 
-func (root FakeJiriRoot) writeManifest(ctx *tool.Context, manifest *Manifest, dir, path string) error {
+func (root FakeJiriRoot) writeManifest(manifest *Manifest, dir, path string) error {
 	bytes, err := xml.Marshal(manifest)
 	if err != nil {
 		return fmt.Errorf("Marshal(%v) failed: %v", manifest, err)
 	}
-	if err := ctx.Run().WriteFile(path, bytes, os.FileMode(0600)); err != nil {
+	if err := root.X.Run().WriteFile(path, bytes, os.FileMode(0600)); err != nil {
 		return err
 	}
-	if err := ctx.Git(tool.RootDirOpt(dir)).Add(path); err != nil {
+	if err := root.X.Git(tool.RootDirOpt(dir)).Add(path); err != nil {
 		return err
 	}
-	if err := ctx.Git(tool.RootDirOpt(dir)).Commit(); err != nil {
+	if err := root.X.Git(tool.RootDirOpt(dir)).Commit(); err != nil {
 		return err
 	}
 	return nil
diff --git a/project/paths.go b/project/paths.go
index 45d874a..6586fe3 100644
--- a/project/paths.go
+++ b/project/paths.go
@@ -9,7 +9,7 @@
 	"os"
 	"path/filepath"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
 )
 
 const (
@@ -25,8 +25,8 @@
 // uses DataDirPath, and the default tool name is "jiri", so nothing actually
 // breaks.  We should revisit the whole data directory thing, and in particular
 // see if we can get rid of tools having to know their own names.
-func DataDirPath(ctx *tool.Context, toolName string) (string, error) {
-	_, projects, tools, _, err := readManifest(ctx, false)
+func DataDirPath(jirix *jiri.X, toolName string) (string, error) {
+	_, projects, tools, _, err := readManifest(jirix, false)
 	if err != nil {
 		return "", err
 	}
@@ -121,8 +121,8 @@
 	return path, nil
 }
 
-func getHost(ctx *tool.Context, name string) (string, error) {
-	hosts, _, _, _, err := readManifest(ctx, false)
+func getHost(jirix *jiri.X, name string) (string, error) {
+	hosts, _, _, _, err := readManifest(jirix, false)
 	if err != nil {
 		return "", err
 	}
@@ -134,13 +134,13 @@
 }
 
 // GerritHost returns the URL that hosts the Gerrit code review system.
-func GerritHost(ctx *tool.Context) (string, error) {
-	return getHost(ctx, "gerrit")
+func GerritHost(jirix *jiri.X) (string, error) {
+	return getHost(jirix, "gerrit")
 }
 
 // GitHost returns the URL that hosts the git repositories.
-func GitHost(ctx *tool.Context) (string, error) {
-	return getHost(ctx, "git")
+func GitHost(jirix *jiri.X) (string, error) {
+	return getHost(jirix, "git")
 }
 
 // JiriRoot returns the root of the jiri universe.
diff --git a/project/project.go b/project/project.go
index ade8eb8..5fb7b6e 100644
--- a/project/project.go
+++ b/project/project.go
@@ -17,6 +17,7 @@
 	"v.io/jiri/collect"
 	"v.io/jiri/gitutil"
 	"v.io/jiri/googlesource"
+	"v.io/jiri/jiri"
 	"v.io/jiri/runutil"
 	"v.io/jiri/tool"
 	"v.io/x/lib/cmdline"
@@ -183,14 +184,14 @@
 // CreateSnapshot creates a manifest that encodes the current state of
 // master branches of all projects and writes this snapshot out to the
 // given file.
-func CreateSnapshot(ctx *tool.Context, path string) error {
-	ctx.TimerPush("create snapshot")
-	defer ctx.TimerPop()
+func CreateSnapshot(jirix *jiri.X, path string) error {
+	jirix.TimerPush("create snapshot")
+	defer jirix.TimerPop()
 
 	manifest := Manifest{}
 
 	// Add all local projects to manifest.
-	localProjects, err := LocalProjects(ctx, FullScan)
+	localProjects, err := LocalProjects(jirix, FullScan)
 	if err != nil {
 		return err
 	}
@@ -205,7 +206,7 @@
 
 	// Add all hosts, tools, and hooks from the current manifest to the
 	// snapshot manifest.
-	hosts, _, tools, hooks, err := readManifest(ctx, true)
+	hosts, _, tools, hooks, err := readManifest(jirix, true)
 	if err != nil {
 		return err
 	}
@@ -220,7 +221,7 @@
 	}
 
 	perm := os.FileMode(0755)
-	if err := ctx.Run().MkdirAll(filepath.Dir(path), perm); err != nil {
+	if err := jirix.Run().MkdirAll(filepath.Dir(path), perm); err != nil {
 		return err
 	}
 	data, err := xml.MarshalIndent(manifest, "", "  ")
@@ -228,7 +229,7 @@
 		return fmt.Errorf("MarshalIndent(%v) failed: %v", manifest, err)
 	}
 	perm = os.FileMode(0644)
-	if err := ctx.Run().WriteFile(path, data, perm); err != nil {
+	if err := jirix.Run().WriteFile(path, data, perm); err != nil {
 		return err
 	}
 	return nil
@@ -238,15 +239,15 @@
 
 // CurrentManifest returns a manifest that identifies the result of
 // the most recent "jiri update" invocation.
-func CurrentManifest(ctx *tool.Context) (*Manifest, error) {
+func CurrentManifest(jirix *jiri.X) (*Manifest, error) {
 	currentManifestPath, err := ToAbs(currentManifestFileName)
 	if err != nil {
 		return nil, err
 	}
-	bytes, err := ctx.Run().ReadFile(currentManifestPath)
+	bytes, err := jirix.Run().ReadFile(currentManifestPath)
 	if err != nil {
 		if os.IsNotExist(err) {
-			fmt.Fprintf(ctx.Stderr(), `WARNING: Could not find %s.
+			fmt.Fprintf(jirix.Stderr(), `WARNING: Could not find %s.
 The contents of this file are stored as metadata in binaries the jiri
 tool builds. To fix this problem, please run "jiri update".
 `, currentManifestPath)
@@ -263,7 +264,7 @@
 
 // writeCurrentManifest writes the given manifest to a file that
 // stores the result of the most recent "jiri update" invocation.
-func writeCurrentManifest(ctx *tool.Context, manifest *Manifest) error {
+func writeCurrentManifest(jirix *jiri.X, manifest *Manifest) error {
 	currentManifestPath, err := ToAbs(currentManifestFileName)
 	if err != nil {
 		return err
@@ -272,7 +273,7 @@
 	if err != nil {
 		return fmt.Errorf("MarshalIndent(%v) failed: %v", manifest, err)
 	}
-	if err := ctx.Run().WriteFile(currentManifestPath, bytes, os.FileMode(0644)); err != nil {
+	if err := jirix.Run().WriteFile(currentManifestPath, bytes, os.FileMode(0644)); err != nil {
 		return err
 	}
 	return nil
@@ -281,15 +282,15 @@
 // CurrentProjectName gets the name of the current project from the
 // current directory by reading the jiri project metadata located in a
 // directory at the root of the current repository.
-func CurrentProjectName(ctx *tool.Context) (string, error) {
-	topLevel, err := ctx.Git().TopLevel()
+func CurrentProjectName(jirix *jiri.X) (string, error) {
+	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return "", nil
 	}
 	metadataDir := filepath.Join(topLevel, metadataDirName)
-	if _, err := ctx.Run().Stat(metadataDir); err == nil {
+	if _, err := jirix.Run().Stat(metadataDir); err == nil {
 		metadataFile := filepath.Join(metadataDir, metadataFileName)
-		bytes, err := ctx.Run().ReadFile(metadataFile)
+		bytes, err := jirix.Run().ReadFile(metadataFile)
 		if err != nil {
 			return "", err
 		}
@@ -304,20 +305,20 @@
 
 // setProjectRevisions sets the current project revision from the master for
 // each project as found on the filesystem
-func setProjectRevisions(ctx *tool.Context, projects Projects) (_ Projects, e error) {
+func setProjectRevisions(jirix *jiri.X, projects Projects) (_ Projects, e error) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		return nil, err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
 
 	for name, project := range projects {
 		switch project.Protocol {
 		case "git":
-			if err := ctx.Run().Chdir(project.Path); err != nil {
+			if err := jirix.Run().Chdir(project.Path); err != nil {
 				return nil, err
 			}
-			revision, err := ctx.Git().CurrentRevisionOfBranch("master")
+			revision, err := jirix.Git().CurrentRevisionOfBranch("master")
 			if err != nil {
 				return nil, err
 			}
@@ -335,25 +336,25 @@
 // projects in the manifest that exist locally will be returned.  Otherwise, a
 // full scan of the filesystem will take place, and all found projects will be
 // returned.
-func LocalProjects(ctx *tool.Context, scanMode ScanMode) (Projects, error) {
-	ctx.TimerPush("local projects")
-	defer ctx.TimerPop()
+func LocalProjects(jirix *jiri.X, scanMode ScanMode) (Projects, error) {
+	jirix.TimerPush("local projects")
+	defer jirix.TimerPop()
 
 	projects := Projects{}
 	if scanMode == FastScan {
 		// Fast path:  Full scan was not requested, and all projects in
 		// manifest exist on local filesystem.  We just use the projects
 		// directly from the manifest.
-		manifestProjects, _, err := ReadManifest(ctx)
+		manifestProjects, _, err := ReadManifest(jirix)
 		if err != nil {
 			return nil, err
 		}
-		projectsExist, err := projectsExistLocally(ctx, manifestProjects)
+		projectsExist, err := projectsExistLocally(jirix, manifestProjects)
 		if err != nil {
 			return nil, err
 		}
 		if projectsExist {
-			return setProjectRevisions(ctx, manifestProjects)
+			return setProjectRevisions(jirix, manifestProjects)
 		}
 	}
 
@@ -367,24 +368,24 @@
 
 	// Initial call to findLocalProjects -- it will recursively search all the
 	// directories under JiriRoot.
-	ctx.TimerPush("scan fs")
-	err = findLocalProjects(ctx, root, projects)
-	ctx.TimerPop()
+	jirix.TimerPush("scan fs")
+	err = findLocalProjects(jirix, root, projects)
+	jirix.TimerPop()
 	if err != nil {
 		return nil, err
 	}
-	return setProjectRevisions(ctx, projects)
+	return setProjectRevisions(jirix, projects)
 }
 
 // projectsExistLocally returns true iff all the given projects exist on the
 // local filesystem.
 // Note that this may return true even if there are projects on the local
 // filesystem not included in the provided projects argument.
-func projectsExistLocally(ctx *tool.Context, projects Projects) (bool, error) {
-	ctx.TimerPush("match manifest")
-	defer ctx.TimerPop()
+func projectsExistLocally(jirix *jiri.X, projects Projects) (bool, error) {
+	jirix.TimerPush("match manifest")
+	defer jirix.TimerPop()
 	for _, p := range projects {
-		isLocal, err := isLocalProject(ctx, p.Path)
+		isLocal, err := isLocalProject(jirix, p.Path)
 		if err != nil {
 			return false, err
 		}
@@ -398,23 +399,23 @@
 // PollProjects returns the set of changelists that exist remotely but not
 // locally. Changes are grouped by projects and contain author identification
 // and a description of their content.
-func PollProjects(ctx *tool.Context, projectSet map[string]struct{}) (_ Update, e error) {
-	ctx.TimerPush("poll projects")
-	defer ctx.TimerPop()
+func PollProjects(jirix *jiri.X, projectSet map[string]struct{}) (_ Update, e error) {
+	jirix.TimerPush("poll projects")
+	defer jirix.TimerPop()
 
 	// Switch back to current working directory when we're done.
 	cwd, err := os.Getwd()
 	if err != nil {
 		return nil, err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
 
 	// Gather local & remote project data.
-	localProjects, err := LocalProjects(ctx, FastScan)
+	localProjects, err := LocalProjects(jirix, FastScan)
 	if err != nil {
 		return nil, err
 	}
-	_, remoteProjects, _, _, err := readManifest(ctx, false)
+	_, remoteProjects, _, _, err := readManifest(jirix, false)
 	if err != nil {
 		return nil, err
 	}
@@ -443,17 +444,17 @@
 			case "git":
 
 				// Enter project directory - this assumes absolute paths.
-				if err := ctx.Run().Chdir(updateOp.destination); err != nil {
+				if err := jirix.Run().Chdir(updateOp.destination); err != nil {
 					return nil, err
 				}
 
 				// Fetch the latest from origin.
-				if err := ctx.Git().FetchRefspec("origin", updateOp.project.RemoteBranch); err != nil {
+				if err := jirix.Git().FetchRefspec("origin", updateOp.project.RemoteBranch); err != nil {
 					return nil, err
 				}
 
 				// Collect commits visible from FETCH_HEAD that aren't visible from master.
-				commitsText, err := ctx.Git().Log("FETCH_HEAD", "master", "%an%n%ae%n%B")
+				commitsText, err := jirix.Git().Log("FETCH_HEAD", "master", "%an%n%ae%n%B")
 				if err != nil {
 					return nil, err
 				}
@@ -480,8 +481,8 @@
 
 // ReadManifest retrieves and parses the manifest that determines what
 // projects and tools are part of the jiri universe.
-func ReadManifest(ctx *tool.Context) (Projects, Tools, error) {
-	_, p, t, _, e := readManifest(ctx, false)
+func ReadManifest(jirix *jiri.X) (Projects, Tools, error) {
+	_, p, t, _, e := readManifest(jirix, false)
 	return p, t, e
 }
 
@@ -490,11 +491,11 @@
 // TODO(nlacasse,toddw): Once the manifest project is specified in the
 // manifest, we should get the remote directly from the manifest, and not from
 // the filesystem.
-func getManifestRemote(ctx *tool.Context, manifestPath string) (string, error) {
+func getManifestRemote(jirix *jiri.X, manifestPath string) (string, error) {
 	var remote string
-	return remote, ctx.NewSeq().Pushd(manifestPath).Call(
+	return remote, jirix.NewSeq().Pushd(manifestPath).Call(
 		func() (e error) {
-			remote, e = ctx.Git().RemoteUrl("origin")
+			remote, e = jirix.Git().RemoteUrl("origin")
 			return
 		}, "get manifest origin").Done()
 }
@@ -502,15 +503,15 @@
 // readManifest implements the ReadManifest logic and provides an
 // optional flag that can be used to fetch the latest manifest updates
 // from the manifest repository.
-func readManifest(ctx *tool.Context, update bool) (Hosts, Projects, Tools, Hooks, error) {
-	ctx.TimerPush("read manifest")
-	defer ctx.TimerPop()
+func readManifest(jirix *jiri.X, update bool) (Hosts, Projects, Tools, Hooks, error) {
+	jirix.TimerPush("read manifest")
+	defer jirix.TimerPop()
 	if update {
 		manifestPath, err := ToAbs(".manifest")
 		if err != nil {
 			return nil, nil, nil, nil, err
 		}
-		manifestRemote, err := getManifestRemote(ctx, manifestPath)
+		manifestRemote, err := getManifestRemote(jirix, manifestPath)
 		if err != nil {
 			return nil, nil, nil, nil, err
 		}
@@ -521,16 +522,16 @@
 			Revision:     "HEAD",
 			RemoteBranch: "master",
 		}
-		if err := resetProject(ctx, project); err != nil {
+		if err := resetProject(jirix, project); err != nil {
 			return nil, nil, nil, nil, err
 		}
 	}
-	path, err := ResolveManifestPath(ctx.Manifest())
+	path, err := ResolveManifestPath(jirix.Manifest())
 	if err != nil {
 		return nil, nil, nil, nil, err
 	}
 	hosts, projects, tools, hooks, stack := Hosts{}, Projects{}, Tools{}, Hooks{}, map[string]struct{}{}
-	if err := loadManifest(ctx, path, hosts, projects, tools, hooks, stack); err != nil {
+	if err := loadManifest(jirix, path, hosts, projects, tools, hooks, stack); err != nil {
 		return nil, nil, nil, nil, err
 	}
 	return hosts, projects, tools, hooks, nil
@@ -540,74 +541,74 @@
 // remote counterparts identified by the given manifest. Optionally,
 // the 'gc' flag can be used to indicate that local projects that no
 // longer exist remotely should be removed.
-func UpdateUniverse(ctx *tool.Context, gc bool) (e error) {
-	ctx.TimerPush("update universe")
-	defer ctx.TimerPop()
-	_, remoteProjects, remoteTools, remoteHooks, err := readManifest(ctx, true)
+func UpdateUniverse(jirix *jiri.X, gc bool) (e error) {
+	jirix.TimerPush("update universe")
+	defer jirix.TimerPop()
+	_, remoteProjects, remoteTools, remoteHooks, err := readManifest(jirix, true)
 	if err != nil {
 		return err
 	}
 	// 1. Update all local projects to match their remote counterparts.
-	if err := updateProjects(ctx, remoteProjects, gc); err != nil {
+	if err := updateProjects(jirix, remoteProjects, gc); err != nil {
 		return err
 	}
 	// 2. Build all tools in a temporary directory.
-	tmpDir, err := ctx.Run().TempDir("", "tmp-jiri-tools-build")
+	tmpDir, err := jirix.Run().TempDir("", "tmp-jiri-tools-build")
 	if err != nil {
 		return fmt.Errorf("TempDir() failed: %v", err)
 	}
-	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)
-	if err := buildToolsFromMaster(ctx, remoteTools, tmpDir); err != nil {
+	defer collect.Error(func() error { return jirix.Run().RemoveAll(tmpDir) }, &e)
+	if err := buildToolsFromMaster(jirix, remoteTools, tmpDir); err != nil {
 		return err
 	}
 	// 3. Install the tools into $JIRI_ROOT/devtools/bin.
-	if err := InstallTools(ctx, tmpDir); err != nil {
+	if err := InstallTools(jirix, tmpDir); err != nil {
 		return err
 	}
 	// 4. Run all specified hooks
-	return runHooks(ctx, remoteHooks)
+	return runHooks(jirix, remoteHooks)
 }
 
 // ApplyToLocalMaster applies an operation expressed as the given function to
 // the local master branch of the given projects.
-func ApplyToLocalMaster(ctx *tool.Context, projects Projects, fn func() error) (e error) {
+func ApplyToLocalMaster(jirix *jiri.X, projects Projects, fn func() error) (e error) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
 
 	// Loop through all projects, checking out master and stashing any unstaged
 	// changes.
 	for _, project := range projects {
 		p := project
-		if err := ctx.Run().Chdir(p.Path); err != nil {
+		if err := jirix.Run().Chdir(p.Path); err != nil {
 			return err
 		}
 		switch p.Protocol {
 		case "git":
-			branch, err := ctx.Git().CurrentBranchName()
+			branch, err := jirix.Git().CurrentBranchName()
 			if err != nil {
 				return err
 			}
-			stashed, err := ctx.Git().Stash()
+			stashed, err := jirix.Git().Stash()
 			if err != nil {
 				return err
 			}
-			if err := ctx.Git().CheckoutBranch("master"); err != nil {
+			if err := jirix.Git().CheckoutBranch("master"); err != nil {
 				return err
 			}
 			// After running the function, return to this project's directory,
 			// checkout the original branch, and stash pop if necessary.
 			defer collect.Error(func() error {
-				if err := ctx.Run().Chdir(p.Path); err != nil {
+				if err := jirix.Run().Chdir(p.Path); err != nil {
 					return err
 				}
-				if err := ctx.Git().CheckoutBranch(branch); err != nil {
+				if err := jirix.Git().CheckoutBranch(branch); err != nil {
 					return err
 				}
 				if stashed {
-					return ctx.Git().StashPop()
+					return jirix.Git().StashPop()
 				}
 				return nil
 			}, &e)
@@ -620,14 +621,14 @@
 
 // BuildTools builds the given tools and places the resulting binaries into the
 // given directory.
-func BuildTools(ctx *tool.Context, tools Tools, outputDir string) error {
-	ctx.TimerPush("build tools")
-	defer ctx.TimerPop()
+func BuildTools(jirix *jiri.X, tools Tools, outputDir string) error {
+	jirix.TimerPush("build tools")
+	defer jirix.TimerPop()
 	if len(tools) == 0 {
 		// Nothing to do here...
 		return nil
 	}
-	projects, err := LocalProjects(ctx, FastScan)
+	projects, err := LocalProjects(jirix, FastScan)
 	if err != nil {
 		return err
 	}
@@ -664,7 +665,7 @@
 		workspaces = append(workspaces, strings.Split(envGoPath, string(filepath.ListSeparator))...)
 	}
 	var stderr bytes.Buffer
-	opts := ctx.Run().Opts()
+	opts := jirix.Run().Opts()
 	// We unset GOARCH and GOOS because jiri update should always build for the
 	// native architecture and OS.  Also, as of go1.5, setting GOBIN is not
 	// compatible with GOARCH or GOOS.
@@ -677,7 +678,7 @@
 	opts.Stdout = ioutil.Discard
 	opts.Stderr = &stderr
 	args := append([]string{"install"}, toolPkgs...)
-	if err := ctx.Run().CommandWithOpts(opts, "go", args...); err != nil {
+	if err := jirix.Run().CommandWithOpts(opts, "go", args...); err != nil {
 		return fmt.Errorf("tool build failed\n%v", stderr.String())
 	}
 	return nil
@@ -687,8 +688,8 @@
 // available in the local master branch of the tools repository. Notably, this
 // function does not perform any version control operation on the master
 // branch.
-func buildToolsFromMaster(ctx *tool.Context, tools Tools, outputDir string) error {
-	localProjects, err := LocalProjects(ctx, FastScan)
+func buildToolsFromMaster(jirix *jiri.X, tools Tools, outputDir string) error {
+	localProjects, err := LocalProjects(jirix, FastScan)
 	if err != nil {
 		return err
 	}
@@ -714,16 +715,16 @@
 	}
 
 	updateFn := func() error {
-		return ApplyToLocalMaster(ctx, toolProjects, func() error {
-			return BuildTools(ctx, toolsToBuild, outputDir)
+		return ApplyToLocalMaster(jirix, toolProjects, func() error {
+			return BuildTools(jirix, toolsToBuild, outputDir)
 		})
 	}
 
 	// Always log the output of updateFn, irrespective of
 	// the value of the verbose flag.
 	opts := runutil.Opts{Verbose: true}
-	if err := ctx.Run().FunctionWithOpts(opts, updateFn, "build tools: %v", strings.Join(toolNames, " ")); err != nil {
-		fmt.Fprintf(ctx.Stderr(), "%v\n", err)
+	if err := jirix.Run().FunctionWithOpts(opts, updateFn, "build tools: %v", strings.Join(toolNames, " ")); err != nil {
+		fmt.Fprintf(jirix.Stderr(), "%v\n", err)
 		failed = true
 	}
 	if failed {
@@ -735,18 +736,18 @@
 // CleanupProjects restores the given jiri projects back to their master
 // branches and gets rid of all the local changes. If "cleanupBranches" is
 // true, it will also delete all the non-master branches.
-func CleanupProjects(ctx *tool.Context, projects Projects, cleanupBranches bool) (e error) {
+func CleanupProjects(jirix *jiri.X, projects Projects, cleanupBranches bool) (e error) {
 	wd, err := os.Getwd()
 	if err != nil {
 		return fmt.Errorf("Getwd() failed: %v", err)
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(wd) }, &e)
+	defer collect.Error(func() error { return jirix.Run().Chdir(wd) }, &e)
 	for _, project := range projects {
 		localProjectDir := project.Path
-		if err := ctx.Run().Chdir(localProjectDir); err != nil {
+		if err := jirix.Run().Chdir(localProjectDir); err != nil {
 			return err
 		}
-		if err := resetLocalProject(ctx, cleanupBranches, project.RemoteBranch); err != nil {
+		if err := resetLocalProject(jirix, cleanupBranches, project.RemoteBranch); err != nil {
 			return err
 		}
 	}
@@ -755,31 +756,31 @@
 
 // resetLocalProject checks out the master branch, cleans up untracked files
 // and uncommitted changes, and optionally deletes all the other branches.
-func resetLocalProject(ctx *tool.Context, cleanupBranches bool, remoteBranch string) error {
+func resetLocalProject(jirix *jiri.X, cleanupBranches bool, remoteBranch string) error {
 	// Check out master and clean up changes.
-	curBranchName, err := ctx.Git().CurrentBranchName()
+	curBranchName, err := jirix.Git().CurrentBranchName()
 	if err != nil {
 		return err
 	}
 	if curBranchName != "master" {
-		if err := ctx.Git().CheckoutBranch("master", gitutil.ForceOpt(true)); err != nil {
+		if err := jirix.Git().CheckoutBranch("master", gitutil.ForceOpt(true)); err != nil {
 			return err
 		}
 	}
-	if err := ctx.Git().RemoveUntrackedFiles(); err != nil {
+	if err := jirix.Git().RemoveUntrackedFiles(); err != nil {
 		return err
 	}
 	// Discard any uncommitted changes.
 	if remoteBranch == "" {
 		remoteBranch = "master"
 	}
-	if err := ctx.Git().Reset("origin/" + remoteBranch); err != nil {
+	if err := jirix.Git().Reset("origin/" + remoteBranch); err != nil {
 		return err
 	}
 
 	// Delete all the other branches.
 	// At this point we should be at the master branch.
-	branches, _, err := ctx.Git().GetBranches()
+	branches, _, err := jirix.Git().GetBranches()
 	if err != nil {
 		return err
 	}
@@ -788,7 +789,7 @@
 			continue
 		}
 		if cleanupBranches {
-			if err := ctx.Git().DeleteBranch(branch, gitutil.ForceOpt(true)); err != nil {
+			if err := jirix.Git().DeleteBranch(branch, gitutil.ForceOpt(true)); err != nil {
 				return nil
 			}
 		}
@@ -798,7 +799,7 @@
 }
 
 // isLocalProject returns true if there is a project at the given path.
-func isLocalProject(ctx *tool.Context, path string) (bool, error) {
+func isLocalProject(jirix *jiri.X, path string) (bool, error) {
 	absPath, err := ToAbs(path)
 	if err != nil {
 		return false, err
@@ -806,7 +807,7 @@
 	// Existence of a metadata directory is how we know we've found a
 	// Jiri-maintained project.
 	metadataDir := filepath.Join(absPath, metadataDirName)
-	_, err = ctx.Run().Stat(metadataDir)
+	_, err = jirix.Run().Stat(metadataDir)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return false, nil
@@ -818,14 +819,14 @@
 
 // projectAtPath returns a Project struct corresponding to the project at the
 // path in the filesystem.
-func projectAtPath(ctx *tool.Context, path string) (Project, error) {
+func projectAtPath(jirix *jiri.X, path string) (Project, error) {
 	var project Project
 	absPath, err := ToAbs(path)
 	if err != nil {
 		return project, err
 	}
 	metadataFile := filepath.Join(absPath, metadataDirName, metadataFileName)
-	bytes, err := ctx.Run().ReadFile(metadataFile)
+	bytes, err := jirix.Run().ReadFile(metadataFile)
 	if err != nil {
 		return project, err
 	}
@@ -842,17 +843,17 @@
 
 // findLocalProjects scans the filesystem for all projects.  Note that project
 // directories can be nested recursively.
-func findLocalProjects(ctx *tool.Context, path string, projects Projects) error {
+func findLocalProjects(jirix *jiri.X, path string, projects Projects) error {
 	absPath, err := ToAbs(path)
 	if err != nil {
 		return err
 	}
-	isLocal, err := isLocalProject(ctx, absPath)
+	isLocal, err := isLocalProject(jirix, absPath)
 	if err != nil {
 		return err
 	}
 	if isLocal {
-		project, err := projectAtPath(ctx, absPath)
+		project, err := projectAtPath(jirix, absPath)
 		if err != nil {
 			return err
 		}
@@ -866,13 +867,13 @@
 	}
 
 	// Recurse into all the sub directories.
-	fileInfos, err := ctx.Run().ReadDir(path)
+	fileInfos, err := jirix.Run().ReadDir(path)
 	if err != nil {
 		return err
 	}
 	for _, fileInfo := range fileInfos {
 		if fileInfo.IsDir() && !strings.HasPrefix(fileInfo.Name(), ".") {
-			if err := findLocalProjects(ctx, filepath.Join(path, fileInfo.Name()), projects); err != nil {
+			if err := findLocalProjects(jirix, filepath.Join(path, fileInfo.Name()), projects); err != nil {
 				return err
 			}
 		}
@@ -882,10 +883,10 @@
 
 // InstallTools installs the tools from the given directory into
 // $JIRI_ROOT/devtools/bin.
-func InstallTools(ctx *tool.Context, dir string) error {
-	ctx.TimerPush("install tools")
-	defer ctx.TimerPop()
-	if ctx.DryRun() {
+func InstallTools(jirix *jiri.X, dir string) error {
+	jirix.TimerPush("install tools")
+	defer jirix.TimerPop()
+	if jirix.DryRun() {
 		// In "dry run" mode, no binaries are built.
 		return nil
 	}
@@ -902,14 +903,14 @@
 		installFn := func() error {
 			src := filepath.Join(dir, fi.Name())
 			dst := filepath.Join(binDir, fi.Name())
-			if err := ctx.Run().Rename(src, dst); err != nil {
+			if err := jirix.Run().Rename(src, dst); err != nil {
 				return err
 			}
 			return nil
 		}
 		opts := runutil.Opts{Verbose: true}
-		if err := ctx.Run().FunctionWithOpts(opts, installFn, "install tool %q", fi.Name()); err != nil {
-			fmt.Fprintf(ctx.Stderr(), "%v\n", err)
+		if err := jirix.Run().FunctionWithOpts(opts, installFn, "install tool %q", fi.Name()); err != nil {
+			fmt.Fprintf(jirix.Stderr(), "%v\n", err)
 			failed = true
 		}
 	}
@@ -924,7 +925,7 @@
 	}
 	for _, subCmd := range v23SubCmds {
 		subCmdPath := filepath.Join(binDir, subCmd)
-		if err := ctx.Run().RemoveAll(subCmdPath); err != nil {
+		if err := jirix.Run().RemoveAll(subCmdPath); err != nil {
 			return err
 		}
 	}
@@ -933,9 +934,9 @@
 }
 
 // runHooks runs the specified hooks
-func runHooks(ctx *tool.Context, hooks Hooks) error {
-	ctx.TimerPush("run hooks")
-	defer ctx.TimerPop()
+func runHooks(jirix *jiri.X, hooks Hooks) error {
+	jirix.TimerPush("run hooks")
+	defer jirix.TimerPop()
 	for _, hook := range hooks {
 		command := hook.Path
 		args := []string{}
@@ -946,7 +947,7 @@
 		for _, arg := range hook.Args {
 			args = append(args, arg.Arg)
 		}
-		if err := ctx.Run().Command(command, args...); err != nil {
+		if err := jirix.Run().Command(command, args...); err != nil {
 			return fmt.Errorf("Hook %v failed: %v command: %v args: %v", hook.Name, err, command, args)
 		}
 	}
@@ -955,24 +956,24 @@
 
 // resetProject advances the local master branch of the given
 // project, which is expected to exist locally at project.Path.
-func resetProject(ctx *tool.Context, project Project) error {
+func resetProject(jirix *jiri.X, project Project) error {
 	fn := func() error {
 		switch project.Protocol {
 		case "git":
 			if project.Remote == "" {
 				return fmt.Errorf("project %v does not have a remote", project.Name)
 			}
-			if err := ctx.Git().SetRemoteUrl("origin", project.Remote); err != nil {
+			if err := jirix.Git().SetRemoteUrl("origin", project.Remote); err != nil {
 				return err
 			}
-			if err := ctx.Git().Fetch("origin"); err != nil {
+			if err := jirix.Git().Fetch("origin"); err != nil {
 				return err
 			}
 
 			// Having a specific revision trumps everything else - once fetched,
 			// always reset to that revision.
 			if project.Revision != "" && project.Revision != "HEAD" {
-				return ctx.Git().Reset(project.Revision)
+				return jirix.Git().Reset(project.Revision)
 			}
 
 			// If no revision, reset to the configured remote branch, or master
@@ -981,18 +982,18 @@
 			if remoteBranch == "" {
 				remoteBranch = "master"
 			}
-			return ctx.Git().Reset("origin/" + remoteBranch)
+			return jirix.Git().Reset("origin/" + remoteBranch)
 		default:
 			return UnsupportedProtocolErr(project.Protocol)
 		}
 	}
-	return ApplyToLocalMaster(ctx, Projects{project.Name: project}, fn)
+	return ApplyToLocalMaster(jirix, Projects{project.Name: project}, fn)
 }
 
 // loadManifest loads the given manifest, processing all of its
 // imports, projects and tools settings.
-func loadManifest(ctx *tool.Context, path string, hosts Hosts, projects Projects, tools Tools, hooks Hooks, stack map[string]struct{}) error {
-	data, err := ctx.Run().ReadFile(path)
+func loadManifest(jirix *jiri.X, path string, hosts Hosts, projects Projects, tools Tools, hooks Hooks, stack map[string]struct{}) error {
+	data, err := jirix.Run().ReadFile(path)
 	if err != nil {
 		return err
 	}
@@ -1010,7 +1011,7 @@
 			return err
 		}
 		stack[manifest.Name] = struct{}{}
-		if err := loadManifest(ctx, path, hosts, projects, tools, hooks, stack); err != nil {
+		if err := loadManifest(jirix, path, hosts, projects, tools, hooks, stack); err != nil {
 			return err
 		}
 		delete(stack, manifest.Name)
@@ -1101,18 +1102,18 @@
 
 // reportNonMaster checks if the given project is on master branch and
 // if not, reports this fact along with information on how to update it.
-func reportNonMaster(ctx *tool.Context, project Project) (e error) {
+func reportNonMaster(jirix *jiri.X, project Project) (e error) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
-	if err := ctx.Run().Chdir(project.Path); err != nil {
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
+	if err := jirix.Run().Chdir(project.Path); err != nil {
 		return err
 	}
 	switch project.Protocol {
 	case "git":
-		current, err := ctx.Git().CurrentBranchName()
+		current, err := jirix.Git().CurrentBranchName()
 		if err != nil {
 			return err
 		}
@@ -1120,7 +1121,7 @@
 			line1 := fmt.Sprintf(`NOTE: "jiri update" only updates the "master" branch and the current branch is %q`, current)
 			line2 := fmt.Sprintf(`to update the %q branch once the master branch is updated, run "git merge master"`, current)
 			opts := runutil.Opts{Verbose: true}
-			ctx.Run().OutputWithOpts(opts, []string{line1, line2})
+			jirix.Run().OutputWithOpts(opts, []string{line1, line2})
 		}
 		return nil
 	default:
@@ -1130,7 +1131,7 @@
 
 // getRemoteHeadRevisions attempts to get the repo statuses from remote for HEAD
 // projects so we can detect when a local project is already up-to-date.
-func getRemoteHeadRevisions(ctx *tool.Context, remoteProjects Projects) {
+func getRemoteHeadRevisions(jirix *jiri.X, remoteProjects Projects) {
 	someAtHead := false
 	for _, rp := range remoteProjects {
 		if rp.Revision == "HEAD" {
@@ -1141,14 +1142,14 @@
 	if !someAtHead {
 		return
 	}
-	gitHost, gitHostErr := GitHost(ctx)
+	gitHost, gitHostErr := GitHost(jirix)
 	if gitHostErr != nil || !googlesource.IsGoogleSourceHost(gitHost) {
 		return
 	}
-	repoStatuses, err := googlesource.GetRepoStatuses(ctx, gitHost)
+	repoStatuses, err := googlesource.GetRepoStatuses(jirix.Context, gitHost)
 	if err != nil {
 		// Log the error but don't fail.
-		fmt.Fprintf(ctx.Stderr(), "Error fetching repo statuses from remote: %v\n", err)
+		fmt.Fprintf(jirix.Stderr(), "Error fetching repo statuses from remote: %v\n", err)
 		return
 	}
 	for name, rp := range remoteProjects {
@@ -1168,45 +1169,45 @@
 	}
 }
 
-func updateProjects(ctx *tool.Context, remoteProjects Projects, gc bool) error {
-	ctx.TimerPush("update projects")
-	defer ctx.TimerPop()
+func updateProjects(jirix *jiri.X, remoteProjects Projects, gc bool) error {
+	jirix.TimerPush("update projects")
+	defer jirix.TimerPop()
 
 	scanMode := FastScan
 	if gc {
 		scanMode = FullScan
 	}
-	localProjects, err := LocalProjects(ctx, scanMode)
+	localProjects, err := LocalProjects(jirix, scanMode)
 	if err != nil {
 		return err
 	}
-	getRemoteHeadRevisions(ctx, remoteProjects)
+	getRemoteHeadRevisions(jirix, remoteProjects)
 	ops, err := computeOperations(localProjects, remoteProjects, gc)
 	if err != nil {
 		return err
 	}
 
 	for _, op := range ops {
-		if err := op.Test(ctx); err != nil {
+		if err := op.Test(jirix); err != nil {
 			return err
 		}
 	}
 	failed := false
-	manifest := &Manifest{Label: ctx.Manifest()}
+	manifest := &Manifest{Label: jirix.Manifest()}
 	for _, op := range ops {
-		updateFn := func() error { return op.Run(ctx, manifest) }
+		updateFn := func() error { return op.Run(jirix, manifest) }
 		// Always log the output of updateFn, irrespective of
 		// the value of the verbose flag.
 		opts := runutil.Opts{Verbose: true}
-		if err := ctx.Run().FunctionWithOpts(opts, updateFn, "%v", op); err != nil {
-			fmt.Fprintf(ctx.Stderr(), "%v\n", err)
+		if err := jirix.Run().FunctionWithOpts(opts, updateFn, "%v", op); err != nil {
+			fmt.Fprintf(jirix.Stderr(), "%v\n", err)
 			failed = true
 		}
 	}
 	if failed {
 		return cmdline.ErrExitCode(2)
 	}
-	if err := writeCurrentManifest(ctx, manifest); err != nil {
+	if err := writeCurrentManifest(jirix, manifest); err != nil {
 		return err
 	}
 	return nil
@@ -1214,17 +1215,17 @@
 
 // writeMetadata stores the given project metadata in the directory
 // identified by the given path.
-func writeMetadata(ctx *tool.Context, project Project, dir string) (e error) {
+func writeMetadata(jirix *jiri.X, project Project, dir string) (e error) {
 	metadataDir := filepath.Join(dir, metadataDirName)
 	cwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
-	if err := ctx.Run().MkdirAll(metadataDir, os.FileMode(0755)); err != nil {
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
+	if err := jirix.Run().MkdirAll(metadataDir, os.FileMode(0755)); err != nil {
 		return err
 	}
-	if err := ctx.Run().Chdir(metadataDir); err != nil {
+	if err := jirix.Run().Chdir(metadataDir); err != nil {
 		return err
 	}
 	// Replace absolute project paths with relative paths to make it
@@ -1240,10 +1241,10 @@
 	}
 	metadataFile := filepath.Join(metadataDir, metadataFileName)
 	tmpMetadataFile := metadataFile + ".tmp"
-	if err := ctx.Run().WriteFile(tmpMetadataFile, bytes, os.FileMode(0644)); err != nil {
+	if err := jirix.Run().WriteFile(tmpMetadataFile, bytes, os.FileMode(0644)); err != nil {
 		return err
 	}
-	if err := ctx.Run().Rename(tmpMetadataFile, metadataFile); err != nil {
+	if err := jirix.Run().Rename(tmpMetadataFile, metadataFile); err != nil {
 		return err
 	}
 	return nil
@@ -1256,12 +1257,12 @@
 //
 // NOTE: The function assumes that the the given project is on a
 // master branch.
-func addProjectToManifest(ctx *tool.Context, manifest *Manifest, project Project) error {
+func addProjectToManifest(jirix *jiri.X, manifest *Manifest, project Project) error {
 	// If the project uses relative revision, replace it with an absolute one.
 	switch project.Protocol {
 	case "git":
 		if project.Revision == "HEAD" {
-			revision, err := ctx.Git(tool.RootDirOpt(project.Path)).CurrentRevision()
+			revision, err := jirix.Git(tool.RootDirOpt(project.Path)).CurrentRevision()
 			if err != nil {
 				return err
 			}
@@ -1283,11 +1284,11 @@
 	// Project identifies the project this operation pertains to.
 	Project() Project
 	// Run executes the operation.
-	Run(ctx *tool.Context, manifest *Manifest) error
+	Run(jirix *jiri.X, manifest *Manifest) error
 	// String returns a string representation of the operation.
 	String() string
 	// Test checks whether the operation would fail.
-	Test(ctx *tool.Context) error
+	Test(jirix *jiri.X) error
 }
 
 // commonOperation represents a project operation.
@@ -1311,28 +1312,28 @@
 	commonOperation
 }
 
-func (op createOperation) Run(ctx *tool.Context, manifest *Manifest) (e error) {
-	hosts, _, _, _, err := readManifest(ctx, false)
+func (op createOperation) Run(jirix *jiri.X, manifest *Manifest) (e error) {
+	hosts, _, _, _, err := readManifest(jirix, false)
 	if err != nil {
 		return err
 	}
 
 	path, perm := filepath.Dir(op.destination), os.FileMode(0755)
-	if err := ctx.Run().MkdirAll(path, perm); err != nil {
+	if err := jirix.Run().MkdirAll(path, perm); err != nil {
 		return err
 	}
 	// Create a temporary directory for the initial setup of the
 	// project to prevent an untimely termination from leaving the
 	// $JIRI_ROOT directory in an inconsistent state.
 	tmpDirPrefix := strings.Replace(op.Project().Name, "/", ".", -1) + "-"
-	tmpDir, err := ctx.Run().TempDir(path, tmpDirPrefix)
+	tmpDir, err := jirix.Run().TempDir(path, tmpDirPrefix)
 	if err != nil {
 		return err
 	}
-	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)
+	defer collect.Error(func() error { return jirix.Run().RemoveAll(tmpDir) }, &e)
 	switch op.project.Protocol {
 	case "git":
-		if err := ctx.Git().Clone(op.project.Remote, tmpDir); err != nil {
+		if err := jirix.Git().Clone(op.project.Remote, tmpDir); err != nil {
 			return err
 		}
 
@@ -1348,12 +1349,12 @@
 				if err != nil {
 					return err
 				}
-				src, err := ctx.Run().ReadFile(filepath.Join(mdir, githook.Path))
+				src, err := jirix.Run().ReadFile(filepath.Join(mdir, githook.Path))
 				if err != nil {
 					return err
 				}
 				dst := filepath.Join(gitHookDir, githook.Name)
-				if err := ctx.Run().WriteFile(dst, src, perm); err != nil {
+				if err := jirix.Run().WriteFile(dst, src, perm); err != nil {
 					return err
 				}
 			}
@@ -1363,11 +1364,11 @@
 		// write to .git/info/exclude
 		excludeString := "/.jiri/\n"
 		excludeDir := filepath.Join(tmpDir, ".git", "info")
-		if err := ctx.Run().MkdirAll(excludeDir, os.FileMode(0750)); err != nil {
+		if err := jirix.Run().MkdirAll(excludeDir, os.FileMode(0750)); err != nil {
 			return err
 		}
 		excludeFile := filepath.Join(excludeDir, "exclude")
-		if err := ctx.Run().WriteFile(excludeFile, []byte(excludeString), perm); err != nil {
+		if err := jirix.Run().WriteFile(excludeFile, []byte(excludeString), perm); err != nil {
 			return err
 		}
 
@@ -1375,38 +1376,38 @@
 		if err != nil {
 			return err
 		}
-		defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
-		if err := ctx.Run().Chdir(tmpDir); err != nil {
+		defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
+		if err := jirix.Run().Chdir(tmpDir); err != nil {
 			return err
 		}
-		if err := ctx.Git().Reset(op.project.Revision); err != nil {
+		if err := jirix.Git().Reset(op.project.Revision); err != nil {
 			return err
 		}
 	default:
 		return UnsupportedProtocolErr(op.project.Protocol)
 	}
-	if err := writeMetadata(ctx, op.project, tmpDir); err != nil {
+	if err := writeMetadata(jirix, op.project, tmpDir); err != nil {
 		return err
 	}
-	if err := ctx.Run().Chmod(tmpDir, os.FileMode(0755)); err != nil {
+	if err := jirix.Run().Chmod(tmpDir, os.FileMode(0755)); err != nil {
 		return err
 	}
-	if err := ctx.Run().Rename(tmpDir, op.destination); err != nil {
+	if err := jirix.Run().Rename(tmpDir, op.destination); err != nil {
 		return err
 	}
-	if err := resetProject(ctx, op.project); err != nil {
+	if err := resetProject(jirix, op.project); err != nil {
 		return err
 	}
-	return addProjectToManifest(ctx, manifest, op.project)
+	return addProjectToManifest(jirix, manifest, op.project)
 }
 
 func (op createOperation) String() string {
 	return fmt.Sprintf("create project %q in %q and advance it to %q", op.project.Name, op.destination, fmtRevision(op.project.Revision))
 }
 
-func (op createOperation) Test(ctx *tool.Context) error {
+func (op createOperation) Test(jirix *jiri.X) error {
 	// Check the local file system.
-	if _, err := ctx.Run().Stat(op.destination); err != nil {
+	if _, err := jirix.Run().Stat(op.destination); err != nil {
 		if !os.IsNotExist(err) {
 			return err
 		}
@@ -1424,7 +1425,7 @@
 	gc bool
 }
 
-func (op deleteOperation) Run(ctx *tool.Context, _ *Manifest) error {
+func (op deleteOperation) Run(jirix *jiri.X, _ *Manifest) error {
 	if op.gc {
 		// Never delete the <JiriProject>.
 		if op.project.Name == JiriProject {
@@ -1434,12 +1435,12 @@
 				"development tools and will thus not be deleted",
 			}
 			opts := runutil.Opts{Verbose: true}
-			ctx.Run().OutputWithOpts(opts, lines)
+			jirix.Run().OutputWithOpts(opts, lines)
 			return nil
 		}
 		// Never delete projects with non-master branches, uncommitted
 		// work, or untracked content.
-		git := ctx.Git(tool.RootDirOpt(op.project.Path))
+		git := jirix.Git(tool.RootDirOpt(op.project.Path))
 		branches, _, err := git.GetBranches()
 		if err != nil {
 			return err
@@ -1459,10 +1460,10 @@
 				"work, or untracked files and will thus not be deleted",
 			}
 			opts := runutil.Opts{Verbose: true}
-			ctx.Run().OutputWithOpts(opts, lines)
+			jirix.Run().OutputWithOpts(opts, lines)
 			return nil
 		}
-		return ctx.Run().RemoveAll(op.source)
+		return jirix.Run().RemoveAll(op.source)
 	}
 	lines := []string{
 		fmt.Sprintf("NOTE: project %v was not found in the project manifest", op.project.Name),
@@ -1471,7 +1472,7 @@
 		`or invoke "jiri update -gc" to remove all such local projects`,
 	}
 	opts := runutil.Opts{Verbose: true}
-	ctx.Run().OutputWithOpts(opts, lines)
+	jirix.Run().OutputWithOpts(opts, lines)
 	return nil
 }
 
@@ -1479,8 +1480,8 @@
 	return fmt.Sprintf("delete project %q from %q", op.project.Name, op.source)
 }
 
-func (op deleteOperation) Test(ctx *tool.Context) error {
-	if _, err := ctx.Run().Stat(op.source); err != nil {
+func (op deleteOperation) Test(jirix *jiri.X) error {
+	if _, err := jirix.Run().Stat(op.source); err != nil {
 		if os.IsNotExist(err) {
 			return fmt.Errorf("cannot delete %q as it does not exist", op.source)
 		}
@@ -1494,38 +1495,38 @@
 	commonOperation
 }
 
-func (op moveOperation) Run(ctx *tool.Context, manifest *Manifest) error {
+func (op moveOperation) Run(jirix *jiri.X, manifest *Manifest) error {
 	path, perm := filepath.Dir(op.destination), os.FileMode(0755)
-	if err := ctx.Run().MkdirAll(path, perm); err != nil {
+	if err := jirix.Run().MkdirAll(path, perm); err != nil {
 		return err
 	}
-	if err := ctx.Run().Rename(op.source, op.destination); err != nil {
+	if err := jirix.Run().Rename(op.source, op.destination); err != nil {
 		return err
 	}
-	if err := reportNonMaster(ctx, op.project); err != nil {
+	if err := reportNonMaster(jirix, op.project); err != nil {
 		return err
 	}
-	if err := resetProject(ctx, op.project); err != nil {
+	if err := resetProject(jirix, op.project); err != nil {
 		return err
 	}
-	if err := writeMetadata(ctx, op.project, op.project.Path); err != nil {
+	if err := writeMetadata(jirix, op.project, op.project.Path); err != nil {
 		return err
 	}
-	return addProjectToManifest(ctx, manifest, op.project)
+	return addProjectToManifest(jirix, manifest, op.project)
 }
 
 func (op moveOperation) String() string {
 	return fmt.Sprintf("move project %q located in %q to %q and advance it to %q", op.project.Name, op.source, op.destination, fmtRevision(op.project.Revision))
 }
 
-func (op moveOperation) Test(ctx *tool.Context) error {
-	if _, err := ctx.Run().Stat(op.source); err != nil {
+func (op moveOperation) Test(jirix *jiri.X) error {
+	if _, err := jirix.Run().Stat(op.source); err != nil {
 		if os.IsNotExist(err) {
 			return fmt.Errorf("cannot move %q to %q as the source does not exist", op.source, op.destination)
 		}
 		return err
 	}
-	if _, err := ctx.Run().Stat(op.destination); err != nil {
+	if _, err := jirix.Run().Stat(op.destination); err != nil {
 		if !os.IsNotExist(err) {
 			return err
 		}
@@ -1540,24 +1541,24 @@
 	commonOperation
 }
 
-func (op updateOperation) Run(ctx *tool.Context, manifest *Manifest) error {
-	if err := reportNonMaster(ctx, op.project); err != nil {
+func (op updateOperation) Run(jirix *jiri.X, manifest *Manifest) error {
+	if err := reportNonMaster(jirix, op.project); err != nil {
 		return err
 	}
-	if err := resetProject(ctx, op.project); err != nil {
+	if err := resetProject(jirix, op.project); err != nil {
 		return err
 	}
-	if err := writeMetadata(ctx, op.project, op.project.Path); err != nil {
+	if err := writeMetadata(jirix, op.project, op.project.Path); err != nil {
 		return err
 	}
-	return addProjectToManifest(ctx, manifest, op.project)
+	return addProjectToManifest(jirix, manifest, op.project)
 }
 
 func (op updateOperation) String() string {
 	return fmt.Sprintf("advance project %q located in %q to %q", op.project.Name, op.source, fmtRevision(op.project.Revision))
 }
 
-func (op updateOperation) Test(ctx *tool.Context) error {
+func (op updateOperation) Test(jirix *jiri.X) error {
 	return nil
 }
 
@@ -1567,15 +1568,15 @@
 	commonOperation
 }
 
-func (op nullOperation) Run(ctx *tool.Context, manifest *Manifest) error {
-	return addProjectToManifest(ctx, manifest, op.project)
+func (op nullOperation) Run(jirix *jiri.X, manifest *Manifest) error {
+	return addProjectToManifest(jirix, manifest, op.project)
 }
 
 func (op nullOperation) String() string {
 	return fmt.Sprintf("project %q located in %q at revision %q is up-to-date", op.project.Name, op.source, fmtRevision(op.project.Revision))
 }
 
-func (op nullOperation) Test(ctx *tool.Context) error {
+func (op nullOperation) Test(jirix *jiri.X) error {
 	return nil
 }
 
@@ -1696,8 +1697,8 @@
 
 // ParseNames identifies the set of projects that a jiri command should
 // be applied to.
-func ParseNames(ctx *tool.Context, args []string, defaultProjects map[string]struct{}) (map[string]Project, error) {
-	projects, _, err := ReadManifest(ctx)
+func ParseNames(jirix *jiri.X, args []string, defaultProjects map[string]struct{}) (map[string]Project, error) {
+	projects, _, err := ReadManifest(jirix)
 	if err != nil {
 		return nil, err
 	}
@@ -1712,7 +1713,7 @@
 		} else {
 			// Issue a warning if the target project does not exist in the
 			// project manifest.
-			fmt.Fprintf(ctx.Stderr(), "WARNING: project %q does not exist in the project manifest and will be skipped\n", name)
+			fmt.Fprintf(jirix.Stderr(), "WARNING: project %q does not exist in the project manifest and will be skipped\n", name)
 		}
 	}
 	return result, nil
diff --git a/project/project_test.go b/project/project_test.go
index 3355850..b15b445 100644
--- a/project/project_test.go
+++ b/project/project_test.go
@@ -18,25 +18,26 @@
 	"strings"
 	"testing"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
+	"v.io/jiri/jiri/jiritest"
 )
 
-func addRemote(t *testing.T, ctx *tool.Context, localProject, name, remoteProject string) {
+func addRemote(t *testing.T, jirix *jiri.X, localProject, name, remoteProject string) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(localProject); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(localProject); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().AddRemote(name, remoteProject); err != nil {
+	if err := jirix.Git().AddRemote(name, remoteProject); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func checkReadme(t *testing.T, ctx *tool.Context, project, message string) {
-	if _, err := ctx.Run().Stat(project); err != nil {
+func checkReadme(t *testing.T, jirix *jiri.X, project, message string) {
+	if _, err := jirix.Run().Stat(project); err != nil {
 		t.Fatalf("%v", err)
 	}
 	readmeFile := filepath.Join(project, "README")
@@ -50,8 +51,8 @@
 }
 
 // Checks that /.jiri/ is ignored in a local project checkout
-func checkGitIgnore(t *testing.T, ctx *tool.Context, project string) {
-	if _, err := ctx.Run().Stat(project); err != nil {
+func checkGitIgnore(t *testing.T, jirix *jiri.X, project string) {
+	if _, err := jirix.Run().Stat(project); err != nil {
 		t.Fatalf("%v", err)
 	}
 	gitInfoExcludeFile := filepath.Join(project, ".git", "info", "exclude")
@@ -65,7 +66,7 @@
 	}
 }
 
-func createLocalManifestCopy(t *testing.T, ctx *tool.Context, dir, manifestDir string) {
+func createLocalManifestCopy(t *testing.T, jirix *jiri.X, dir, manifestDir string) {
 	// Load the remote manifest.
 	manifestFile := filepath.Join(manifestDir, "v2", "default")
 	data, err := ioutil.ReadFile(manifestFile)
@@ -88,7 +89,7 @@
 	}
 }
 
-func createLocalManifestStub(t *testing.T, ctx *tool.Context, dir string) {
+func createLocalManifestStub(t *testing.T, jirix *jiri.X, dir string) {
 	// Create a manifest stub.
 	manifest := Manifest{}
 	manifest.Imports = append(manifest.Imports, Import{Name: "default"})
@@ -104,9 +105,9 @@
 	}
 }
 
-func createRemoteManifest(t *testing.T, ctx *tool.Context, dir string, remotes []string) {
+func createRemoteManifest(t *testing.T, jirix *jiri.X, dir string, remotes []string) {
 	manifestDir, perm := filepath.Join(dir, "v2"), os.FileMode(0755)
-	if err := ctx.Run().MkdirAll(manifestDir, perm); err != nil {
+	if err := jirix.Run().MkdirAll(manifestDir, perm); err != nil {
 		t.Fatalf("%v", err)
 	}
 	manifest := Manifest{}
@@ -129,10 +130,10 @@
 			Location: "git://example.com/git",
 		},
 	}
-	commitManifest(t, ctx, &manifest, dir)
+	commitManifest(t, jirix, &manifest, dir)
 }
 
-func commitManifest(t *testing.T, ctx *tool.Context, manifest *Manifest, manifestDir string) {
+func commitManifest(t *testing.T, jirix *jiri.X, manifest *Manifest, manifestDir string) {
 	data, err := xml.Marshal(*manifest)
 	if err != nil {
 		t.Fatalf("%v", err)
@@ -145,16 +146,16 @@
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(manifestDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(manifestDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CommitFile(manifestFile, "creating manifest"); err != nil {
+	if err := jirix.Git().CommitFile(manifestFile, "creating manifest"); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func deleteProject(t *testing.T, ctx *tool.Context, manifestDir, project string) {
+func deleteProject(t *testing.T, jirix *jiri.X, manifestDir, project string) {
 	manifestFile := filepath.Join(manifestDir, "v2", "default")
 	data, err := ioutil.ReadFile(manifestFile)
 	if err != nil {
@@ -165,20 +166,20 @@
 		t.Fatalf("Unmarshal() failed: %v\n%v", err, data)
 	}
 	manifest.Projects = append(manifest.Projects, Project{Exclude: true, Name: project})
-	commitManifest(t, ctx, &manifest, manifestDir)
+	commitManifest(t, jirix, &manifest, manifestDir)
 }
 
 // Identify the current revision for a given project.
-func currentRevision(t *testing.T, ctx *tool.Context, project string) string {
+func currentRevision(t *testing.T, jirix *jiri.X, project string) string {
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(project); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(project); err != nil {
 		t.Fatalf("%v", err)
 	}
-	revision, err := ctx.Git().CurrentRevision()
+	revision, err := jirix.Git().CurrentRevision()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -186,7 +187,7 @@
 }
 
 // Fix the revision in the manifest file.
-func setRevisionForProject(t *testing.T, ctx *tool.Context, manifestDir, project, revision string) {
+func setRevisionForProject(t *testing.T, jirix *jiri.X, manifestDir, project, revision string) {
 	manifestFile := filepath.Join(manifestDir, "v2", "default")
 	data, err := ioutil.ReadFile(manifestFile)
 	if err != nil {
@@ -208,19 +209,19 @@
 	if !updated {
 		t.Fatalf("failed to fix revision for project %v", project)
 	}
-	commitManifest(t, ctx, &manifest, manifestDir)
+	commitManifest(t, jirix, &manifest, manifestDir)
 }
 
-func holdProjectBack(t *testing.T, ctx *tool.Context, manifestDir, project string) {
-	revision := currentRevision(t, ctx, project)
-	setRevisionForProject(t, ctx, manifestDir, project, revision)
+func holdProjectBack(t *testing.T, jirix *jiri.X, manifestDir, project string) {
+	revision := currentRevision(t, jirix, project)
+	setRevisionForProject(t, jirix, manifestDir, project, revision)
 }
 
 func localProjectName(i int) string {
 	return "test-local-project-" + fmt.Sprintf("%d", i)
 }
 
-func moveProject(t *testing.T, ctx *tool.Context, manifestDir, project, dst string) {
+func moveProject(t *testing.T, jirix *jiri.X, manifestDir, project, dst string) {
 	manifestFile := filepath.Join(manifestDir, "v2", "default")
 	data, err := ioutil.ReadFile(manifestFile)
 	if err != nil {
@@ -242,50 +243,50 @@
 	if !updated {
 		t.Fatalf("failed to set path for project %v", project)
 	}
-	commitManifest(t, ctx, &manifest, manifestDir)
+	commitManifest(t, jirix, &manifest, manifestDir)
 }
 
 func remoteProjectName(i int) string {
 	return "test-remote-project-" + fmt.Sprintf("%d", i)
 }
 
-func setupNewProject(t *testing.T, ctx *tool.Context, dir, name string, ignore bool) string {
+func setupNewProject(t *testing.T, jirix *jiri.X, dir, name string, ignore bool) string {
 	projectDir, perm := filepath.Join(dir, name), os.FileMode(0755)
-	if err := ctx.Run().MkdirAll(projectDir, perm); err != nil {
+	if err := jirix.Run().MkdirAll(projectDir, perm); err != nil {
 		t.Fatalf("%v", err)
 	}
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Init(projectDir); err != nil {
+	if err := jirix.Git().Init(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if ignore {
 		ignoreFile := filepath.Join(projectDir, ".gitignore")
-		if err := ctx.Run().WriteFile(ignoreFile, []byte(metadataDirName), os.FileMode(0644)); err != nil {
+		if err := jirix.Run().WriteFile(ignoreFile, []byte(metadataDirName), os.FileMode(0644)); err != nil {
 			t.Fatalf("%v", err)
 		}
-		if err := ctx.Git().Add(ignoreFile); err != nil {
+		if err := jirix.Git().Add(ignoreFile); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}
-	if err := ctx.Git().Commit(); err != nil {
+	if err := jirix.Git().Commit(); err != nil {
 		t.Fatalf("%v", err)
 	}
 	return projectDir
 }
 
-func writeEmptyMetadata(t *testing.T, ctx *tool.Context, projectDir string) {
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+func writeEmptyMetadata(t *testing.T, jirix *jiri.X, projectDir string) {
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
 	metadataDir := filepath.Join(projectDir, metadataDirName)
-	if err := ctx.Run().MkdirAll(metadataDir, os.FileMode(0755)); err != nil {
+	if err := jirix.Run().MkdirAll(metadataDir, os.FileMode(0755)); err != nil {
 		t.Fatalf("%v", err)
 	}
 	bytes, err := xml.Marshal(Project{})
@@ -293,12 +294,12 @@
 		t.Fatalf("Marshal() failed: %v", err)
 	}
 	metadataFile := filepath.Join(metadataDir, metadataFileName)
-	if err := ctx.Run().WriteFile(metadataFile, bytes, os.FileMode(0644)); err != nil {
+	if err := jirix.Run().WriteFile(metadataFile, bytes, os.FileMode(0644)); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func writeReadme(t *testing.T, ctx *tool.Context, projectDir, message string) {
+func writeReadme(t *testing.T, jirix *jiri.X, projectDir, message string) {
 	path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644)
 	if err := ioutil.WriteFile(path, []byte(message), perm); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", path, perm, err)
@@ -307,39 +308,39 @@
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CommitFile(path, "creating README"); err != nil {
+	if err := jirix.Git().CommitFile(path, "creating README"); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func createAndCheckoutBranch(t *testing.T, ctx *tool.Context, projectDir, branch string) {
+func createAndCheckoutBranch(t *testing.T, jirix *jiri.X, projectDir, branch string) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
+	if err := jirix.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
-func resetToOriginMaster(t *testing.T, ctx *tool.Context, projectDir string) {
+func resetToOriginMaster(t *testing.T, jirix *jiri.X, projectDir string) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().Reset("origin/master"); err != nil {
+	if err := jirix.Git().Reset("origin/master"); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
@@ -359,49 +360,40 @@
 // TestLocalProjects tests the behavior of the LocalProjects method with
 // different ScanModes.
 func TestLocalProjects(t *testing.T) {
-	ctx := tool.NewDefaultContext()
-	rootDir, err := ctx.Run().TempDir("", "")
-	if err != nil {
-		t.Fatalf("TempDir() failed: %v", err)
-	}
-	defer ctx.Run().RemoveAll(rootDir)
-	oldRoot := os.Getenv("JIRI_ROOT")
-	if err := os.Setenv("JIRI_ROOT", rootDir); err != nil {
-		t.Fatalf("%v", err)
-	}
-	defer os.Setenv("JIRI_ROOT", oldRoot)
+	jirix, cleanup := jiritest.NewX(t)
+	defer cleanup()
 
-	manifestDir := setupNewProject(t, ctx, rootDir, ".manifest", false)
+	manifestDir := setupNewProject(t, jirix, jirix.Root, ".manifest", false)
 
 	// Create some projects.
 	numProjects, projectPaths := 3, []string{}
 	for i := 0; i < numProjects; i++ {
 		projectName := localProjectName(i)
-		projectPath := setupNewProject(t, ctx, rootDir, projectName, true)
+		projectPath := setupNewProject(t, jirix, jirix.Root, projectName, true)
 		project := Project{
 			Path:     projectPath,
 			Name:     projectName,
 			Protocol: "git",
 		}
-		if err := writeMetadata(ctx, project, projectPath); err != nil {
+		if err := writeMetadata(jirix, project, projectPath); err != nil {
 			t.Fatalf("writeMetadata %v %v) failed: %v\n", project, projectPath, err)
 		}
 		projectPaths = append(projectPaths, projectPath)
 	}
 
 	// Create manifest but only tell it about the first project.
-	createRemoteManifest(t, ctx, manifestDir, projectPaths[:1])
+	createRemoteManifest(t, jirix, manifestDir, projectPaths[:1])
 
 	// LocalProjects with scanMode = FastScan should only find the first
 	// project.
-	foundProjects, err := LocalProjects(ctx, FastScan)
+	foundProjects, err := LocalProjects(jirix, FastScan)
 	if err != nil {
 		t.Fatalf("LocalProjects(%v) failed: %v", FastScan, err)
 	}
 	checkProjectsMatchPaths(t, foundProjects, projectPaths[:1])
 
 	// LocalProjects with scanMode = FullScan should find all projects.
-	foundProjects, err = LocalProjects(ctx, FullScan)
+	foundProjects, err = LocalProjects(jirix, FullScan)
 	if err != nil {
 		t.Fatalf("LocalProjects(%v) failed: %v", FastScan, err)
 	}
@@ -409,10 +401,10 @@
 
 	// Check that deleting a project forces LocalProjects to run a full scan,
 	// even if FastScan is specified.
-	if err := ctx.Run().RemoveAll(projectPaths[0]); err != nil {
+	if err := jirix.Run().RemoveAll(projectPaths[0]); err != nil {
 		t.Fatalf("RemoveAll(%v) failed: %v", projectPaths[0])
 	}
-	foundProjects, err = LocalProjects(ctx, FastScan)
+	foundProjects, err = LocalProjects(jirix, FastScan)
 	if err != nil {
 		t.Fatalf("LocalProjects(%v) failed: %v", FastScan, err)
 	}
@@ -427,50 +419,46 @@
 	// Setup an instance of jiri universe, creating the remote repositories for
 	// the manifest and projects under the "remote" directory, which is ignored
 	// from the consideration of LocalProjects().
-	ctx := tool.NewDefaultContext()
-	rootDir, err := ctx.Run().TempDir("", "")
-	if err != nil {
-		t.Fatalf("TempDir() failed: %v", err)
-	}
-	defer ctx.Run().RemoveAll(rootDir)
-	localDir := filepath.Join(rootDir, "local")
-	remoteDir := filepath.Join(rootDir, "remote")
-	localManifest := setupNewProject(t, ctx, localDir, ".manifest", false)
-	writeEmptyMetadata(t, ctx, localManifest)
-	remoteManifest := setupNewProject(t, ctx, remoteDir, "test-remote-manifest", false)
-	addRemote(t, ctx, localManifest, "origin", remoteManifest)
-	numProjects, remoteProjects := 5, []string{}
-	for i := 0; i < numProjects; i++ {
-		remoteProject := setupNewProject(t, ctx, remoteDir, remoteProjectName(i), true)
-		remoteProjects = append(remoteProjects, remoteProject)
-	}
-	createRemoteManifest(t, ctx, remoteManifest, remoteProjects)
-	oldRoot := os.Getenv("JIRI_ROOT")
+	jirix, cleanup := jiritest.NewX(t)
+	defer cleanup()
+
+	localDir := filepath.Join(jirix.Root, "local")
+	remoteDir := filepath.Join(jirix.Root, "remote")
 	if err := os.Setenv("JIRI_ROOT", localDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer os.Setenv("JIRI_ROOT", oldRoot)
+
+	localManifest := setupNewProject(t, jirix, localDir, ".manifest", false)
+	writeEmptyMetadata(t, jirix, localManifest)
+	remoteManifest := setupNewProject(t, jirix, remoteDir, "test-remote-manifest", false)
+	addRemote(t, jirix, localManifest, "origin", remoteManifest)
+	numProjects, remoteProjects := 5, []string{}
+	for i := 0; i < numProjects; i++ {
+		remoteProject := setupNewProject(t, jirix, remoteDir, remoteProjectName(i), true)
+		remoteProjects = append(remoteProjects, remoteProject)
+	}
+	createRemoteManifest(t, jirix, remoteManifest, remoteProjects)
 
 	// Check that calling UpdateUniverse() creates local copies of
 	// the remote repositories, advancing projects to HEAD or to
 	// the fixed revision set in the manifest.
 	for _, remoteProject := range remoteProjects {
-		writeReadme(t, ctx, remoteProject, "revision 1")
+		writeReadme(t, jirix, remoteProject, "revision 1")
 	}
-	holdProjectBack(t, ctx, remoteManifest, remoteProjects[0])
+	holdProjectBack(t, jirix, remoteManifest, remoteProjects[0])
 	for _, remoteProject := range remoteProjects {
-		writeReadme(t, ctx, remoteProject, "revision 2")
+		writeReadme(t, jirix, remoteProject, "revision 2")
 	}
-	if err := UpdateUniverse(ctx, false); err != nil {
+	if err := UpdateUniverse(jirix, false); err != nil {
 		t.Fatalf("%v", err)
 	}
 	checkCreateFn := func(i int, revision string) {
 		localProject := filepath.Join(localDir, localProjectName(i))
-		checkGitIgnore(t, ctx, localProject)
+		checkGitIgnore(t, jirix, localProject)
 		if i == 0 {
-			checkReadme(t, ctx, localProject, "revision 1")
+			checkReadme(t, jirix, localProject, "revision 1")
 		} else {
-			checkReadme(t, ctx, localProject, revision)
+			checkReadme(t, jirix, localProject, revision)
 		}
 	}
 	for i, _ := range remoteProjects {
@@ -480,16 +468,16 @@
 	// Commit more work to the remote repositories and check that
 	// calling UpdateUniverse() advances project to HEAD or to the
 	// fixed revision set in the manifest.
-	holdProjectBack(t, ctx, remoteManifest, remoteProjects[1])
+	holdProjectBack(t, jirix, remoteManifest, remoteProjects[1])
 	for _, remoteProject := range remoteProjects {
-		writeReadme(t, ctx, remoteProject, "revision 3")
+		writeReadme(t, jirix, remoteProject, "revision 3")
 	}
-	if err := UpdateUniverse(ctx, false); err != nil {
+	if err := UpdateUniverse(jirix, false); err != nil {
 		t.Fatalf("%v", err)
 	}
 	checkUpdateFn := func(i int, revision string) {
 		if i == 1 {
-			checkReadme(t, ctx, filepath.Join(localDir, localProjectName(i)), "revision 2")
+			checkReadme(t, jirix, filepath.Join(localDir, localProjectName(i)), "revision 2")
 		} else {
 			checkCreateFn(i, revision)
 		}
@@ -506,7 +494,7 @@
 	if err := ioutil.WriteFile(file, want, perm); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", file, err, perm)
 	}
-	if err := UpdateUniverse(ctx, false); err != nil {
+	if err := UpdateUniverse(jirix, false); err != nil {
 		t.Fatalf("%v", err)
 	}
 	got, err := ioutil.ReadFile(file)
@@ -521,13 +509,13 @@
 	// located and check that UpdateUniverse() moves the local
 	// copy of the project.
 	destination := filepath.Join("test", localProjectName(2))
-	moveProject(t, ctx, remoteManifest, remoteProjects[2], destination)
-	if err := UpdateUniverse(ctx, false); err != nil {
+	moveProject(t, jirix, remoteManifest, remoteProjects[2], destination)
+	if err := UpdateUniverse(jirix, false); err != nil {
 		t.Fatalf("%v", err)
 	}
 	checkMoveFn := func(i int, revision string) {
 		if i == 2 {
-			checkReadme(t, ctx, filepath.Join(localDir, destination), revision)
+			checkReadme(t, jirix, filepath.Join(localDir, destination), revision)
 		} else {
 			checkUpdateFn(i, revision)
 		}
@@ -538,14 +526,14 @@
 
 	// Delete a remote project and check that UpdateUniverse()
 	// deletes the local copy of the project.
-	deleteProject(t, ctx, remoteManifest, remoteProjects[3])
-	if err := UpdateUniverse(ctx, true); err != nil {
+	deleteProject(t, jirix, remoteManifest, remoteProjects[3])
+	if err := UpdateUniverse(jirix, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 	checkDeleteFn := func(i int, revision string) {
 		if i == 3 {
 			localProject := filepath.Join(localDir, localProjectName(i))
-			if _, err := ctx.Run().Stat(localProject); err == nil {
+			if _, err := jirix.Run().Stat(localProject); err == nil {
 				t.Fatalf("project %v has not been deleted", localProject)
 			} else {
 				if !os.IsNotExist(err) {
@@ -563,32 +551,32 @@
 	// Commit to a non-master branch of a remote project and check that
 	// UpdateUniverse() can update the local project to point to a revision on
 	// that branch.
-	writeReadme(t, ctx, remoteProjects[4], "master commit")
-	createAndCheckoutBranch(t, ctx, remoteProjects[4], "non_master")
-	writeReadme(t, ctx, remoteProjects[4], "non master commit")
-	remoteBranchRevision := currentRevision(t, ctx, remoteProjects[4])
-	setRevisionForProject(t, ctx, remoteManifest, remoteProjects[4], remoteBranchRevision)
-	if err := UpdateUniverse(ctx, true); err != nil {
+	writeReadme(t, jirix, remoteProjects[4], "master commit")
+	createAndCheckoutBranch(t, jirix, remoteProjects[4], "non_master")
+	writeReadme(t, jirix, remoteProjects[4], "non master commit")
+	remoteBranchRevision := currentRevision(t, jirix, remoteProjects[4])
+	setRevisionForProject(t, jirix, remoteManifest, remoteProjects[4], remoteBranchRevision)
+	if err := UpdateUniverse(jirix, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 	localProject := filepath.Join(localDir, localProjectName(4))
-	localBranchRevision := currentRevision(t, ctx, localProject)
+	localBranchRevision := currentRevision(t, jirix, localProject)
 	if localBranchRevision != remoteBranchRevision {
 		t.Fatalf("project 4 is at revision %v, expected %v\n", localBranchRevision, remoteBranchRevision)
 	}
 	// Reset back to origin/master so the next update without a "revision" works.
-	resetToOriginMaster(t, ctx, localProject)
+	resetToOriginMaster(t, jirix, localProject)
 
 	// Create a local manifest that imports the remote manifest
 	// and check that UpdateUniverse() has no effect.
-	createLocalManifestStub(t, ctx, localDir)
-	if err := UpdateUniverse(ctx, true); err != nil {
+	createLocalManifestStub(t, jirix, localDir)
+	if err := UpdateUniverse(jirix, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	checkRebaseFn := func(i int, revision string) {
 		if i == 4 {
-			checkReadme(t, ctx, localProject, "non master commit")
+			checkReadme(t, jirix, localProject, "non master commit")
 		} else {
 			checkDeleteFn(i, revision)
 		}
@@ -600,9 +588,9 @@
 	// Create a local manifest that matches the remote manifest,
 	// then revert the remote manifest to its initial version and
 	// check that UpdateUniverse() has no effect.
-	createLocalManifestCopy(t, ctx, localDir, remoteManifest)
-	createRemoteManifest(t, ctx, remoteManifest, remoteProjects)
-	if err := UpdateUniverse(ctx, true); err != nil {
+	createLocalManifestCopy(t, jirix, localDir, remoteManifest)
+	createRemoteManifest(t, jirix, remoteManifest, remoteProjects)
+	if err := UpdateUniverse(jirix, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 	for i, _ := range remoteProjects {
diff --git a/project/state.go b/project/state.go
index eef43b5..2f86aa9 100644
--- a/project/state.go
+++ b/project/state.go
@@ -8,6 +8,7 @@
 	"os"
 	"path/filepath"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/tool"
 )
 
@@ -24,11 +25,11 @@
 	Project        Project
 }
 
-func setProjectState(ctx *tool.Context, state *ProjectState, checkDirty bool, ch chan<- error) {
+func setProjectState(jirix *jiri.X, state *ProjectState, checkDirty bool, ch chan<- error) {
 	var err error
 	switch state.Project.Protocol {
 	case "git":
-		scm := ctx.Git(tool.RootDirOpt(state.Project.Path))
+		scm := jirix.Git(tool.RootDirOpt(state.Project.Path))
 		var branches []string
 		branches, state.CurrentBranch, err = scm.GetBranches()
 		if err != nil {
@@ -38,7 +39,7 @@
 		for _, branch := range branches {
 			file := filepath.Join(state.Project.Path, MetadataDirName(), branch, ".gerrit_commit_message")
 			hasFile := true
-			if _, err := ctx.Run().Stat(file); err != nil {
+			if _, err := jirix.Run().Stat(file); err != nil {
 				if !os.IsNotExist(err) {
 					ch <- err
 					return
@@ -69,8 +70,8 @@
 	ch <- nil
 }
 
-func GetProjectStates(ctx *tool.Context, checkDirty bool) (map[string]*ProjectState, error) {
-	projects, err := LocalProjects(ctx, FastScan)
+func GetProjectStates(jirix *jiri.X, checkDirty bool) (map[string]*ProjectState, error) {
+	projects, err := LocalProjects(jirix, FastScan)
 	if err != nil {
 		return nil, err
 	}
@@ -81,8 +82,8 @@
 			Project: project,
 		}
 		states[name] = state
-		// ctx is not threadsafe, so we make a clone for each goroutine.
-		go setProjectState(ctx.Clone(tool.ContextOpts{}), state, checkDirty, sem)
+		// jirix is not threadsafe, so we make a clone for each goroutine.
+		go setProjectState(jirix.Clone(tool.ContextOpts{}), state, checkDirty, sem)
 	}
 	for _ = range projects {
 		err := <-sem
diff --git a/rebuild.go b/rebuild.go
index 577c1d8..2ca2de0 100644
--- a/rebuild.go
+++ b/rebuild.go
@@ -8,14 +8,14 @@
 	"fmt"
 
 	"v.io/jiri/collect"
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
-	"v.io/jiri/tool"
 	"v.io/x/lib/cmdline"
 )
 
 // cmdRebuild represents the "jiri rebuild" command.
 var cmdRebuild = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runRebuild),
+	Runner: jiri.RunnerFunc(runRebuild),
 	Name:   "rebuild",
 	Short:  "Rebuild all jiri tools",
 	Long: `
@@ -28,21 +28,20 @@
 `,
 }
 
-func runRebuild(env *cmdline.Env, args []string) (e error) {
-	ctx := tool.NewContextFromEnv(env)
-	_, tools, err := project.ReadManifest(ctx)
+func runRebuild(jirix *jiri.X, args []string) (e error) {
+	_, tools, err := project.ReadManifest(jirix)
 	if err != nil {
 		return err
 	}
 
 	// Create a temporary directory in which tools will be built.
-	tmpDir, err := ctx.Run().TempDir("", "tmp-jiri-rebuild")
+	tmpDir, err := jirix.Run().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 ctx.Run().RemoveAll(tmpDir) }, &e)
+	defer collect.Error(func() error { return jirix.Run().RemoveAll(tmpDir) }, &e)
 
 	// Paranoid sanity checking.
 	if _, ok := tools[project.JiriName]; !ok {
@@ -50,8 +49,8 @@
 	}
 
 	// Build and install tools.
-	if err := project.BuildTools(ctx, tools, tmpDir); err != nil {
+	if err := project.BuildTools(jirix, tools, tmpDir); err != nil {
 		return err
 	}
-	return project.InstallTools(ctx, tmpDir)
+	return project.InstallTools(jirix, tmpDir)
 }
diff --git a/snapshot.go b/snapshot.go
index f90f81e..0afc338 100644
--- a/snapshot.go
+++ b/snapshot.go
@@ -15,8 +15,8 @@
 
 	"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"
 )
 
@@ -47,7 +47,7 @@
 
 // cmdSnapshotCreate represents the "jiri snapshot create" command.
 var cmdSnapshotCreate = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runSnapshotCreate),
+	Runner: jiri.RunnerFunc(runSnapshotCreate),
 	Name:   "create",
 	Short:  "Create a new project snapshot",
 	Long: `
@@ -83,13 +83,12 @@
 	ArgsLong: "<label> is the snapshot label.",
 }
 
-func runSnapshotCreate(env *cmdline.Env, args []string) error {
+func runSnapshotCreate(jirix *jiri.X, args []string) error {
 	if len(args) != 1 {
-		return env.UsageErrorf("unexpected number of arguments")
+		return jirix.UsageErrorf("unexpected number of arguments")
 	}
 	label := args[0]
-	ctx := tool.NewContextFromEnv(env)
-	if err := checkSnapshotDir(ctx); err != nil {
+	if err := checkSnapshotDir(jirix); err != nil {
 		return err
 	}
 	snapshotDir, err := getSnapshotDir()
@@ -101,14 +100,14 @@
 	// state and push the changes to the remote repository (if
 	// applicable), or fail with no effect.
 	createFn := func() error {
-		revision, err := ctx.Git().CurrentRevision()
+		revision, err := jirix.Git().CurrentRevision()
 		if err != nil {
 			return err
 		}
-		if err := createSnapshot(ctx, snapshotDir, snapshotFile, label); err != nil {
+		if err := createSnapshot(jirix, snapshotDir, snapshotFile, label); err != nil {
 			// Clean up on all errors.
-			ctx.Git().Reset(revision)
-			ctx.Git().RemoveUntrackedFiles()
+			jirix.Git().Reset(revision)
+			jirix.Git().RemoveUntrackedFiles()
 			return err
 		}
 		return nil
@@ -120,7 +119,7 @@
 		Protocol: "git",
 		Revision: "HEAD",
 	}
-	if err := project.ApplyToLocalMaster(ctx, project.Projects{p.Name: p}, createFn); err != nil {
+	if err := project.ApplyToLocalMaster(jirix, project.Projects{p.Name: p}, createFn); err != nil {
 		return err
 	}
 	return nil
@@ -128,53 +127,53 @@
 
 // checkSnapshotDir makes sure that he local snapshot directory exists
 // and is initialized properly.
-func checkSnapshotDir(ctx *tool.Context) (e error) {
+func checkSnapshotDir(jirix *jiri.X) (e error) {
 	snapshotDir, err := getSnapshotDir()
 	if err != nil {
 		return err
 	}
-	if _, err := ctx.Run().Stat(snapshotDir); err != nil {
+	if _, err := jirix.Run().Stat(snapshotDir); err != nil {
 		if !os.IsNotExist(err) {
 			return err
 		}
 		if remoteFlag {
-			if err := ctx.Run().MkdirAll(snapshotDir, 0755); err != nil {
+			if err := jirix.Run().MkdirAll(snapshotDir, 0755); err != nil {
 				return err
 			}
 			return nil
 		}
 		createFn := func() (err error) {
-			if err := ctx.Run().MkdirAll(snapshotDir, 0755); err != nil {
+			if err := jirix.Run().MkdirAll(snapshotDir, 0755); err != nil {
 				return err
 			}
-			if err := ctx.Git().Init(snapshotDir); err != nil {
+			if err := jirix.Git().Init(snapshotDir); err != nil {
 				return err
 			}
 			cwd, err := os.Getwd()
 			if err != nil {
 				return err
 			}
-			defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
-			if err := ctx.Run().Chdir(snapshotDir); err != nil {
+			defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
+			if err := jirix.Run().Chdir(snapshotDir); err != nil {
 				return err
 			}
-			if err := ctx.Git().Commit(); err != nil {
+			if err := jirix.Git().Commit(); err != nil {
 				return err
 			}
 			return nil
 		}
 		if err := createFn(); err != nil {
-			ctx.Run().RemoveAll(snapshotDir)
+			jirix.Run().RemoveAll(snapshotDir)
 			return err
 		}
 	}
 	return nil
 }
 
-func createSnapshot(ctx *tool.Context, snapshotDir, snapshotFile, label string) error {
+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(ctx, snapshotFile); err != nil {
+	if err := project.CreateSnapshot(jirix, snapshotFile); err != nil {
 		return err
 	}
 
@@ -182,19 +181,19 @@
 	// latest snapshot.
 	symlink := filepath.Join(snapshotDir, label)
 	newSymlink := symlink + ".new"
-	if err := ctx.Run().RemoveAll(newSymlink); err != nil {
+	if err := jirix.Run().RemoveAll(newSymlink); err != nil {
 		return err
 	}
 	relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator))
-	if err := ctx.Run().Symlink(relativeSnapshotPath, newSymlink); err != nil {
+	if err := jirix.Run().Symlink(relativeSnapshotPath, newSymlink); err != nil {
 		return err
 	}
-	if err := ctx.Run().Rename(newSymlink, symlink); err != nil {
+	if err := jirix.Run().Rename(newSymlink, symlink); err != nil {
 		return err
 	}
 
 	// Revision the changes.
-	if err := revisionChanges(ctx, snapshotDir, snapshotFile, label); err != nil {
+	if err := revisionChanges(jirix, snapshotDir, snapshotFile, label); err != nil {
 		return err
 	}
 	return nil
@@ -220,28 +219,28 @@
 // revisionChanges commits changes identified by the given manifest
 // file and label to the manifest repository and (if applicable)
 // pushes these changes to the remote repository.
-func revisionChanges(ctx *tool.Context, snapshotDir, snapshotFile, label string) (e error) {
+func revisionChanges(jirix *jiri.X, snapshotDir, snapshotFile, label string) (e error) {
 	cwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
-	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
-	if err := ctx.Run().Chdir(snapshotDir); err != nil {
+	defer collect.Error(func() error { return jirix.Run().Chdir(cwd) }, &e)
+	if err := jirix.Run().Chdir(snapshotDir); err != nil {
 		return err
 	}
 	relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator))
-	if err := ctx.Git().Add(relativeSnapshotPath); err != nil {
+	if err := jirix.Git().Add(relativeSnapshotPath); err != nil {
 		return err
 	}
-	if err := ctx.Git().Add(label); err != nil {
+	if err := jirix.Git().Add(label); err != nil {
 		return err
 	}
 	name := strings.TrimPrefix(snapshotFile, snapshotDir)
-	if err := ctx.Git().CommitWithMessage(fmt.Sprintf("adding snapshot %q for label %q", name, label)); err != nil {
+	if err := jirix.Git().CommitWithMessage(fmt.Sprintf("adding snapshot %q for label %q", name, label)); err != nil {
 		return err
 	}
 	if remoteFlag {
-		if err := ctx.Git().Push("origin", "master", gitutil.VerifyOpt(false)); err != nil {
+		if err := jirix.Git().Push("origin", "master", gitutil.VerifyOpt(false)); err != nil {
 			return err
 		}
 	}
@@ -250,7 +249,7 @@
 
 // cmdSnapshotList represents the "jiri snapshot list" command.
 var cmdSnapshotList = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runSnapshotList),
+	Runner: jiri.RunnerFunc(runSnapshotList),
 	Name:   "list",
 	Short:  "List existing project snapshots",
 	Long: `
@@ -262,9 +261,8 @@
 	ArgsLong: "<label ...> is a list of snapshot labels.",
 }
 
-func runSnapshotList(env *cmdline.Env, args []string) error {
-	ctx := tool.NewContextFromEnv(env)
-	if err := checkSnapshotDir(ctx); err != nil {
+func runSnapshotList(jirix *jiri.X, args []string) error {
+	if err := checkSnapshotDir(jirix); err != nil {
 		return err
 	}
 
@@ -300,12 +298,12 @@
 	failed := false
 	for _, label := range args {
 		labelDir := filepath.Join(snapshotDir, "labels", label)
-		if _, err := ctx.Run().Stat(labelDir); err != nil {
+		if _, err := jirix.Run().Stat(labelDir); err != nil {
 			if !os.IsNotExist(err) {
 				return err
 			}
 			failed = true
-			fmt.Fprintf(env.Stderr, "snapshot label %q not found", label)
+			fmt.Fprintf(jirix.Stderr(), "snapshot label %q not found", label)
 		}
 	}
 	if failed {
@@ -322,9 +320,9 @@
 		if err != nil {
 			return fmt.Errorf("ReadDir(%v) failed: %v", labelDir, err)
 		}
-		fmt.Fprintf(env.Stdout, "snapshots of label %q:\n", label)
+		fmt.Fprintf(jirix.Stdout(), "snapshots of label %q:\n", label)
 		for _, fileInfo := range fileInfoList {
-			fmt.Fprintf(env.Stdout, "  %v\n", fileInfo.Name())
+			fmt.Fprintf(jirix.Stdout(), "  %v\n", fileInfo.Name())
 		}
 	}
 	return nil
diff --git a/snapshot_test.go b/snapshot_test.go
index fe54d1d..bb97ed9 100644
--- a/snapshot_test.go
+++ b/snapshot_test.go
@@ -11,14 +11,14 @@
 	"path/filepath"
 	"testing"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
-	"v.io/x/lib/cmdline"
 )
 
-func createLabelDir(t *testing.T, ctx *tool.Context, snapshotDir, name string, snapshots []string) {
+func createLabelDir(t *testing.T, jirix *jiri.X, snapshotDir, name string, snapshots []string) {
 	labelDir, perm := filepath.Join(snapshotDir, "labels", name), os.FileMode(0700)
-	if err := ctx.Run().MkdirAll(labelDir, perm); err != nil {
+	if err := jirix.Run().MkdirAll(labelDir, perm); err != nil {
 		t.Fatalf("MkdirAll(%v, %v) failed: %v", labelDir, perm, err)
 	}
 	for i, snapshot := range snapshots {
@@ -29,7 +29,7 @@
 		}
 		if i == 0 {
 			symlinkPath := filepath.Join(snapshotDir, name)
-			if err := ctx.Run().Symlink(path, symlinkPath); err != nil {
+			if err := jirix.Run().Symlink(path, symlinkPath); err != nil {
 				t.Fatalf("Symlink(%v, %v) failed: %v", path, symlinkPath, err)
 			}
 		}
@@ -58,15 +58,13 @@
 }
 
 func TestList(t *testing.T) {
-	ctx := tool.NewDefaultContext()
-
 	// Setup a fake JIRI_ROOT.
-	root, err := project.NewFakeJiriRoot(ctx)
+	root, err := project.NewFakeJiriRoot()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	defer func() {
-		if err := root.Cleanup(ctx); err != nil {
+		if err := root.Cleanup(); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}()
@@ -112,17 +110,14 @@
 		// Create the snapshots directory and populate it with the
 		// data specified by the test suite.
 		for _, label := range labels {
-			createLabelDir(t, ctx, test.dir, label.name, label.snapshots)
+			createLabelDir(t, root.X, test.dir, label.name, label.snapshots)
 		}
 
 		// Check that running "jiri snapshot list" with no arguments
 		// returns the expected output.
 		var stdout bytes.Buffer
-		env := &cmdline.Env{Stdout: &stdout}
-		if err != nil {
-			t.Fatalf("%v", err)
-		}
-		if err := runSnapshotList(env, nil); err != nil {
+		root.X.Context = tool.NewContext(tool.ContextOpts{Stdout: &stdout})
+		if err := runSnapshotList(root.X, nil); err != nil {
 			t.Fatalf("%v", err)
 		}
 		got, want := stdout.String(), generateOutput(labels)
@@ -133,7 +128,7 @@
 		// Check that running "jiri snapshot list" with one argument
 		// returns the expected output.
 		stdout.Reset()
-		if err := runSnapshotList(env, []string{"stable"}); err != nil {
+		if err := runSnapshotList(root.X, []string{"stable"}); err != nil {
 			t.Fatalf("%v", err)
 		}
 		got, want = stdout.String(), generateOutput(labels[1:])
@@ -144,7 +139,7 @@
 		// Check that running "jiri snapshot list" with
 		// multiple arguments returns the expected output.
 		stdout.Reset()
-		if err := runSnapshotList(env, []string{"beta", "stable"}); err != nil {
+		if err := runSnapshotList(root.X, []string{"beta", "stable"}); err != nil {
 			t.Fatalf("%v", err)
 		}
 		got, want = stdout.String(), generateOutput(labels)
@@ -154,12 +149,12 @@
 	}
 }
 
-func checkReadme(t *testing.T, ctx *tool.Context, project, message string) {
-	if _, err := ctx.Run().Stat(project); err != nil {
+func checkReadme(t *testing.T, jirix *jiri.X, project, message string) {
+	if _, err := jirix.Run().Stat(project); err != nil {
 		t.Fatalf("%v", err)
 	}
 	readmeFile := filepath.Join(project, "README")
-	data, err := ctx.Run().ReadFile(readmeFile)
+	data, err := jirix.Run().ReadFile(readmeFile)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -176,34 +171,32 @@
 	return "test-remote-project-" + fmt.Sprintf("%d", i+1)
 }
 
-func writeReadme(t *testing.T, ctx *tool.Context, projectDir, message string) {
+func writeReadme(t *testing.T, jirix *jiri.X, projectDir, message string) {
 	path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644)
-	if err := ctx.Run().WriteFile(path, []byte(message), perm); err != nil {
+	if err := jirix.Run().WriteFile(path, []byte(message), perm); err != nil {
 		t.Fatalf("%v", err)
 	}
 	cwd, err := os.Getwd()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	defer ctx.Run().Chdir(cwd)
-	if err := ctx.Run().Chdir(projectDir); err != nil {
+	defer jirix.Run().Chdir(cwd)
+	if err := jirix.Run().Chdir(projectDir); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := ctx.Git().CommitFile(path, "creating README"); err != nil {
+	if err := jirix.Git().CommitFile(path, "creating README"); err != nil {
 		t.Fatalf("%v", err)
 	}
 }
 
 func TestCreate(t *testing.T) {
-	ctx := tool.NewDefaultContext()
-
 	// Setup a fake JIRI_ROOT instance.
-	root, err := project.NewFakeJiriRoot(ctx)
+	root, err := project.NewFakeJiriRoot()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	defer func() {
-		if err := root.Cleanup(ctx); err != nil {
+		if err := root.Cleanup(); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}()
@@ -211,10 +204,10 @@
 	// Setup the initial remote and local projects.
 	numProjects, remoteProjects := 2, []string{}
 	for i := 0; i < numProjects; i++ {
-		if err := root.CreateRemoteProject(ctx, remoteProjectName(i)); err != nil {
+		if err := root.CreateRemoteProject(remoteProjectName(i)); err != nil {
 			t.Fatalf("%v", err)
 		}
-		if err := root.AddProject(ctx, project.Project{
+		if err := root.AddProject(project.Project{
 			Name:   remoteProjectName(i),
 			Path:   localProjectName(i),
 			Remote: root.Projects[remoteProjectName(i)],
@@ -233,24 +226,24 @@
 	// Create initial commits in the remote projects and use
 	// UpdateUniverse() to mirror them locally.
 	for i := 0; i < numProjects; i++ {
-		writeReadme(t, ctx, root.Projects[remoteProjectName(i)], "revision 1")
+		writeReadme(t, root.X, root.Projects[remoteProjectName(i)], "revision 1")
 	}
-	if err := project.UpdateUniverse(ctx, true); err != nil {
+	if err := project.UpdateUniverse(root.X, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Create a local snapshot.
 	var stdout bytes.Buffer
-	env := &cmdline.Env{Stdout: &stdout}
+	root.X.Context = tool.NewContext(tool.ContextOpts{Stdout: &stdout})
 	remoteFlag = false
-	if err := runSnapshotCreate(env, []string{"test-local"}); err != nil {
+	if err := runSnapshotCreate(root.X, []string{"test-local"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Remove the local project repositories.
 	for i, _ := range remoteProjects {
 		localProject := filepath.Join(root.Dir, localProjectName(i))
-		if err := ctx.Run().RemoveAll(localProject); err != nil {
+		if err := root.X.Run().RemoveAll(localProject); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}
@@ -262,28 +255,28 @@
 		t.Fatalf("%v", err)
 	}
 	snapshotFile := filepath.Join(snapshotDir, "test-local")
-	localCtx := ctx.Clone(tool.ContextOpts{
+	localX := root.X.Clone(tool.ContextOpts{
 		Manifest: &snapshotFile,
 	})
-	if err := project.UpdateUniverse(localCtx, true); err != nil {
+	if err := project.UpdateUniverse(localX, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 	for i, _ := range remoteProjects {
 		localProject := filepath.Join(root.Dir, localProjectName(i))
-		checkReadme(t, ctx, localProject, "revision 1")
+		checkReadme(t, root.X, localProject, "revision 1")
 	}
 
 	// Create a remote snapshot.
 	remoteFlag = true
-	root.EnableRemoteManifestPush(ctx)
-	if err := runSnapshotCreate(env, []string{"test-remote"}); err != nil {
+	root.EnableRemoteManifestPush()
+	if err := runSnapshotCreate(root.X, []string{"test-remote"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 
 	// Remove the local project repositories.
 	for i, _ := range remoteProjects {
 		localProject := filepath.Join(root.Dir, localProjectName(i))
-		if err := ctx.Run().RemoveAll(localProject); err != nil {
+		if err := root.X.Run().RemoveAll(localProject); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}
@@ -291,14 +284,14 @@
 	// Check that invoking the UpdateUniverse() with the remote snapshot
 	// restores the local repositories.
 	manifest := "snapshot/test-remote"
-	remoteCtx := ctx.Clone(tool.ContextOpts{
+	remoteX := root.X.Clone(tool.ContextOpts{
 		Manifest: &manifest,
 	})
-	if err := project.UpdateUniverse(remoteCtx, true); err != nil {
+	if err := project.UpdateUniverse(remoteX, true); err != nil {
 		t.Fatalf("%v", err)
 	}
 	for i, _ := range remoteProjects {
 		localProject := filepath.Join(root.Dir, localProjectName(i))
-		checkReadme(t, ctx, localProject, "revision 1")
+		checkReadme(t, root.X, localProject, "revision 1")
 	}
 }
diff --git a/update.go b/update.go
index 5576500..ab1f60d 100644
--- a/update.go
+++ b/update.go
@@ -8,6 +8,7 @@
 	"path/filepath"
 	"time"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/retry"
 	"v.io/jiri/tool"
@@ -28,7 +29,7 @@
 
 // cmdUpdate represents the "jiri update" command.
 var cmdUpdate = &cmdline.Command{
-	Runner: cmdline.RunnerFunc(runUpdate),
+	Runner: jiri.RunnerFunc(runUpdate),
 	Name:   "update",
 	Short:  "Update all jiri tools and projects",
 	Long: `
@@ -42,9 +43,7 @@
 `,
 }
 
-func runUpdate(env *cmdline.Env, _ []string) error {
-	ctx := tool.NewContextFromEnv(env)
-
+func runUpdate(jirix *jiri.X, _ []string) error {
 	// Create a snapshot of the current state of all projects and
 	// write it to the $JIRI_ROOT/.update_history folder.
 	root, err := project.JiriRoot()
@@ -52,14 +51,14 @@
 		return err
 	}
 	snapshotFile := filepath.Join(root, ".update_history", time.Now().Format(time.RFC3339))
-	if err := project.CreateSnapshot(ctx, snapshotFile); err != nil {
+	if err := project.CreateSnapshot(jirix, snapshotFile); err != nil {
 		return err
 	}
 
 	// Update all projects to their latest version.
 	// Attempt <attemptsFlag> times before failing.
 	updateFn := func() error {
-		return project.UpdateUniverse(ctx, gcFlag)
+		return project.UpdateUniverse(jirix, gcFlag)
 	}
-	return retry.Function(ctx, updateFn, retry.AttemptsOpt(attemptsFlag))
+	return retry.Function(jirix.Context, updateFn, retry.AttemptsOpt(attemptsFlag))
 }
diff --git a/util/config.go b/util/config.go
index 84249cd..9268b83 100644
--- a/util/config.go
+++ b/util/config.go
@@ -11,7 +11,7 @@
 	"path/filepath"
 	"sort"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
 	"v.io/x/lib/set"
 )
 
@@ -282,16 +282,16 @@
 
 // LoadConfig returns the configuration stored in the tools
 // configuration file.
-func LoadConfig(ctx *tool.Context) (*Config, error) {
-	configPath, err := ConfigFilePath(ctx)
+func LoadConfig(jirix *jiri.X) (*Config, error) {
+	configPath, err := ConfigFilePath(jirix)
 	if err != nil {
 		return nil, err
 	}
-	return loadConfig(ctx, configPath)
+	return loadConfig(jirix, configPath)
 }
 
-func loadConfig(ctx *tool.Context, path string) (*Config, error) {
-	configBytes, err := ctx.Run().ReadFile(path)
+func loadConfig(jirix *jiri.X, path string) (*Config, error) {
+	configBytes, err := jirix.Run().ReadFile(path)
 	if err != nil {
 		return nil, err
 	}
@@ -340,15 +340,15 @@
 
 // SaveConfig writes the given configuration to the tools
 // configuration file.
-func SaveConfig(ctx *tool.Context, config *Config) error {
-	configPath, err := ConfigFilePath(ctx)
+func SaveConfig(jirix *jiri.X, config *Config) error {
+	configPath, err := ConfigFilePath(jirix)
 	if err != nil {
 		return err
 	}
-	return saveConfig(ctx, config, configPath)
+	return saveConfig(jirix, config, configPath)
 }
 
-func saveConfig(ctx *tool.Context, config *Config, path string) error {
+func saveConfig(jirix *jiri.X, config *Config, path string) error {
 	var data configSchema
 	data.APICheckProjects = set.String.ToSlice(config.apiCheckProjects)
 	sort.Strings(data.APICheckProjects)
@@ -398,10 +398,10 @@
 	if err != nil {
 		return fmt.Errorf("MarshalIndent(%v) failed: %v", data, err)
 	}
-	if err := ctx.Run().MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
+	if err := jirix.Run().MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
 		return err
 	}
-	if err := ctx.Run().WriteFile(path, bytes, os.FileMode(0644)); err != nil {
+	if err := jirix.Run().WriteFile(path, bytes, os.FileMode(0644)); err != nil {
 		return err
 	}
 	return nil
diff --git a/util/config_test.go b/util/config_test.go
index bf99242..87204f5 100644
--- a/util/config_test.go
+++ b/util/config_test.go
@@ -10,7 +10,6 @@
 	"testing"
 
 	"v.io/jiri/project"
-	"v.io/jiri/tool"
 )
 
 var (
@@ -112,14 +111,12 @@
 }
 
 func TestConfigSerialization(t *testing.T) {
-	ctx := tool.NewDefaultContext()
-
-	root, err := project.NewFakeJiriRoot(ctx)
+	root, err := project.NewFakeJiriRoot()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	defer func() {
-		if err := root.Cleanup(ctx); err != nil {
+		if err := root.Cleanup(); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}()
@@ -141,10 +138,10 @@
 		VDLWorkspacesOpt(vdlWorkspaces),
 	)
 
-	if err := SaveConfig(ctx, config); err != nil {
+	if err := SaveConfig(root.X, config); err != nil {
 		t.Fatalf("%v", err)
 	}
-	gotConfig, err := LoadConfig(ctx)
+	gotConfig, err := LoadConfig(root.X)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
diff --git a/util/env_test.go b/util/env_test.go
index 47f47cb..5e2d0c4 100644
--- a/util/env_test.go
+++ b/util/env_test.go
@@ -17,14 +17,14 @@
 // of the JIRI_ROOT environment variable as a path, evaluates any
 // symlinks the path might contain, and returns the result.
 func TestJiriRootSymlink(t *testing.T) {
-	ctx := tool.NewDefaultContext()
+	jirix := tool.NewDefaultContext()
 
 	// Create a temporary directory.
-	tmpDir, err := ctx.Run().TempDir("", "")
+	tmpDir, err := jirix.Run().TempDir("", "")
 	if err != nil {
 		t.Fatalf("TempDir() failed: %v", err)
 	}
-	defer ctx.Run().RemoveAll(tmpDir)
+	defer jirix.Run().RemoveAll(tmpDir)
 
 	// Make sure tmpDir is not a symlink itself.
 	tmpDir, err = filepath.EvalSymlinks(tmpDir)
@@ -34,11 +34,11 @@
 
 	// Create a directory and a symlink to it.
 	root, perm := filepath.Join(tmpDir, "root"), os.FileMode(0700)
-	if err := ctx.Run().MkdirAll(root, perm); err != nil {
+	if err := jirix.Run().MkdirAll(root, perm); err != nil {
 		t.Fatalf("%v", err)
 	}
 	symRoot := filepath.Join(tmpDir, "sym_root")
-	if err := ctx.Run().Symlink(root, symRoot); err != nil {
+	if err := jirix.Run().Symlink(root, symRoot); err != nil {
 		t.Fatalf("%v", err)
 	}
 
diff --git a/util/oncall.go b/util/oncall.go
index 643d968..4cab5a9 100644
--- a/util/oncall.go
+++ b/util/oncall.go
@@ -10,7 +10,7 @@
 	"io/ioutil"
 	"time"
 
-	"v.io/jiri/tool"
+	"v.io/jiri/jiri"
 )
 
 type OncallRotation struct {
@@ -25,8 +25,8 @@
 }
 
 // LoadOncallRotation parses the default oncall schedule file.
-func LoadOncallRotation(ctx *tool.Context) (*OncallRotation, error) {
-	oncallRotationsFile, err := OncallRotationPath(ctx)
+func LoadOncallRotation(jirix *jiri.X) (*OncallRotation, error) {
+	oncallRotationsFile, err := OncallRotationPath(jirix)
 	if err != nil {
 		return nil, err
 	}
@@ -43,9 +43,9 @@
 
 // Oncall finds the oncall shift at the given time from the
 // oncall configuration file by comparing timestamps.
-func Oncall(ctx *tool.Context, targetTime time.Time) (*OncallShift, error) {
+func Oncall(jirix *jiri.X, targetTime time.Time) (*OncallShift, error) {
 	// Parse oncall configuration file.
-	rotation, err := LoadOncallRotation(ctx)
+	rotation, err := LoadOncallRotation(jirix)
 	if err != nil {
 		return nil, err
 	}
diff --git a/util/oncall_test.go b/util/oncall_test.go
index 32536a4..85b0d39 100644
--- a/util/oncall_test.go
+++ b/util/oncall_test.go
@@ -12,11 +12,11 @@
 	"testing"
 	"time"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
-	"v.io/jiri/tool"
 )
 
-func createOncallFile(t *testing.T, ctx *tool.Context) {
+func createOncallFile(t *testing.T, jirix *jiri.X) {
 	content := `<?xml version="1.0" ?>
 <rotation>
   <shift>
@@ -35,13 +35,13 @@
     <startDate>Nov 19, 2014 12:00:00 PM</startDate>
   </shift>
 </rotation>`
-	oncallRotationsFile, err := OncallRotationPath(ctx)
+	oncallRotationsFile, err := OncallRotationPath(jirix)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	dir := filepath.Dir(oncallRotationsFile)
 	dirMode := os.FileMode(0700)
-	if err := ctx.Run().MkdirAll(dir, dirMode); err != nil {
+	if err := jirix.Run().MkdirAll(dir, dirMode); err != nil {
 		t.Fatalf("MkdirAll(%q, %v) failed: %v", dir, dirMode, err)
 	}
 	fileMode := os.FileMode(0644)
@@ -51,13 +51,12 @@
 }
 
 func TestOncall(t *testing.T) {
-	ctx := tool.NewDefaultContext()
-	root, err := project.NewFakeJiriRoot(ctx)
+	root, err := project.NewFakeJiriRoot()
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
 	defer func() {
-		if err := root.Cleanup(ctx); err != nil {
+		if err := root.Cleanup(); err != nil {
 			t.Fatalf("%v", err)
 		}
 	}()
@@ -71,7 +70,7 @@
 	defer os.Setenv("JIRI_ROOT", oldRoot)
 
 	// Create a oncall.v1.xml file.
-	createOncallFile(t, ctx)
+	createOncallFile(t, root.X)
 	type testCase struct {
 		targetTime    time.Time
 		expectedShift *OncallShift
@@ -107,7 +106,7 @@
 		},
 	}
 	for _, test := range testCases {
-		got, err := Oncall(ctx, test.targetTime)
+		got, err := Oncall(root.X, test.targetTime)
 		if err != nil {
 			t.Fatalf("want no errors, got: %v", err)
 		}
diff --git a/util/paths.go b/util/paths.go
index e0f2893..a68d10d 100644
--- a/util/paths.go
+++ b/util/paths.go
@@ -10,14 +10,15 @@
 	"path/filepath"
 	"runtime"
 
+	"v.io/jiri/jiri"
 	"v.io/jiri/project"
 	"v.io/jiri/tool"
 	"v.io/x/lib/host"
 )
 
 // ConfigFilePath returns the path to the tools configuration file.
-func ConfigFilePath(ctx *tool.Context) (string, error) {
-	dataDir, err := project.DataDirPath(ctx, tool.Name)
+func ConfigFilePath(jirix *jiri.X) (string, error) {
+	dataDir, err := project.DataDirPath(jirix, tool.Name)
 	if err != nil {
 		return "", err
 	}
@@ -25,8 +26,8 @@
 }
 
 // OncallRotationPath returns the path to the oncall rotation file.
-func OncallRotationPath(ctx *tool.Context) (string, error) {
-	dataDir, err := project.DataDirPath(ctx, tool.Name)
+func OncallRotationPath(jirix *jiri.X) (string, error) {
+	dataDir, err := project.DataDirPath(jirix, tool.Name)
 	if err != nil {
 		return "", err
 	}