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)