Merge "v.io/jiri/runutil: move code out of jiri/util to better locations..."
diff --git a/cl.go b/cl.go
index f5dc422..9460338 100644
--- a/cl.go
+++ b/cl.go
@@ -16,6 +16,7 @@
 	"v.io/jiri/gitutil"
 	"v.io/jiri/jiri"
 	"v.io/jiri/project"
+	"v.io/jiri/runutil"
 	"v.io/jiri/tool"
 	"v.io/x/lib/cmdline"
 )
@@ -97,10 +98,10 @@
 	if err != nil {
 		return nil, err
 	}
-	data, err := jirix.Run().ReadFile(file)
+	data, err := jirix.NewSeq().ReadFile(file)
 	var branches []string
 	if err != nil {
-		if !os.IsNotExist(err) {
+		if !runutil.IsNotExist(err) {
 			return nil, err
 		}
 		if branch != remoteBranchFlag {
@@ -163,9 +164,10 @@
 	if err := jirix.Git().FetchRefspec("origin", remoteBranchFlag); err != nil {
 		return err
 	}
+	s := jirix.NewSeq()
 	for _, branch := range branches {
 		cleanupFn := func() error { return cleanupBranch(jirix, branch) }
-		if err := jirix.Run().Function(cleanupFn, "Cleaning up branch %q", branch); err != nil {
+		if err := s.Call(cleanupFn, "Cleaning up branch %q", branch).Done(); err != nil {
 			return err
 		}
 		if branch == originalBranch {
@@ -209,12 +211,11 @@
 	if err != nil {
 		return err
 	}
-	metadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir)
-	if err := jirix.Run().RemoveAll(filepath.Join(metadataDir, branch)); err != nil {
-		return err
-	}
+	s := jirix.NewSeq()
 	// Remove the branch from all dependency paths.
-	fileInfos, err := jirix.Run().ReadDir(metadataDir)
+	metadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir)
+	fileInfos, err := s.RemoveAll(filepath.Join(metadataDir, branch)).
+		ReadDir(metadataDir)
 	if err != nil {
 		return err
 	}
@@ -226,9 +227,9 @@
 		if err != nil {
 			return err
 		}
-		data, err := jirix.Run().ReadFile(file)
+		data, err := s.ReadFile(file)
 		if err != nil {
-			if !os.IsNotExist(err) {
+			if !runutil.IsNotExist(err) {
 				return err
 			}
 			continue
@@ -237,7 +238,7 @@
 		for i, tmpBranch := range branches {
 			if branch == tmpBranch {
 				data := []byte(strings.Join(append(branches[:i], branches[i+1:]...), "\n"))
-				if err := jirix.Run().WriteFile(file, data, os.FileMode(0644)); err != nil {
+				if err := s.WriteFile(file, data, os.FileMode(0644)).Done(); err != nil {
 					return err
 				}
 				break
@@ -406,8 +407,8 @@
 		if err != nil {
 			return err
 		}
-		if _, err := jirix.Run().Stat(file); err != nil {
-			if !os.IsNotExist(err) {
+		if _, err := jirix.NewSeq().Stat(file); err != nil {
+			if !runutil.IsNotExist(err) {
 				return err
 			}
 			return fmt.Errorf(`Failed to export the branch %q to Gerrit because its ancestor %q has not been exported to Gerrit yet.
@@ -483,9 +484,9 @@
 	if err != nil {
 		return false, err
 	}
-	bytes, err := review.jirix.Run().ReadFile(file)
+	bytes, err := review.jirix.NewSeq().ReadFile(file)
 	if err != nil {
-		if os.IsNotExist(err) {
+		if runutil.IsNotExist(err) {
 			return true, nil
 		}
 		return false, err
@@ -653,7 +654,7 @@
 			if err != nil {
 				return err
 			}
-			message, err := review.jirix.Run().ReadFile(file)
+			message, err := review.jirix.NewSeq().ReadFile(file)
 			if err != nil {
 				return err
 			}
@@ -763,12 +764,13 @@
 	if err != nil {
 		return fmt.Errorf("Getwd() failed: %v", err)
 	}
-	defer collect.Error(func() error { return review.jirix.Run().Chdir(wd) }, &e)
+	defer collect.Error(func() error { return review.jirix.NewSeq().Chdir(wd).Done() }, &e)
 	topLevel, err := review.jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
-	if err := review.jirix.Run().Chdir(topLevel); err != nil {
+	s := review.jirix.NewSeq()
+	if err := s.Chdir(topLevel).Done(); err != nil {
 		return err
 	}
 	defer collect.Error(func() error { return review.cleanup(stashed) }, &e)
@@ -779,9 +781,9 @@
 	message := messageFlag
 	if message == "" {
 		// Message was not passed in flag.  Attempt to read it from file.
-		data, err := review.jirix.Run().ReadFile(file)
+		data, err := s.ReadFile(file)
 		if err != nil {
-			if !os.IsNotExist(err) {
+			if !runutil.IsNotExist(err) {
 				return err
 			}
 		} else {
@@ -835,7 +837,7 @@
 	if err != nil {
 		return "", err
 	}
-	bytes, err := review.jirix.Run().ReadFile(file)
+	bytes, err := review.jirix.NewSeq().ReadFile(file)
 	if err != nil {
 		return "", err
 	}
@@ -868,13 +870,14 @@
 	if err != nil {
 		return err
 	}
+	s := review.jirix.NewSeq()
 	// For the initial commit where the commit message file doesn't exist,
 	// add/remove labels after users finish editing the commit message.
 	//
 	// This behavior is consistent with how Change-ID is added for the
 	// initial commit so we don't confuse users.
-	if _, err := review.jirix.Run().Stat(file); err != nil {
-		if os.IsNotExist(err) {
+	if _, err := s.Stat(file); err != nil {
+		if runutil.IsNotExist(err) {
 			newMessage = review.processLabels(newMessage)
 			if err := review.jirix.Git().CommitAmendWithMessage(newMessage); err != nil {
 				return err
@@ -888,10 +891,8 @@
 		return err
 	}
 	newMetadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir, review.CLOpts.Branch)
-	if err := review.jirix.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
-		return err
-	}
-	if err := review.jirix.Run().WriteFile(file, []byte(newMessage), 0644); err != nil {
+	if err := s.MkdirAll(newMetadataDir, os.FileMode(0755)).
+		WriteFile(file, []byte(newMessage), 0644).Done(); err != nil {
 		return err
 	}
 	return nil
@@ -946,6 +947,7 @@
 		}
 	}()
 
+	s := jirix.NewSeq()
 	// Record the dependent CLs for the new branch. The dependent CLs
 	// are recorded in a <dependencyPathFileName> file as a
 	// newline-separated list of branch names.
@@ -955,14 +957,14 @@
 	}
 	branches = append(branches, originalBranch)
 	newMetadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir, newBranch)
-	if err := jirix.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
+	if err := s.MkdirAll(newMetadataDir, os.FileMode(0755)).Done(); err != nil {
 		return err
 	}
 	file, err := getDependencyPathFileName(jirix, newBranch)
 	if err != nil {
 		return err
 	}
-	if err := jirix.Run().WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)); err != nil {
+	if err := s.WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)).Done(); err != nil {
 		return err
 	}
 
@@ -1020,15 +1022,16 @@
 		if forceOriginalBranch {
 			jirix.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
 		}
-		jirix.Run().Chdir(originalWd)
+		jirix.NewSeq().Chdir(originalWd)
 	}()
 
+	s := jirix.NewSeq()
 	// Switch to an existing directory in master so we can run commands.
 	topLevel, err := jirix.Git().TopLevel()
 	if err != nil {
 		return err
 	}
-	if err := jirix.Run().Chdir(topLevel); err != nil {
+	if err := s.Chdir(topLevel).Done(); err != nil {
 		return err
 	}
 
diff --git a/cl_test.go b/cl_test.go
index 19f3674..327767b 100644
--- a/cl_test.go
+++ b/cl_test.go
@@ -17,6 +17,7 @@
 	"v.io/jiri/gitutil"
 	"v.io/jiri/jiri"
 	"v.io/jiri/jiritest"
+	"v.io/jiri/runutil"
 )
 
 // assertCommitCount asserts that the commit count between two
@@ -34,7 +35,7 @@
 // assertFileContent asserts that the content of the given file
 // matches the expected content.
 func assertFileContent(t *testing.T, jirix *jiri.X, file, want string) {
-	got, err := jirix.Run().ReadFile(file)
+	got, err := jirix.NewSeq().ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -45,9 +46,10 @@
 
 // assertFilesExist asserts that the files exist.
 func assertFilesExist(t *testing.T, jirix *jiri.X, files []string) {
+	s := jirix.NewSeq()
 	for _, file := range files {
-		if _, err := jirix.Run().Stat(file); err != nil {
-			if os.IsNotExist(err) {
+		if _, err := s.Stat(file); err != nil {
+			if runutil.IsNotExist(err) {
 				t.Fatalf("expected file %v to exist but it did not", file)
 			}
 			t.Fatalf("%v", err)
@@ -57,8 +59,9 @@
 
 // assertFilesDoNotExist asserts that the files do not exist.
 func assertFilesDoNotExist(t *testing.T, jirix *jiri.X, files []string) {
+	s := jirix.NewSeq()
 	for _, file := range files {
-		if _, err := jirix.Run().Stat(file); err != nil && !os.IsNotExist(err) {
+		if _, err := s.Stat(file); err != nil && !runutil.IsNotExist(err) {
 			t.Fatalf("%v", err)
 		} else if err == nil {
 			t.Fatalf("expected file %v to not exist but it did", file)
@@ -114,7 +117,8 @@
 
 // commitFile commits a file with the specified content into a branch
 func commitFile(t *testing.T, jirix *jiri.X, filename string, content string) {
-	if err := jirix.Run().WriteFile(filename, []byte(content), 0644); err != nil {
+	s := jirix.NewSeq()
+	if err := s.WriteFile(filename, []byte(content), 0644).Done(); err != nil {
 		t.Fatalf("%v", err)
 	}
 	commitMessage := "Commit " + filename
@@ -134,7 +138,8 @@
 
 // createRepo creates a new repository with the given prefix.
 func createRepo(t *testing.T, jirix *jiri.X, prefix string) string {
-	repoPath, err := jirix.Run().TempDir(jirix.Root, "repo-"+prefix)
+	s := jirix.NewSeq()
+	repoPath, err := s.TempDir(jirix.Root, "repo-"+prefix)
 	if err != nil {
 		t.Fatalf("TempDir() failed: %v", err)
 	}
@@ -144,7 +149,7 @@
 	if err := jirix.Git().Init(repoPath); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := jirix.Run().MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)); err != nil {
+	if err := s.MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)).Done(); err != nil {
 		t.Fatalf("%v", err)
 	}
 	return repoPath
@@ -159,14 +164,14 @@
 // installCommitMsgHook links the gerrit commit-msg hook into a different repo.
 func installCommitMsgHook(t *testing.T, jirix *jiri.X, repoPath string) {
 	hookLocation := path.Join(repoPath, ".git/hooks/commit-msg")
-	if err := jirix.Run().WriteFile(hookLocation, []byte(commitMsgHook), 0755); err != nil {
+	if err := jirix.NewSeq().WriteFile(hookLocation, []byte(commitMsgHook), 0755).Done(); err != nil {
 		t.Fatalf("WriteFile(%v) failed: %v", hookLocation, err)
 	}
 }
 
 // chdir changes the runtime working directory and traps any errors.
 func chdir(t *testing.T, jirix *jiri.X, path string) {
-	if err := jirix.Run().Chdir(path); err != nil {
+	if err := jirix.NewSeq().Chdir(path).Done(); err != nil {
 		_, file, line, _ := runtime.Caller(1)
 		t.Fatalf("%s: %d: Chdir(%v) failed: %v", file, line, path, err)
 	}
@@ -489,6 +494,7 @@
 func TestLabelsInCommitMessage(t *testing.T) {
 	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
 	defer cleanup()
+	s := fake.X.NewSeq()
 	branch := "my-branch"
 	if err := fake.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
@@ -520,7 +526,7 @@
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	bytes, err := fake.X.Run().ReadFile(file)
+	bytes, err := s.ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -553,7 +559,7 @@
 	}
 	expectedRef = gerrit.Reference(review.CLOpts)
 	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	bytes, err = fake.X.Run().ReadFile(file)
+	bytes, err = s.ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -581,7 +587,7 @@
 	}
 	expectedRef = gerrit.Reference(review.CLOpts)
 	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
-	bytes, err = fake.X.Run().ReadFile(file)
+	bytes, err = s.ReadFile(file)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -598,6 +604,7 @@
 func TestDirtyBranch(t *testing.T) {
 	fake, _, _, gerritPath, cleanup := setupTest(t, true)
 	defer cleanup()
+	s := fake.X.NewSeq()
 	branch := "my-branch"
 	if err := fake.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
@@ -606,7 +613,7 @@
 	commitFiles(t, fake.X, files)
 	assertStashSize(t, fake.X, 0)
 	stashedFile, stashedFileContent := "stashed-file", "stashed-file content"
-	if err := fake.X.Run().WriteFile(stashedFile, []byte(stashedFileContent), 0644); err != nil {
+	if err := s.WriteFile(stashedFile, []byte(stashedFileContent), 0644).Done(); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", stashedFile, stashedFileContent, err)
 	}
 	if err := fake.X.Git().Add(stashedFile); err != nil {
@@ -617,18 +624,18 @@
 	}
 	assertStashSize(t, fake.X, 1)
 	modifiedFile, modifiedFileContent := "file1", "modified-file content"
-	if err := fake.X.Run().WriteFile(modifiedFile, []byte(modifiedFileContent), 0644); err != nil {
+	if err := s.WriteFile(modifiedFile, []byte(modifiedFileContent), 0644).Done(); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", modifiedFile, modifiedFileContent, err)
 	}
 	stagedFile, stagedFileContent := "file2", "staged-file content"
-	if err := fake.X.Run().WriteFile(stagedFile, []byte(stagedFileContent), 0644); err != nil {
+	if err := s.WriteFile(stagedFile, []byte(stagedFileContent), 0644).Done(); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", stagedFile, stagedFileContent, err)
 	}
 	if err := fake.X.Git().Add(stagedFile); err != nil {
 		t.Fatalf("%v", err)
 	}
 	untrackedFile, untrackedFileContent := "file3", "untracked-file content"
-	if err := fake.X.Run().WriteFile(untrackedFile, []byte(untrackedFileContent), 0644); err != nil {
+	if err := s.WriteFile(untrackedFile, []byte(untrackedFileContent), 0644).Done(); err != nil {
 		t.Fatalf("WriteFile(%v, %v) failed: %v", untrackedFile, untrackedFileContent, err)
 	}
 	review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath})
@@ -664,13 +671,14 @@
 func TestRunInSubdirectory(t *testing.T) {
 	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
 	defer cleanup()
+	s := fake.X.NewSeq()
 	branch := "my-branch"
 	if err := fake.X.Git().CreateAndCheckoutBranch(branch); err != nil {
 		t.Fatalf("%v", err)
 	}
 	subdir := "sub/directory"
 	subdirPerms := os.FileMode(0744)
-	if err := fake.X.Run().MkdirAll(subdir, subdirPerms); err != nil {
+	if err := s.MkdirAll(subdir, subdirPerms).Done(); err != nil {
 		t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err)
 	}
 	files := []string{path.Join(subdir, "file1")}
diff --git a/project/project.go b/project/project.go
index 2892bc9..2364c52 100644
--- a/project/project.go
+++ b/project/project.go
@@ -1186,8 +1186,9 @@
 		return err
 	}
 
+	updates := newFsUpdates()
 	for _, op := range ops {
-		if err := op.Test(jirix); err != nil {
+		if err := op.Test(jirix, updates); err != nil {
 			return err
 		}
 	}
@@ -1279,6 +1280,31 @@
 	return nil
 }
 
+// fsUpdates is used to track filesystem updates made by operations.
+// TODO(nlacasse): Currently we only use fsUpdates to track deletions so that
+// jiri can delete and create a project in the same directory in one update.
+// There are lots of other cases that should be covered though, like detecting
+// when two projects would be created in the same directory.
+type fsUpdates struct {
+	deletedDirs map[string]bool
+}
+
+func newFsUpdates() *fsUpdates {
+	return &fsUpdates{
+		deletedDirs: map[string]bool{},
+	}
+}
+
+func (u *fsUpdates) deleteDir(dir string) {
+	dir = filepath.Clean(dir)
+	u.deletedDirs[dir] = true
+}
+
+func (u *fsUpdates) isDeleted(dir string) bool {
+	_, ok := u.deletedDirs[filepath.Clean(dir)]
+	return ok
+}
+
 type operation interface {
 	// Project identifies the project this operation pertains to.
 	Project() Project
@@ -1287,7 +1313,7 @@
 	// String returns a string representation of the operation.
 	String() string
 	// Test checks whether the operation would fail.
-	Test(jirix *jiri.X) error
+	Test(jirix *jiri.X, updates *fsUpdates) error
 }
 
 // commonOperation represents a project operation.
@@ -1401,13 +1427,13 @@
 	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(jirix *jiri.X) error {
+func (op createOperation) Test(jirix *jiri.X, updates *fsUpdates) error {
 	// Check the local file system.
 	if _, err := jirix.Run().Stat(op.destination); err != nil {
 		if !os.IsNotExist(err) {
 			return err
 		}
-	} else {
+	} else if !updates.isDeleted(op.destination) {
 		return fmt.Errorf("cannot create %q as it already exists", op.destination)
 	}
 	return nil
@@ -1476,13 +1502,14 @@
 	return fmt.Sprintf("delete project %q from %q", op.project.Name, op.source)
 }
 
-func (op deleteOperation) Test(jirix *jiri.X) error {
+func (op deleteOperation) Test(jirix *jiri.X, updates *fsUpdates) 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)
 		}
 		return err
 	}
+	updates.deleteDir(op.source)
 	return nil
 }
 
@@ -1515,7 +1542,7 @@
 	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(jirix *jiri.X) error {
+func (op moveOperation) Test(jirix *jiri.X, updates *fsUpdates) 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)
@@ -1529,6 +1556,7 @@
 	} else {
 		return fmt.Errorf("cannot move %q to %q as the destination already exists", op.source, op.destination)
 	}
+	updates.deleteDir(op.source)
 	return nil
 }
 
@@ -1554,7 +1582,7 @@
 	return fmt.Sprintf("advance project %q located in %q to %q", op.project.Name, op.source, fmtRevision(op.project.Revision))
 }
 
-func (op updateOperation) Test(jirix *jiri.X) error {
+func (op updateOperation) Test(jirix *jiri.X, _ *fsUpdates) error {
 	return nil
 }
 
@@ -1575,7 +1603,7 @@
 	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(jirix *jiri.X) error {
+func (op nullOperation) Test(jirix *jiri.X, _ *fsUpdates) error {
 	return nil
 }
 
diff --git a/project/project_test.go b/project/project_test.go
index 7c248a9..9230999 100644
--- a/project/project_test.go
+++ b/project/project_test.go
@@ -154,6 +154,20 @@
 	}
 }
 
+func createProject(t *testing.T, jirix *jiri.X, manifestDir, name, remote, path string) {
+	manifestFile := filepath.Join(manifestDir, "v2", "default")
+	data, err := ioutil.ReadFile(manifestFile)
+	if err != nil {
+		t.Fatalf("ReadFile(%v) failed: %v", manifestFile, err)
+	}
+	manifest := project.Manifest{}
+	if err := xml.Unmarshal(data, &manifest); err != nil {
+		t.Fatalf("Unmarshal() failed: %v\n%v", err, data)
+	}
+	manifest.Projects = append(manifest.Projects, project.Project{Name: name, Remote: remote, Path: path})
+	commitManifest(t, jirix, &manifest, manifestDir)
+}
+
 func deleteProject(t *testing.T, jirix *jiri.X, manifestDir, name string) {
 	manifestFile := filepath.Join(manifestDir, "v2", "default")
 	data, err := ioutil.ReadFile(manifestFile)
@@ -428,7 +442,7 @@
 	writeEmptyMetadata(t, jirix, localManifest)
 	remoteManifest := setupNewProject(t, jirix, remoteDir, "test-remote-manifest", false)
 	addRemote(t, jirix, localManifest, "origin", remoteManifest)
-	numProjects, remoteProjects := 5, []string{}
+	numProjects, remoteProjects := 6, []string{}
 	for i := 0; i < numProjects; i++ {
 		remoteProject := setupNewProject(t, jirix, remoteDir, remoteProjectName(i), true)
 		remoteProjects = append(remoteProjects, remoteProject)
@@ -544,21 +558,30 @@
 		checkDeleteFn(i, "revision 3")
 	}
 
-	// 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, 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)
+	// Delete a project and create a new one with a different name but the same
+	// path.  Check that UpdateUniverse() does not fail.
+	path := filepath.Join(localDir, localProjectName(4))
+	deleteProject(t, jirix, remoteManifest, remoteProjects[4])
+	createProject(t, jirix, remoteManifest, "new.project", remoteProjects[4], path)
 	if err := project.UpdateUniverse(jirix, true); err != nil {
 		t.Fatalf("%v", err)
 	}
-	localProject := filepath.Join(localDir, localProjectName(4))
+
+	// 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, jirix, remoteProjects[5], "master commit")
+	createAndCheckoutBranch(t, jirix, remoteProjects[5], "non_master")
+	writeReadme(t, jirix, remoteProjects[5], "non master commit")
+	remoteBranchRevision := currentRevision(t, jirix, remoteProjects[5])
+	setRevisionForProject(t, jirix, remoteManifest, remoteProjects[5], remoteBranchRevision)
+	if err := project.UpdateUniverse(jirix, true); err != nil {
+		t.Fatalf("%v", err)
+	}
+	localProject := filepath.Join(localDir, localProjectName(5))
 	localBranchRevision := currentRevision(t, jirix, localProject)
 	if localBranchRevision != remoteBranchRevision {
-		t.Fatalf("project 4 is at revision %v, expected %v\n", localBranchRevision, remoteBranchRevision)
+		t.Fatalf("project 5 is at revision %v, expected %v\n", localBranchRevision, remoteBranchRevision)
 	}
 	// Reset back to origin/master so the next update without a "revision" works.
 	resetToOriginMaster(t, jirix, localProject)
@@ -571,7 +594,7 @@
 	}
 
 	checkRebaseFn := func(i int, revision string) {
-		if i == 4 {
+		if i == 5 {
 			checkReadme(t, jirix, localProject, "non master commit")
 		} else {
 			checkDeleteFn(i, revision)
diff --git a/snapshot.go b/snapshot.go
index 21c0032..16da995 100644
--- a/snapshot.go
+++ b/snapshot.go
@@ -17,6 +17,7 @@
 	"v.io/jiri/gitutil"
 	"v.io/jiri/jiri"
 	"v.io/jiri/project"
+	"v.io/jiri/runutil"
 	"v.io/x/lib/cmdline"
 )
 
@@ -133,7 +134,7 @@
 	switch _, err := jirix.Run().Stat(dir); {
 	case err == nil:
 		return dir, nil
-	case !os.IsNotExist(err):
+	case !runutil.IsNotExist(err):
 		return "", err
 	case remoteFlag:
 		if err := jirix.Run().MkdirAll(dir, 0755); err != nil {
@@ -274,7 +275,7 @@
 	for _, label := range args {
 		labelDir := filepath.Join(snapshotDir, "labels", label)
 		if _, err := jirix.Run().Stat(labelDir); err != nil {
-			if !os.IsNotExist(err) {
+			if !runutil.IsNotExist(err) {
 				return err
 			}
 			failed = true