diff options
-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 |
4 files changed, 144 insertions, 21 deletions
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 eb4a83a..ba0509a 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -212,11 +212,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 @@ -233,6 +237,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 { @@ -265,7 +284,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 } @@ -273,12 +292,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 } @@ -319,11 +340,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 4f15005..c7d3b78 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{ @@ -591,6 +620,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{ |