package object
import (
"context"
"sort"
fixtures "github.com/go-git/go-git-fixtures/v4"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/format/diff"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/storage/filesystem"
"github.com/go-git/go-git/v5/utils/merkletrie"
. "gopkg.in/check.v1"
)
type ChangeSuite struct {
fixtures.Suite
Storer storer.EncodedObjectStorer
Fixture *fixtures.Fixture
}
func (s *ChangeSuite) SetUpSuite(c *C) {
s.Fixture = fixtures.ByURL("https://github.com/src-d/go-git.git").
ByTag(".git").One()
sto := filesystem.NewStorage(s.Fixture.DotGit(), cache.NewObjectLRUDefault())
s.Storer = sto
}
func (s *ChangeSuite) tree(c *C, h plumbing.Hash) *Tree {
t, err := GetTree(s.Storer, h)
c.Assert(err, IsNil)
return t
}
var _ = Suite(&ChangeSuite{})
func (s *ChangeSuite) TestInsert(c *C) {
// Commit a5078b19f08f63e7948abd0a5e2fb7d319d3a565 of the go-git
// fixture inserted "examples/clone/main.go".
//
// On that commit, the "examples/clone" tree is
// 6efca3ff41cab651332f9ebc0c96bb26be809615
//
// and the "examples/colone/main.go" is
// f95dc8f7923add1a8b9f72ecb1e8db1402de601a
path := "examples/clone/main.go"
name := "main.go"
mode := filemode.Regular
blob := plumbing.NewHash("f95dc8f7923add1a8b9f72ecb1e8db1402de601a")
tree := plumbing.NewHash("6efca3ff41cab651332f9ebc0c96bb26be809615")
change := &Change{
From: empty,
To: ChangeEntry{
Name: path,
Tree: s.tree(c, tree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: blob,
},
},
}
action, err := change.Action()
c.Assert(err, IsNil)
c.Assert(action, Equals, merkletrie.Insert)
from, to, err := change.Files()
c.Assert(err, IsNil)
c.Assert(from, IsNil)
c.Assert(to.Name, Equals, name)
c.Assert(to.Blob.Hash, Equals, blob)
p, err := change.Patch()
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Add)
p, err = change.PatchContext(context.Background())
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Add)
str := change.String()
c.Assert(str, Equals, "<Action: Insert, Path: examples/clone/main.go>")
}
func (s *ChangeSuite) TestDelete(c *C) {
// Commit f6011d65d57c2a866e231fc21a39cb618f86f9ea of the go-git
// fixture deleted "utils/difftree/difftree.go".
//
// The parent of that commit is
// 9b4a386db3d98a4362516a00ef3d04d4698c9bcd.
//
// On that parent commit, the "utils/difftree" tree is
// f3d11566401ce4b0808aab9dd6fad3d5abf1481a.
//
// and the "utils/difftree/difftree.go" is
// e2cb9a5719daf634d45a063112b4044ee81da13ea.
path := "utils/difftree/difftree.go"
name := "difftree.go"
mode := filemode.Regular
blob := plumbing.NewHash("e2cb9a5719daf634d45a063112b4044ee81da13e")
tree := plumbing.NewHash("f3d11566401ce4b0808aab9dd6fad3d5abf1481a")
change := &Change{
From: ChangeEntry{
Name: path,
Tree: s.tree(c, tree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: blob,
},
},
To: empty,
}
action, err := change.Action()
c.Assert(err, IsNil)
c.Assert(action, Equals, merkletrie.Delete)
from, to, err := change.Files()
c.Assert(err, IsNil)
c.Assert(to, IsNil)
c.Assert(from.Name, Equals, name)
c.Assert(from.Blob.Hash, Equals, blob)
p, err := change.Patch()
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Delete)
p, err = change.PatchContext(context.Background())
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 1)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Delete)
str := change.String()
c.Assert(str, Equals, "<Action: Delete, Path: utils/difftree/difftree.go>")
}
func (s *ChangeSuite) TestModify(c *C) {
// Commit 7beaad711378a4daafccc2c04bc46d36df2a0fd1 of the go-git
// fixture modified "examples/latest/latest.go".
// the "examples/latest" tree is
// b1f01b730b855c82431918cb338ad47ed558999b.
// and "examples/latest/latest.go" is blob
// 05f583ace3a9a078d8150905a53a4d82567f125f.
//
// The parent of that commit is
// 337148ef6d751477796922ac127b416b8478fcc4.
// the "examples/latest" tree is
// 8b0af31d2544acb5c4f3816a602f11418cbd126e.
// and "examples/latest/latest.go" is blob
// de927fad935d172929aacf20e71f3bf0b91dd6f9.
path := "utils/difftree/difftree.go"
name := "difftree.go"
mode := filemode.Regular
fromBlob := plumbing.NewHash("05f583ace3a9a078d8150905a53a4d82567f125f")
fromTree := plumbing.NewHash("b1f01b730b855c82431918cb338ad47ed558999b")
toBlob := plumbing.NewHash("de927fad935d172929aacf20e71f3bf0b91dd6f9")
toTree := plumbing.NewHash("8b0af31d2544acb5c4f3816a602f11418cbd126e")
change := &Change{
From: ChangeEntry{
Name: path,
Tree: s.tree(c, fromTree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: fromBlob,
},
},
To: ChangeEntry{
Name: path,
Tree: s.tree(c, toTree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: toBlob,
},
},
}
action, err := change.Action()
c.Assert(err, IsNil)
c.Assert(action, Equals, merkletrie.Modify)
from, to, err := change.Files()
c.Assert(err, IsNil)
c.Assert(from.Name, Equals, name)
c.Assert(from.Blob.Hash, Equals, fromBlob)
c.Assert(to.Name, Equals, name)
c.Assert(to.Blob.Hash, Equals, toBlob)
p, err := change.Patch()
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 7)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Equal)
c.Assert(p.FilePatches()[0].Chunks()[1].Type(), Equals, diff.Delete)
c.Assert(p.FilePatches()[0].Chunks()[2].Type(), Equals, diff.Add)
c.Assert(p.FilePatches()[0].Chunks()[3].Type(), Equals, diff.Equal)
c.Assert(p.FilePatches()[0].Chunks()[4].Type(), Equals, diff.Delete)
c.Assert(p.FilePatches()[0].Chunks()[5].Type(), Equals, diff.Add)
c.Assert(p.FilePatches()[0].Chunks()[6].Type(), Equals, diff.Equal)
p, err = change.PatchContext(context.Background())
c.Assert(err, IsNil)
c.Assert(len(p.FilePatches()), Equals, 1)
c.Assert(len(p.FilePatches()[0].Chunks()), Equals, 7)
c.Assert(p.FilePatches()[0].Chunks()[0].Type(), Equals, diff.Equal)
c.Assert(p.FilePatches()[0].Chunks()[1].Type(), Equals, diff.Delete)
c.Assert(p.FilePatches()[0].Chunks()[2].Type(), Equals, diff.Add)
c.Assert(p.FilePatches()[0].Chunks()[3].Type(), Equals, diff.Equal)
c.Assert(p.FilePatches()[0].Chunks()[4].Type(), Equals, diff.Delete)
c.Assert(p.FilePatches()[0].Chunks()[5].Type(), Equals, diff.Add)
c.Assert(p.FilePatches()[0].Chunks()[6].Type(), Equals, diff.Equal)
str := change.String()
c.Assert(str, Equals, "<Action: Modify, Path: utils/difftree/difftree.go>")
}
func (s *ChangeSuite) TestEmptyChangeFails(c *C) {
change := &Change{}
_, err := change.Action()
c.Assert(err, ErrorMatches, "malformed.*")
_, _, err = change.Files()
c.Assert(err, ErrorMatches, "malformed.*")
str := change.String()
c.Assert(str, Equals, "malformed change")
}
// test reproducing bug #317
func (s *ChangeSuite) TestNoFileFilemodes(c *C) {
f := fixtures.ByURL("https://github.com/git-fixtures/submodule.git").One()
sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
iter, err := sto.IterEncodedObjects(plumbing.AnyObject)
c.Assert(err, IsNil)
var commits []*Commit
iter.ForEach(func(o plumbing.EncodedObject) error {
if o.Type() == plumbing.CommitObject {
commit, err := GetCommit(sto, o.Hash())
c.Assert(err, IsNil)
commits = append(commits, commit)
}
return nil
})
c.Assert(len(commits), Not(Equals), 0)
var prev *Commit
for _, commit := range commits {
if prev == nil {
prev = commit
continue
}
tree, err := commit.Tree()
c.Assert(err, IsNil)
prevTree, err := prev.Tree()
c.Assert(err, IsNil)
changes, err := DiffTree(tree, prevTree)
c.Assert(err, IsNil)
for _, change := range changes {
_, _, err := change.Files()
c.Assert(err, IsNil)
}
prev = commit
}
}
func (s *ChangeSuite) TestErrorsFindingChildsAreDetected(c *C) {
// Commit 7beaad711378a4daafccc2c04bc46d36df2a0fd1 of the go-git
// fixture modified "examples/latest/latest.go".
// the "examples/latest" tree is
// b1f01b730b855c82431918cb338ad47ed558999b.
// and "examples/latest/latest.go" is blob
// 05f583ace3a9a078d8150905a53a4d82567f125f.
//
// The parent of that commit is
// 337148ef6d751477796922ac127b416b8478fcc4.
// the "examples/latest" tree is
// 8b0af31d2544acb5c4f3816a602f11418cbd126e.
// and "examples/latest/latest.go" is blob
// de927fad935d172929aacf20e71f3bf0b91dd6f9.
path := "utils/difftree/difftree.go"
name := "difftree.go"
mode := filemode.Regular
fromBlob := plumbing.NewHash("aaaa") // does not exists
fromTree := plumbing.NewHash("b1f01b730b855c82431918cb338ad47ed558999b")
toBlob := plumbing.NewHash("bbbb") // does not exists
toTree := plumbing.NewHash("8b0af31d2544acb5c4f3816a602f11418cbd126e")
change := &Change{
From: ChangeEntry{
Name: path,
Tree: s.tree(c, fromTree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: fromBlob,
},
},
To: ChangeEntry{},
}
_, _, err := change.Files()
c.Assert(err, ErrorMatches, "object not found")
change = &Change{
From: empty,
To: ChangeEntry{
Name: path,
Tree: s.tree(c, toTree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: toBlob,
},
},
}
_, _, err = change.Files()
c.Assert(err, ErrorMatches, "object not found")
}
func (s *ChangeSuite) TestChangesString(c *C) {
expected := "[]"
changes := Changes{}
obtained := changes.String()
c.Assert(obtained, Equals, expected)
expected = "[<Action: Modify, Path: bla>]"
changes = make([]*Change, 1)
changes[0] = &Change{}
changes[0].From.Name = "bla"
changes[0].To.Name = "bla"
obtained = changes.String()
c.Assert(obtained, Equals, expected)
expected = "[<Action: Modify, Path: bla>, <Action: Delete, Path: foo/bar>]"
changes = make([]*Change, 2)
changes[0] = &Change{}
changes[0].From.Name = "bla"
changes[0].To.Name = "bla"
changes[1] = &Change{}
changes[1].From.Name = "foo/bar"
obtained = changes.String()
c.Assert(obtained, Equals, expected)
}
func (s *ChangeSuite) TestChangesSort(c *C) {
changes := make(Changes, 3)
changes[0] = &Change{}
changes[0].From.Name = "z"
changes[0].To.Name = "z"
changes[1] = &Change{}
changes[1].From.Name = "b/b"
changes[2] = &Change{}
changes[2].To.Name = "b/a"
expected := "[<Action: Insert, Path: b/a>, " +
"<Action: Delete, Path: b/b>, " +
"<Action: Modify, Path: z>]"
sort.Sort(changes)
c.Assert(changes.String(), Equals, expected)
}
func (s *ChangeSuite) TestCancel(c *C) {
// Commit a5078b19f08f63e7948abd0a5e2fb7d319d3a565 of the go-git
// fixture inserted "examples/clone/main.go".
//
// On that commit, the "examples/clone" tree is
// 6efca3ff41cab651332f9ebc0c96bb26be809615
//
// and the "examples/clone/main.go" is
// f95dc8f7923add1a8b9f72ecb1e8db1402de601a
path := "examples/clone/main.go"
name := "main.go"
mode := filemode.Regular
blob := plumbing.NewHash("f95dc8f7923add1a8b9f72ecb1e8db1402de601a")
tree := plumbing.NewHash("6efca3ff41cab651332f9ebc0c96bb26be809615")
change := &Change{
From: empty,
To: ChangeEntry{
Name: path,
Tree: s.tree(c, tree),
TreeEntry: TreeEntry{
Name: name,
Mode: mode,
Hash: blob,
},
},
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
p, err := change.PatchContext(ctx)
c.Assert(p, IsNil)
c.Assert(err, ErrorMatches, "operation canceled")
}