Delete project when create operation fails.

Don't leave behind stale state

Change-Id: I592e55dc89190d05e8ed6f029bfc541bdb7e8ae7
diff --git a/project/operations.go b/project/operations.go
index 1ec2486..53c3504 100644
--- a/project/operations.go
+++ b/project/operations.go
@@ -84,40 +84,8 @@
 	return "create"
 }
 
-func (op createOperation) Run(jirix *jiri.X) (e error) {
-	path, perm := filepath.Dir(op.destination), os.FileMode(0755)
-
-	// Check the local file system.
-	if _, err := os.Stat(op.destination); err != nil {
-		if !os.IsNotExist(err) {
-			return fmtError(err)
-		}
-	} else {
-		if isEmpty, err := isEmpty(op.destination); err != nil {
-			return err
-		} else if !isEmpty {
-			return fmt.Errorf("cannot create %q as it already exists and is not empty", op.destination)
-		} else {
-			if err := os.RemoveAll(op.destination); err != nil {
-				return fmt.Errorf("Not able to delete %q", op.destination)
-			}
-		}
-	}
-	// Create a temporary directory for the initial setup of the
-	// project to prevent an untimely termination from leaving the
-	// root directory in an inconsistent state.
-	if err := os.MkdirAll(path, perm); err != nil {
-		return fmtError(err)
-	}
-
-	cache, err := op.project.CacheDirPath(jirix)
-	if err != nil {
-		return err
-	}
-	if !isPathDir(cache) {
-		cache = ""
-	}
-
+func (op createOperation) checkoutProject(jirix *jiri.X, cache string) error {
+	var err error
 	if jirix.Shared && cache != "" {
 		err = clone(jirix, cache, op.destination, gitutil.SharedOpt(true),
 			gitutil.NoCheckoutOpt(true), gitutil.DepthOpt(op.project.HistoryDepth))
@@ -131,15 +99,10 @@
 			gitutil.NoCheckoutOpt(true), gitutil.DepthOpt(op.project.HistoryDepth))
 	}
 	if err != nil {
-		if err2 := os.RemoveAll(op.destination); err2 != nil {
-			return fmt.Errorf("Not able to remove %q after clone failed: %s, original error: %s", op.destination, err2, err)
-		}
 		return err
 	}
+
 	if err := os.Chmod(op.destination, os.FileMode(0755)); err != nil {
-		if err2 := os.RemoveAll(op.destination); err2 != nil {
-			return fmt.Errorf("Not able to remove %q after chmod failed: %s, original error: %s", op.destination, err2, fmtError(err))
-		}
 		return fmtError(err)
 	}
 
@@ -165,6 +128,47 @@
 	return nil
 }
 
+func (op createOperation) Run(jirix *jiri.X) (e error) {
+	path, perm := filepath.Dir(op.destination), os.FileMode(0755)
+
+	// Check the local file system.
+	if _, err := os.Stat(op.destination); err != nil {
+		if !os.IsNotExist(err) {
+			return fmtError(err)
+		}
+	} else {
+		if isEmpty, err := isEmpty(op.destination); err != nil {
+			return err
+		} else if !isEmpty {
+			return fmt.Errorf("cannot create %q as it already exists and is not empty", op.destination)
+		} else {
+			if err := os.RemoveAll(op.destination); err != nil {
+				return fmt.Errorf("Not able to delete %q", op.destination)
+			}
+		}
+	}
+
+	if err := os.MkdirAll(path, perm); err != nil {
+		return fmtError(err)
+	}
+
+	cache, err := op.project.CacheDirPath(jirix)
+	if err != nil {
+		return err
+	}
+	if !isPathDir(cache) {
+		cache = ""
+	}
+
+	if err := op.checkoutProject(jirix, cache); err != nil {
+		if err := os.RemoveAll(op.destination); err != nil {
+			jirix.Logger.Warningf("Not able to remove %q after create failed: %s", op.destination, err)
+		}
+		return err
+	}
+	return nil
+}
+
 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))
 }
diff --git a/project/project_test.go b/project/project_test.go
index ef60bcd..bbd22ec 100644
--- a/project/project_test.go
+++ b/project/project_test.go
@@ -1042,6 +1042,38 @@
 	}
 }
 
+// TestUpdateUniverseWithBadRevision checks that UpdateUniverse
+// will not leave bad state behind.
+func TestUpdateUniverseWithBadRevision(t *testing.T) {
+	localProjects, fake, cleanup := setupUniverse(t)
+	defer cleanup()
+
+	m, err := fake.ReadRemoteManifest()
+	if err != nil {
+		t.Fatal(err)
+	}
+	projects := []project.Project{}
+	for _, p := range m.Projects {
+		if p.Name == localProjects[1].Name {
+			p.Revision = "badrev"
+		}
+		projects = append(projects, p)
+	}
+	m.Projects = projects
+	if err := fake.WriteRemoteManifest(m); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := fake.UpdateUniverse(false); err == nil {
+		t.Fatal("should have thrown error")
+	}
+
+	if err := dirExists(localProjects[1].Path); err == nil {
+		t.Fatalf("expected project %q at path %q not to exist but it did", localProjects[1].Name, localProjects[1].Path)
+	}
+
+}
+
 func commitChanges(t *testing.T, jirix *jiri.X, dir string) {
 	scm := gitutil.New(jirix, gitutil.UserNameOpt("John Doe"), gitutil.UserEmailOpt("john.doe@example.com"), gitutil.RootDirOpt(dir))
 	if err := scm.AddUpdatedFiles(); err != nil {