Merge pull request #874 from smola/patchcontext

plumbing: add context to allow cancel on diff/patch computing
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index 52b621c..dc12f23 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -469,7 +469,7 @@
 	// File mode is retrieved from a constant defined in the target specific
 	// files (dotgit_rewrite_packed_refs_*). Some modes are not available
 	// in all filesystems.
-	openFlags := openAndLockPackedRefsMode
+	openFlags := d.openAndLockPackedRefsMode()
 	if doCreate {
 		openFlags |= os.O_CREATE
 	}
diff --git a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go b/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go
new file mode 100644
index 0000000..7f1c02c
--- /dev/null
+++ b/storage/filesystem/dotgit/dotgit_rewrite_packed_refs.go
@@ -0,0 +1,81 @@
+package dotgit
+
+import (
+	"io"
+	"os"
+	"runtime"
+
+	"gopkg.in/src-d/go-billy.v4"
+	"gopkg.in/src-d/go-git.v4/utils/ioutil"
+)
+
+func (d *DotGit) openAndLockPackedRefsMode() int {
+	if billy.CapabilityCheck(d.fs, billy.ReadAndWriteCapability) {
+		return os.O_RDWR
+	}
+
+	return os.O_RDONLY
+}
+
+func (d *DotGit) rewritePackedRefsWhileLocked(
+	tmp billy.File, pr billy.File) error {
+	// Try plain rename. If we aren't using the bare Windows filesystem as the
+	// storage layer, we might be able to get away with a rename over a locked
+	// file.
+	err := d.fs.Rename(tmp.Name(), pr.Name())
+	if err == nil {
+		return nil
+	}
+
+	// If we are in a filesystem that does not support rename (e.g. sivafs)
+	// a full copy is done.
+	if err == billy.ErrNotSupported {
+		return d.copyNewFile(tmp, pr)
+	}
+
+	if runtime.GOOS != "windows" {
+		return err
+	}
+
+	// Otherwise, Windows doesn't let us rename over a locked file, so
+	// we have to do a straight copy.  Unfortunately this could result
+	// in a partially-written file if the process fails before the
+	// copy completes.
+	return d.copyToExistingFile(tmp, pr)
+}
+
+func (d *DotGit) copyToExistingFile(tmp, pr billy.File) error {
+	_, err := pr.Seek(0, io.SeekStart)
+	if err != nil {
+		return err
+	}
+	err = pr.Truncate(0)
+	if err != nil {
+		return err
+	}
+	_, err = tmp.Seek(0, io.SeekStart)
+	if err != nil {
+		return err
+	}
+	_, err = io.Copy(pr, tmp)
+
+	return err
+}
+
+func (d *DotGit) copyNewFile(tmp billy.File, pr billy.File) (err error) {
+	prWrite, err := d.fs.Create(pr.Name())
+	if err != nil {
+		return err
+	}
+
+	defer ioutil.CheckClose(prWrite, &err)
+
+	_, err = tmp.Seek(0, io.SeekStart)
+	if err != nil {
+		return err
+	}
+
+	_, err = io.Copy(prWrite, tmp)
+
+	return err
+}
diff --git a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_nix.go b/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_nix.go
deleted file mode 100644
index c760793..0000000
--- a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_nix.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// +build !windows,!norwfs
-
-package dotgit
-
-import (
-	"os"
-
-	"gopkg.in/src-d/go-billy.v4"
-)
-
-const openAndLockPackedRefsMode = os.O_RDWR
-
-func (d *DotGit) rewritePackedRefsWhileLocked(
-	tmp billy.File, pr billy.File) error {
-	// On non-Windows platforms, we can have atomic rename.
-	return d.fs.Rename(tmp.Name(), pr.Name())
-}
diff --git a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_norwfs.go b/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_norwfs.go
deleted file mode 100644
index 6e43b42..0000000
--- a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_norwfs.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// +build norwfs
-
-package dotgit
-
-import (
-	"io"
-	"os"
-
-	"gopkg.in/src-d/go-billy.v4"
-)
-
-const openAndLockPackedRefsMode = os.O_RDONLY
-
-// Instead of renaming that can not be supported in simpler filesystems
-// a full copy is done.
-func (d *DotGit) rewritePackedRefsWhileLocked(
-	tmp billy.File, pr billy.File) error {
-
-	prWrite, err := d.fs.Create(pr.Name())
-	if err != nil {
-		return err
-	}
-
-	defer prWrite.Close()
-
-	_, err = tmp.Seek(0, io.SeekStart)
-	if err != nil {
-		return err
-	}
-
-	_, err = io.Copy(prWrite, tmp)
-
-	return err
-}
diff --git a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_windows.go b/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_windows.go
deleted file mode 100644
index 897d2c9..0000000
--- a/storage/filesystem/dotgit/dotgit_rewrite_packed_refs_windows.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// +build windows,!norwfs
-
-package dotgit
-
-import (
-	"io"
-	"os"
-
-	"gopkg.in/src-d/go-billy.v4"
-)
-
-const openAndLockPackedRefsMode = os.O_RDWR
-
-func (d *DotGit) rewritePackedRefsWhileLocked(
-	tmp billy.File, pr billy.File) error {
-	// If we aren't using the bare Windows filesystem as the storage
-	// layer, we might be able to get away with a rename over a locked
-	// file.
-	err := d.fs.Rename(tmp.Name(), pr.Name())
-	if err == nil {
-		return nil
-	}
-
-	// Otherwise, Windows doesn't let us rename over a locked file, so
-	// we have to do a straight copy.  Unfortunately this could result
-	// in a partially-written file if the process fails before the
-	// copy completes.
-	_, err = pr.Seek(0, io.SeekStart)
-	if err != nil {
-		return err
-	}
-	err = pr.Truncate(0)
-	if err != nil {
-		return err
-	}
-	_, err = tmp.Seek(0, io.SeekStart)
-	if err != nil {
-		return err
-	}
-	_, err = io.Copy(pr, tmp)
-	return err
-}
diff --git a/utils/diff/diff.go b/utils/diff/diff.go
index b840ad6..f49ae55 100644
--- a/utils/diff/diff.go
+++ b/utils/diff/diff.go
@@ -16,8 +16,8 @@
 // string into the dst string.
 func Do(src, dst string) (diffs []diffmatchpatch.Diff) {
 	dmp := diffmatchpatch.New()
-	wSrc, wDst, warray := dmp.DiffLinesToChars(src, dst)
-	diffs = dmp.DiffMain(wSrc, wDst, false)
+	wSrc, wDst, warray := dmp.DiffLinesToRunes(src, dst)
+	diffs = dmp.DiffMainRunes(wSrc, wDst, false)
 	diffs = dmp.DiffCharsToLines(diffs, warray)
 	return diffs
 }