Fix rename when files are on different partition

Change-Id: I865bd90846d617b6619a2b5796b1767e2de8baba
diff --git a/osutil/rename.go b/osutil/rename.go
new file mode 100644
index 0000000..ccb6822
--- /dev/null
+++ b/osutil/rename.go
@@ -0,0 +1,27 @@
+package osutil
+
+import (
+	"os"
+	"os/exec"
+	"syscall"
+)
+
+func Rename(src, dst string) error {
+	if err := os.Rename(src, dst); err != nil {
+		// Check if the rename operation failed
+		// because the source and destination are
+		// located on different mount points.
+		linkErr, ok := err.(*os.LinkError)
+		if !ok {
+			return err
+		}
+		errno, ok := linkErr.Err.(syscall.Errno)
+		if !ok || errno != syscall.EXDEV {
+			return err
+		}
+		// Fall back to a non-atomic rename.
+		cmd := exec.Command("mv", src, dst)
+		return cmd.Run()
+	}
+	return nil
+}
diff --git a/project/project.go b/project/project.go
index e668c12..57b0b6d 100644
--- a/project/project.go
+++ b/project/project.go
@@ -27,6 +27,7 @@
 	"fuchsia.googlesource.com/jiri/gitutil"
 	"fuchsia.googlesource.com/jiri/googlesource"
 	"fuchsia.googlesource.com/jiri/log"
+	"fuchsia.googlesource.com/jiri/osutil"
 	"fuchsia.googlesource.com/jiri/runutil"
 )
 
@@ -172,7 +173,7 @@
 	if err := ioutil.WriteFile(tmp, data, 0644); err != nil {
 		return fmtError(err)
 	}
-	return fmtError(os.Rename(tmp, filename))
+	return fmtError(osutil.Rename(tmp, filename))
 }
 
 type LocalConfig struct {
@@ -2553,7 +2554,7 @@
 	if err := os.Chmod(tmpDir, os.FileMode(0755)); err != nil {
 		return fmtError(err)
 	}
-	if err := os.Rename(tmpDir, op.destination); err != nil {
+	if err := osutil.Rename(tmpDir, op.destination); err != nil {
 		return fmtError(err)
 	}
 	if err := checkoutHeadRevision(jirix, op.project, false); err != nil {
@@ -2700,7 +2701,7 @@
 		if err := os.MkdirAll(path, perm); err != nil {
 			return fmtError(err)
 		}
-		if err := os.Rename(op.source, op.destination); err != nil {
+		if err := osutil.Rename(op.source, op.destination); err != nil {
 			return fmtError(err)
 		}
 	}
diff --git a/update.go b/update.go
index 47f3710..e73314c 100644
--- a/update.go
+++ b/update.go
@@ -212,16 +212,16 @@
 		return err
 	}
 
-	err = os.Rename(path, oldfile.Name())
+	err = osutil.Rename(path, oldfile.Name())
 	if err != nil {
 		return err
 	}
 
 	// Replace the existing version.
-	err = os.Rename(newfile.Name(), path)
+	err = osutil.Rename(newfile.Name(), path)
 	if err != nil {
 		// Try to rollback the change in case of error.
-		rerr := os.Rename(oldfile.Name(), path)
+		rerr := osutil.Rename(oldfile.Name(), path)
 		if rerr != nil {
 			return rerr
 		}