Set timestamps for changed files in gitfs.
Change-Id: Iab14bd9c6a072e128299d4d76325afd2d9b732d9
diff --git a/cmd/gitfs-populate/main.go b/cmd/gitfs-populate/main.go
index b5dd5f9..33c00b0 100644
--- a/cmd/gitfs-populate/main.go
+++ b/cmd/gitfs-populate/main.go
@@ -16,11 +16,15 @@
import (
"flag"
+ "fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
+ "sort"
"strings"
+ "syscall"
+ "time"
)
type repoTree struct {
@@ -174,8 +178,11 @@
return nil
}
-// clearLinks removes all symlinks to the RO tree.
-func clearLinks(dir, mount string) error {
+// clearLinks removes all symlinks to the RO tree. It returns the workspace name that was linked before.
+func clearLinks(mount, dir string) (string, error) {
+ mount = filepath.Clean(mount)
+
+ var prefix string
var dirs []string
if err := filepath.Walk(dir, func(n string, fi os.FileInfo, err error) error {
if fi.Mode()&os.ModeSymlink != 0 {
@@ -184,6 +191,7 @@
return err
}
if strings.HasPrefix(target, mount) {
+ prefix = target
if err := os.Remove(n); err != nil {
return err
}
@@ -194,18 +202,80 @@
}
return nil
}); err != nil {
- return err
+ return "", err
}
for _, d := range dirs {
// Ignore error: dir may still contain entries.
os.Remove(d)
}
- return nil
+
+ prefix = strings.TrimPrefix(prefix, mount+"/")
+ if i := strings.Index(prefix, "/"); i != -1 {
+ prefix = prefix[:i]
+ }
+ return prefix, nil
+}
+
+func getSHA1s(dir string) (map[string]string, error) {
+ attr := "user.gitsha1"
+
+ shamap := map[string]string{}
+
+ data := make([]byte, 1024)
+
+ if err := filepath.Walk(dir, func(n string, fi os.FileInfo, err error) error {
+ if fi.Mode()&os.ModeType != 0 {
+ return nil
+ }
+ if filepath.Base(n) == ".gitid" {
+ return nil
+ }
+
+ sz, err := syscall.Getxattr(n, attr, data)
+ if err != nil {
+ return fmt.Errorf("Getxattr(%s, %s): %v", n, attr, err)
+ }
+ rel, err := filepath.Rel(dir, n)
+ if err != nil {
+ return err
+ }
+ shamap[rel] = string(data[:sz])
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ return shamap, nil
+}
+
+// Returns the filenames (as relative paths) in newDir that have
+// changed relative to the files in oldDir.
+func changedFiles(oldDir, newDir string) ([]string, error) {
+ // TODO(hanwen): could be parallel.
+ oldSHA1s, err := getSHA1s(oldDir)
+ if err != nil {
+ return nil, err
+ }
+ newSHA1s, err := getSHA1s(newDir)
+ if err != nil {
+ return nil, err
+ }
+
+ var changed []string
+ for k, v := range newSHA1s {
+ old, ok := oldSHA1s[k]
+ if !ok || old != v {
+ changed = append(changed, k)
+ }
+ }
+ sort.Strings(changed)
+ return changed, nil
}
// populateCheckout updates a RW dir with new symlinks to the given RO dir.
func populateCheckout(ro, rw string) error {
- if err := clearLinks(ro, rw); err != nil {
+ wsName, err := clearLinks(filepath.Dir(ro), rw)
+ if err != nil {
log.Fatal(err)
}
@@ -219,7 +289,26 @@
return err
}
- return createLinks(roTree, rwTree, ro, rw)
+ if err := createLinks(roTree, rwTree, ro, rw); err != nil {
+ return err
+ }
+
+ // TODO(hanwen): can be done in parallel to the other processes.
+ oldRoot := filepath.Join(filepath.Dir(ro), wsName)
+ changed, err := changedFiles(oldRoot, ro)
+ if err != nil {
+ return fmt.Errorf("changedFiles: %v", err)
+ }
+
+ // TODO(hanwen): parallel?
+ now := time.Now()
+ for _, n := range changed {
+ if err := os.Chtimes(filepath.Join(ro, n), now, now); err != nil {
+ return err
+ }
+ }
+
+ return nil
}
func main() {
diff --git a/cmd/gitfs-populate/main_test.go b/cmd/gitfs-populate/main_test.go
index 436fdb0..1437b74 100644
--- a/cmd/gitfs-populate/main_test.go
+++ b/cmd/gitfs-populate/main_test.go
@@ -1,13 +1,18 @@
package main
import (
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
+ "syscall"
"testing"
)
+const attr = "user.gitsha1"
+const checksum = "3f75526aa8f01eea5d76cee10722195dc73676de"
+
func createFSTree(names []string) (string, error) {
dir, err := ioutil.TempDir("", "")
if err != nil {
@@ -22,6 +27,9 @@
if err := ioutil.WriteFile(p, []byte{42}, 0644); err != nil {
return dir, err
}
+ if err := syscall.Setxattr(p, attr, []byte(checksum), 0); err != nil {
+ return dir, fmt.Errorf("Setxattr: %v", err)
+ }
}
return dir, nil
}
@@ -99,6 +107,10 @@
t.Fatal("createFSTree:", err)
}
+ if err := os.Symlink(filepath.Join(dir, "ro/obsolete"), filepath.Join(dir, "rw/obsolete")); err != nil {
+ t.Errorf("Symlink: %v", err)
+ }
+
if err := populateCheckout(filepath.Join(dir, "ro"), filepath.Join(dir, "rw")); err != nil {
t.Errorf("populateCheckout: %v", err)
}
@@ -130,4 +142,33 @@
}
}
+ if fi, err := os.Lstat(filepath.Join(dir, "rw/obsolete")); err == nil {
+ t.Fatalf("obsolete symlink still there: %v", fi)
+ }
+}
+
+func TestChangedFiles(t *testing.T) {
+ dir, err := createFSTree([]string{
+ "r1/a",
+ "r1/b",
+ "r2/a",
+ "r2/b",
+ "r2/c",
+ })
+ if err != nil {
+ t.Fatalf("createFSTree: %v", err)
+ }
+
+ ck2 := "3f75526aa8f01eea5d76cee10722195dc73676df"
+ if err := syscall.Setxattr(filepath.Join(dir, "r2/b"), attr, []byte(ck2), 0); err != nil {
+ t.Fatalf("Setxattr: %v", err)
+ }
+
+ got, err := changedFiles(filepath.Join(dir, "r1"), filepath.Join(dir, "r2"))
+ if err != nil {
+ t.Fatalf("changedFiles: %v", err)
+ }
+ if want := []string{"b", "c"}; !reflect.DeepEqual(want, got) {
+ t.Errorf("got %v, want %v", got, want)
+ }
}