Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 8b17cf0

Browse files
authored
Merge pull request #436 from mcuadros/symlink
worktree: symlink support
2 parents 3ae5d4d + ada10c2 commit 8b17cf0

File tree

6 files changed

+198
-27
lines changed

6 files changed

+198
-27
lines changed

utils/merkletrie/filesystem/node.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,25 +136,54 @@ func (n *node) calculateHash(path string, file os.FileInfo) ([]byte, error) {
136136
return make([]byte, 24), nil
137137
}
138138

139-
f, err := n.fs.Open(path)
139+
var hash plumbing.Hash
140+
var err error
141+
if file.Mode()&os.ModeSymlink != 0 {
142+
hash, err = n.doCalculateHashForSymlink(path, file)
143+
} else {
144+
hash, err = n.doCalculateHashForRegular(path, file)
145+
}
146+
147+
if err != nil {
148+
return nil, err
149+
}
150+
151+
mode, err := filemode.NewFromOSFileMode(file.Mode())
140152
if err != nil {
141153
return nil, err
142154
}
143155

156+
return append(hash[:], mode.Bytes()...), nil
157+
}
158+
159+
func (n *node) doCalculateHashForRegular(path string, file os.FileInfo) (plumbing.Hash, error) {
160+
f, err := n.fs.Open(path)
161+
if err != nil {
162+
return plumbing.ZeroHash, err
163+
}
164+
144165
defer f.Close()
145166

146167
h := plumbing.NewHasher(plumbing.BlobObject, file.Size())
147168
if _, err := io.Copy(h, f); err != nil {
148-
return nil, err
169+
return plumbing.ZeroHash, err
149170
}
150171

151-
mode, err := filemode.NewFromOSFileMode(file.Mode())
172+
return h.Sum(), nil
173+
}
174+
175+
func (n *node) doCalculateHashForSymlink(path string, file os.FileInfo) (plumbing.Hash, error) {
176+
target, err := n.fs.Readlink(path)
152177
if err != nil {
153-
return nil, err
178+
return plumbing.ZeroHash, err
154179
}
155180

156-
hash := h.Sum()
157-
return append(hash[:], mode.Bytes()...), nil
181+
h := plumbing.NewHasher(plumbing.BlobObject, file.Size())
182+
if _, err := h.Write([]byte(target)); err != nil {
183+
return plumbing.ZeroHash, err
184+
}
185+
186+
return h.Sum(), nil
158187
}
159188

