Merge "jiri: Allow deletion+creation of projects with the same path during update."
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)