diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2017-06-19 12:24:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-19 12:24:28 +0200 |
commit | 8b17cf05989402408ee1b66953a12ebfdf700aac (patch) | |
tree | 512411e012ef8ff2cf1a51e8785d9edb3b4c2332 | |
parent | 3ae5d4de35e76f2f573b550d93bb2aed8137f1cb (diff) | |
parent | ada10c2978a6c7dea8109f2d92dd092f57f9de84 (diff) | |
download | go-git-8b17cf05989402408ee1b66953a12ebfdf700aac.tar.gz |
Merge pull request #436 from mcuadros/symlink
worktree: symlink support
-rw-r--r-- | utils/merkletrie/filesystem/node.go | 41 | ||||
-rw-r--r-- | utils/merkletrie/filesystem/node_test.go | 19 | ||||
-rw-r--r-- | worktree.go | 49 | ||||
-rw-r--r-- | worktree_commit.go | 32 | ||||
-rw-r--r-- | worktree_status.go | 33 | ||||
-rw-r--r-- | worktree_test.go | 51 |
6 files changed, 198 insertions, 27 deletions
diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go index 5461bd3..a8f3b86 100644 --- a/utils/merkletrie/filesystem/node.go +++ b/utils/merkletrie/filesystem/node.go @@ -136,25 +136,54 @@ func (n *node) calculateHash(path string, file os.FileInfo) ([]byte, error) { return make([]byte, 24), nil } - f, err := n.fs.Open(path) + var hash plumbing.Hash + var err error + if file.Mode()&os.ModeSymlink != 0 { + hash, err = n.doCalculateHashForSymlink(path, file) + } else { + hash, err = n.doCalculateHashForRegular(path, file) + } + + if err != nil { + return nil, err + } + + mode, err := filemode.NewFromOSFileMode(file.Mode()) if err != nil { return nil, err } + return append(hash[:], mode.Bytes()...), nil +} + +func (n *node) doCalculateHashForRegular(path string, file os.FileInfo) (plumbing.Hash, error) { + f, err := n.fs.Open(path) + if err != nil { + return plumbing.ZeroHash, err + } + defer f.Close() h := plumbing.NewHasher(plumbing.BlobObject, file.Size()) if _, err := io.Copy(h, f); err != nil { - return nil, err + return plumbing.ZeroHash, err } - mode, err := filemode.NewFromOSFileMode(file.Mode()) + return h.Sum(), nil +} + +func (n *node) doCalculateHashForSymlink(path string, file os.FileInfo) (plumbing.Hash, error) { + target, err := n.fs.Readlink(path) if err != nil { - return nil, err + return plumbing.ZeroHash, err } - hash := h.Sum() - return append(hash[:], mode.Bytes()...), nil + h := plumbing.NewHasher(plumbing.BlobObject, file.Size()) + if _, err := h.Write([]byte(target)); err != nil { + return plumbing.ZeroHash, err + } + + return h.Sum(), nil } func (n *node) String() string { diff --git a/utils/merkletrie/filesystem/node_test.go b/utils/merkletrie/filesystem/node_test.go index a383716..bf1178a 100644 --- a/utils/merkletrie/filesystem/node_test.go +++ b/utils/merkletrie/filesystem/node_test.go @@ -25,11 +25,13 @@ func (s *NoderSuite) TestDiff(c *C) { WriteFile(fsA, "foo", []byte("foo"), 0644) WriteFile(fsA, "qux/bar", []byte("foo"), 0644) WriteFile(fsA, "qux/qux", []byte("foo"), 0644) + fsA.Symlink("foo", "bar") fsB := memfs.New() WriteFile(fsB, "foo", []byte("foo"), 0644) WriteFile(fsB, "qux/bar", []byte("foo"), 0644) WriteFile(fsB, "qux/qux", []byte("foo"), 0644) + fsB.Symlink("foo", "bar") ch, err := merkletrie.DiffTree( NewRootNode(fsA, nil), @@ -41,6 +43,23 @@ func (s *NoderSuite) TestDiff(c *C) { c.Assert(ch, HasLen, 0) } +func (s *NoderSuite) TestDiffChangeLink(c *C) { + fsA := memfs.New() + fsA.Symlink("qux", "foo") + + fsB := memfs.New() + fsB.Symlink("bar", "foo") + + ch, err := merkletrie.DiffTree( + NewRootNode(fsA, nil), + NewRootNode(fsB, nil), + IsEquals, + ) + + c.Assert(err, IsNil) + c.Assert(ch, HasLen, 1) +} + func (s *NoderSuite) TestDiffChangeContent(c *C) { fsA := memfs.New() WriteFile(fsA, "foo", []byte("foo"), 0644) diff --git a/worktree.go b/worktree.go index aa30b83..0e63cf1 100644 --- a/worktree.go +++ b/worktree.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" "io" - "io/ioutil" + stdioutil "io/ioutil" "os" "path/filepath" @@ -15,6 +15,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/format/index" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/utils/merkletrie" + "gopkg.in/src-d/go-git/utils/ioutil" "gopkg.in/src-d/go-billy.v3" ) @@ -327,29 +328,49 @@ func (w *Worktree) checkoutChangeRegularFile(name string, return nil } -func (w *Worktree) checkoutFile(f *object.File) error { - from, err := f.Reader() +func (w *Worktree) checkoutFile(f *object.File) (err error) { + mode, err := f.Mode.ToOSFileMode() if err != nil { - return err + return } - defer from.Close() - mode, err := f.Mode.ToOSFileMode() + if mode&os.ModeSymlink != 0 { + return w.checkoutFileSymlink(f) + } + + from, err := f.Reader() if err != nil { - return err + return } + defer ioutil.CheckClose(from, &err) + to, err := w.fs.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) if err != nil { - return err + return } - defer to.Close() - if _, err := io.Copy(to, from); err != nil { - return err + defer ioutil.CheckClose(to, &err) + + _, err = io.Copy(to, from) + return +} + +func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { + from, err := f.Reader() + if err != nil { + return + } + + defer ioutil.CheckClose(from, &err) + + bytes, err := stdioutil.ReadAll(from) + if err != nil { + return } - return err + err = w.fs.Symlink(string(bytes), f.Name) + return } func (w *Worktree) addIndexFromTreeEntry(name string, f *object.TreeEntry, idx *index.Index) error { @@ -363,7 +384,7 @@ func (w *Worktree) addIndexFromTreeEntry(name string, f *object.TreeEntry, idx * } func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, idx *index.Index) error { - fi, err := w.fs.Stat(name) + fi, err := w.fs.Lstat(name) if err != nil { return err } @@ -477,7 +498,7 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { return nil, err } - input, err := ioutil.ReadAll(f) + input, err := stdioutil.ReadAll(f) if err != nil { return nil, err } diff --git a/worktree_commit.go b/worktree_commit.go index 5307b41..fc63d16 100644 --- a/worktree_commit.go +++ b/worktree_commit.go @@ -2,6 +2,7 @@ package git import ( "io" + "os" "path/filepath" "strings" @@ -186,11 +187,15 @@ func (h *commitIndexHelper) copyIndexEntryToStorage(e *index.Entry) error { } func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error) { - fi, err := h.fs.Stat(e.Name) + fi, err := h.fs.Lstat(e.Name) if err != nil { return err } + if fi.Mode()&os.ModeSymlink != 0 { + return h.doCopyIndexEntryFromSymlinkToStorage(e, fi) + } + obj := h.s.NewEncodedObject() obj.SetType(plumbing.BlobObject) obj.SetSize(fi.Size()) @@ -217,6 +222,31 @@ func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error return err } +func (h *commitIndexHelper) doCopyIndexEntryFromSymlinkToStorage(e *index.Entry, fi os.FileInfo) error { + obj := h.s.NewEncodedObject() + obj.SetType(plumbing.BlobObject) + obj.SetSize(fi.Size()) + + writer, err := obj.Writer() + if err != nil { + return err + } + + defer ioutil.CheckClose(writer, &err) + + target, err := h.fs.Readlink(e.Name) + if err != nil { + return err + } + + if _, err := writer.Write([]byte(target)); err != nil { + return err + } + + _, err = h.s.SetEncodedObject(obj) + return err +} + func (h *commitIndexHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) { for i, e := range t.Entries { if e.Mode != filemode.Dir && !e.Hash.IsZero() { diff --git a/worktree_status.go b/worktree_status.go index 7cc4b0f..648ea89 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -244,11 +244,15 @@ func (w *Worktree) Add(path string) (plumbing.Hash, error) { } func (w *Worktree) calculateBlobHash(filename string) (hash plumbing.Hash, err error) { - fi, err := w.fs.Stat(filename) + fi, err := w.fs.Lstat(filename) if err != nil { return plumbing.ZeroHash, err } + if fi.Mode()&os.ModeSymlink != 0 { + return w.calculateBlobHashFromSymlink(filename) + } + f, err := w.fs.Open(filename) if err != nil { return plumbing.ZeroHash, err @@ -265,6 +269,21 @@ func (w *Worktree) calculateBlobHash(filename string) (hash plumbing.Hash, err e return } +func (w *Worktree) calculateBlobHashFromSymlink(link string) (plumbing.Hash, error) { + target, err := w.fs.Readlink(link) + if err != nil { + return plumbing.ZeroHash, err + } + + h := plumbing.NewHasher(plumbing.BlobObject, int64(len(target))) + _, err = h.Write([]byte(target)) + if err != nil { + return plumbing.ZeroHash, err + } + + return h.Sum(), nil +} + func (w *Worktree) addOrUpdateFileToIndex(filename string, h plumbing.Hash) error { idx, err := w.r.Storer.Index() if err != nil { @@ -297,7 +316,7 @@ func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbin } func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error { - info, err := w.fs.Stat(filename) + info, err := w.fs.Lstat(filename) if err != nil { return err } @@ -305,12 +324,14 @@ func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbi e.Hash = h e.ModifiedAt = info.ModTime() e.Mode, err = filemode.NewFromOSFileMode(info.Mode()) - e.Size = uint32(info.Size()) - if err != nil { return err } + if e.Mode.IsRegular() { + e.Size = uint32(info.Size()) + } + fillSystemInfo(e, info.Sys()) return nil } @@ -351,11 +372,11 @@ func (w *Worktree) deleteFromFilesystem(path string) error { // Move moves or rename a file in the worktree and the index, directories are // not supported. func (w *Worktree) Move(from, to string) (plumbing.Hash, error) { - if _, err := w.fs.Stat(from); err != nil { + if _, err := w.fs.Lstat(from); err != nil { return plumbing.ZeroHash, err } - if _, err := w.fs.Stat(to); err == nil { + if _, err := w.fs.Lstat(to); err == nil { return plumbing.ZeroHash, ErrDestinationExists } diff --git a/worktree_test.go b/worktree_test.go index 9efa231..06f82ca 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -56,6 +56,35 @@ func (s *WorktreeSuite) TestCheckout(c *C) { c.Assert(idx.Entries, HasLen, 9) } +func (s *WorktreeSuite) TestCheckoutSymlink(c *C) { + dir, err := ioutil.TempDir("", "checkout") + defer os.RemoveAll(dir) + + r, err := PlainInit(dir, false) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + w.fs.Symlink("not-exists", "bar") + w.Add("bar") + w.Commit("foo", &CommitOptions{Author: defaultSignature()}) + + r.Storer.SetIndex(&index.Index{Version: 2}) + w.fs = osfs.New(filepath.Join(dir, "worktree-empty")) + + err = w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) + + target, err := w.fs.Readlink("bar") + c.Assert(target, Equals, "not-exists") + c.Assert(err, IsNil) +} + func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) { url := "https://github.com/git-fixtures/submodule.git" w := &Worktree{ @@ -641,6 +670,28 @@ func (s *WorktreeSuite) TestAddUnmodified(c *C) { c.Assert(err, IsNil) } +func (s *WorktreeSuite) TestAddSymlink(c *C) { + dir, err := ioutil.TempDir("", "checkout") + defer os.RemoveAll(dir) + + r, err := PlainInit(dir, false) + c.Assert(err, IsNil) + err = util.WriteFile(r.wt, "foo", []byte("qux"), 0644) + c.Assert(err, IsNil) + err = r.wt.Symlink("foo", "bar") + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + h, err := w.Add("foo") + c.Assert(err, IsNil) + c.Assert(h, Not(Equals), plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c")) + + h, err = w.Add("bar") + c.Assert(err, IsNil) + c.Assert(h, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c")) +} + func (s *WorktreeSuite) TestRemove(c *C) { fs := memfs.New() w := &Worktree{ |