160189
func (n *node) String() string {

utils/merkletrie/filesystem/node_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ func (s *NoderSuite) TestDiff(c *C) {
2525
WriteFile(fsA, "foo", []byte("foo"), 0644)
2626
WriteFile(fsA, "qux/bar", []byte("foo"), 0644)
2727
WriteFile(fsA, "qux/qux", []byte("foo"), 0644)
28+
fsA.Symlink("foo", "bar")
2829

2930
fsB := memfs.New()
3031
WriteFile(fsB, "foo", []byte("foo"), 0644)
3132
WriteFile(fsB, "qux/bar", []byte("foo"), 0644)
3233
WriteFile(fsB, "qux/qux", []byte("foo"), 0644)
34+
fsB.Symlink("foo", "bar")
3335

3436
ch, err := merkletrie.DiffTree(
3537
NewRootNode(fsA, nil),
@@ -41,6 +43,23 @@ func (s *NoderSuite) TestDiff(c *C) {
4143
c.Assert(ch, HasLen, 0)
4244
}
4345

46+
func (s *NoderSuite) TestDiffChangeLink(c *C) {
47+
fsA := memfs.New()
48+
fsA.Symlink("qux", "foo")
49+
50+
fsB := memfs.New()
51+
fsB.Symlink("bar", "foo")
52+
53+
ch, err := merkletrie.DiffTree(
54+
NewRootNode(fsA, nil),
55+
NewRootNode(fsB, nil),
56+
IsEquals,
57+
)
58+
59+
c.Assert(err, IsNil)
60+
c.Assert(ch, HasLen, 1)
61+
}
62+
4463
func (s *NoderSuite) TestDiffChangeContent(c *C) {
4564
fsA := memfs.New()
4665
WriteFile(fsA, "foo", []byte("foo"), 0644)

worktree.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"io"
7-
"io/ioutil"
7+
stdioutil "io/ioutil"
88
"os"
99
"path/filepath"
1010

@@ -15,6 +15,7 @@ import (
1515
"gopkg.in/src-d/go-git.v4/plumbing/format/index"
1616
"gopkg.in/src-d/go-git.v4/plumbing/object"
1717
"gopkg.in/src-d/go-git.v4/utils/merkletrie"
18+
"gopkg.in/src-d/go-git/utils/ioutil"
1819

1920
"gopkg.in/src-d/go-billy.v3"
2021
)
@@ -327,29 +328,49 @@ func (w *Worktree) checkoutChangeRegularFile(name string,
327328
return nil
328329
}
329330

330-
func (w *Worktree) checkoutFile(f *object.File) error {
331-
from, err := f.Reader()
331+
func (w *Worktree) checkoutFile(f *object.File) (err error) {
332+
mode, err := f.Mode.ToOSFileMode()
332333
if err != nil {
333-
return err
334+
return
334335
}
335-
defer from.Close()
336336

337-
mode, err := f.Mode.ToOSFileMode()
337+
if mode&os.ModeSymlink != 0 {
338+
return w.checkoutFileSymlink(f)
339+
}
340+
341+
from, err := f.Reader()
338342
if err != nil {
339-
return err
343+
return
340344
}
341345

346+
defer ioutil.CheckClose(from, &err)
347+
342348
to, err := w.fs.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm())
343349
if err != nil {
344-
return err
350+
return
345351
}
346-
defer to.Close()
347352

348-
if _, err := io.Copy(to, from); err != nil {
349-
return err
353+
defer ioutil.CheckClose(to, &err)
354+
355+
_, err = io.Copy(to, from)
356+
return
357+
}
358+
359+
func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) {
360+
from, err := f.Reader()
361+
if err != nil {
362+
return
363+
}
364+
365+
defer ioutil.CheckClose(from, &err)
366+
367+
bytes, err := stdioutil.ReadAll(from)
368+
if err != nil {
369+
return
350370
}
351371

352-
return err
372+
err = w.fs.Symlink(string(bytes), f.Name)
373+
return
353374
}
354375

355376
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 *
363384
}
364385

365386
func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, idx *index.Index) error {
366-
fi, err := w.fs.Stat(name)
387+
fi, err := w.fs.Lstat(name)
367388
if err != nil {
368389
return err
369390
}
@@ -477,7 +498,7 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) {
477498
return nil, err
478499
}
479500

480-
input, err := ioutil.ReadAll(f)
501+
input, err := stdioutil.ReadAll(f)
481502
if err != nil {
482503
return nil, err
483504
}

worktree_commit.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package git
22

33
import (
44
"io"
5+
"os"
56
"path/filepath"
67
"strings"
78

@@ -186,11 +187,15 @@ func (h *commitIndexHelper) copyIndexEntryToStorage(e *index.Entry) error {
186187
}
187188

188189
func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error) {
189-
fi, err := h.fs.Stat(e.Name)
190+
fi, err := h.fs.Lstat(e.Name)
190191
if err != nil {
191192
return err
192193
}
193194

195+
if fi.Mode()&os.ModeSymlink != 0 {
196+
return h.doCopyIndexEntryFromSymlinkToStorage(e, fi)
197+
}
198+
194199
obj := h.s.NewEncodedObject()
195200
obj.SetType(plumbing.BlobObject)
196201
obj.SetSize(fi.Size())
@@ -217,6 +222,31 @@ func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error
217222
return err
218223
}
219224

225+
func (h *commitIndexHelper) doCopyIndexEntryFromSymlinkToStorage(e *index.Entry, fi os.FileInfo) error {
226+
obj := h.s.NewEncodedObject()
227+
obj.SetType(plumbing.BlobObject)
228+
obj.SetSize(fi.Size())
229+
230+
writer, err := obj.Writer()
231+
if err != nil {
232+
return err
233+
}
234+
235+
defer ioutil.CheckClose(writer, &err)
236+
237+
target, err := h.fs.Readlink(e.Name)
238+
if err != nil {
239+
return err
240+
}
241+
242+
if _, err := writer.Write([]byte(target)); err != nil {
243+
return err
244+
}
245+
246+
_, err = h.s.SetEncodedObject(obj)
247+
return err
248+
}
249+
220250
func (h *commitIndexHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) {
221251
for i, e := range t.Entries {
222252
if e.Mode != filemode.Dir && !e.Hash.IsZero() {

worktree_status.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,15 @@ func (w *Worktree) Add(path string) (plumbing.Hash, error) {
244244
}
245245

246246
func (w *Worktree) calculateBlobHash(filename string) (hash plumbing.Hash, err error) {
247-
fi, err := w.fs.Stat(filename)
247+
fi, err := w.fs.Lstat(filename)
248248
if err != nil {
249249
return plumbing.ZeroHash, err
250250
}
251251

252+
if fi.Mode()&os.ModeSymlink != 0 {
253+
return w.calculateBlobHashFromSymlink(filename)
254+
}
255+
252256
f, err := w.fs.Open(filename)
253257
if err != nil {
254258
return plumbing.ZeroHash, err
@@ -265,6 +269,21 @@ func (w *Worktree) calculateBlobHash(filename string) (hash plumbing.Hash, err e
265269
return
266270
}
267271

272+
func (w *Worktree) calculateBlobHashFromSymlink(link string) (plumbing.Hash, error) {
273+
target, err := w.fs.Readlink(link)
274+
if err != nil {
275+
return plumbing.ZeroHash, err
276+
}
277+
278+
h := plumbing.NewHasher(plumbing.BlobObject, int64(len(target)))
279+
_, err = h.Write([]byte(target))
280+
if err != nil {
281+
return plumbing.ZeroHash, err
282+
}
283+
284+
return h.Sum(), nil
285+
}
286+
268287
func (w *Worktree) addOrUpdateFileToIndex(filename string, h plumbing.Hash) error {
269288
idx, err := w.r.Storer.Index()
270289
if err != nil {
@@ -297,20 +316,22 @@ func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbin
297316
}
298317

299318
func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error {
300-
info, err := w.fs.Stat(filename)
319+
info, err := w.fs.Lstat(filename)
301320
if err != nil {
302321
return err
303322
}
304323

305324
e.Hash = h
306325
e.ModifiedAt = info.ModTime()
307326
e.Mode, err = filemode.NewFromOSFileMode(info.Mode())
308-
e.Size = uint32(info.Size())
309-
310327
if err != nil {
311328
return err
312329
}
313330

331+
if e.Mode.IsRegular() {
332+
e.Size = uint32(info.Size())
333+
}
334+
314335
fillSystemInfo(e, info.Sys())
315336
return nil
316337
}
@@ -351,11 +372,11 @@ func (w *Worktree) deleteFromFilesystem(path string) error {
351372
// Move moves or rename a file in the worktree and the index, directories are
352373
// not supported.
353374
func (w *Worktree) Move(from, to string) (plumbing.Hash, error) {
354-
if _, err := w.fs.Stat(from); err != nil {
375+
if _, err := w.fs.Lstat(from); err != nil {
355376
return plumbing.ZeroHash, err
356377
}
357378

358-
if _, err := w.fs.Stat(to); err == nil {
379+
if _, err := w.fs.Lstat(to); err == nil {
359380
return plumbing.ZeroHash, ErrDestinationExists
360381
}
361382

worktree_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ func (s *WorktreeSuite) TestCheckout(c *C) {
5656
c.Assert(idx.Entries, HasLen, 9)
5757
}
5858

59+
func (s *WorktreeSuite) TestCheckoutSymlink(c *C) {
60+
dir, err := ioutil.TempDir("", "checkout")
61+
defer os.RemoveAll(dir)
62+
63+
r, err := PlainInit(dir, false)
64+
c.Assert(err, IsNil)
65+
66+
w, err := r.Worktree()
67+
c.Assert(err, IsNil)
68+
69+
w.fs.Symlink("not-exists", "bar")
70+
w.Add("bar")
71+
w.Commit("foo", &CommitOptions{Author: defaultSignature()})
72+
73+
r.Storer.SetIndex(&index.Index{Version: 2})
74+
w.fs = osfs.New(filepath.Join(dir, "worktree-empty"))
75+
76+
err = w.Checkout(&CheckoutOptions{})
77+
c.Assert(err, IsNil)
78+
79+
status, err := w.Status()
80+
c.Assert(err, IsNil)
81+
c.Assert(status.IsClean(), Equals, true)
82+
83+
target, err := w.fs.Readlink("bar")
84+
c.Assert(target, Equals, "not-exists")
85+
c.Assert(err, IsNil)
86+
}
87+
5988
func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) {
6089
url := "https://github.com/git-fixtures/submodule.git"
6190
w := &Worktree{
@@ -641,6 +670,28 @@ func (s *WorktreeSuite) TestAddUnmodified(c *C) {
641670
c.Assert(err, IsNil)
642671
}
643672

673+
func (s *WorktreeSuite) TestAddSymlink(c *C) {
674+
dir, err := ioutil.TempDir("", "checkout")
675+
defer os.RemoveAll(dir)
676+
677+
r, err := PlainInit(dir, false)
678+
c.Assert(err, IsNil)
679+
err = util.WriteFile(r.wt, "foo", []byte("qux"), 0644)
680+
c.Assert(err, IsNil)
681+
err = r.wt.Symlink("foo", "bar")
682+
c.Assert(err, IsNil)
683+
684+
w, err := r.Worktree()
685+
c.Assert(err, IsNil)
686+
h, err := w.Add("foo")
687+
c.Assert(err, IsNil)
688+
c.Assert(h, Not(Equals), plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
689+
690+
h, err = w.Add("bar")
691+
c.Assert(err, IsNil)
692+
c.Assert(h, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
693+
}
694+
644695
func (s *WorktreeSuite) TestRemove(c *C) {
645696
fs := memfs.New()
646697
w := &Worktree{

0 commit comments

Comments
 (0)