package object import ( "context" "sort" "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" "gopkg.in/src-d/go-git-fixtures.v3" ) type ChangeSuite struct { fixtures.Suite Storer storer.EncodedObjectStorer Fixture *fixtures.Fixture } func (s *ChangeSuite) SetUpSuite(c *C) { s.Suite.SetUpSuite(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, "") } 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, "") } 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, "") } 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) { s.Suite.SetUpSuite(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 = "[]" 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 = "[, ]" 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 := "[, " + ", " + "]" 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/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, }, }, } ctx, cancel := context.WithCancel(context.Background()) cancel() p, err := change.PatchContext(ctx) c.Assert(p, IsNil) c.Assert(err, ErrorMatches, "operation canceled") }