diff options
Diffstat (limited to 'plumbing/revlist/revlist.go')
-rw-r--r-- | plumbing/revlist/revlist.go | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/plumbing/revlist/revlist.go b/plumbing/revlist/revlist.go new file mode 100644 index 0000000..106e78b --- /dev/null +++ b/plumbing/revlist/revlist.go @@ -0,0 +1,128 @@ +// Package revlist implements functions to walk the objects referenced by a +// commit history. Roughly equivalent to git-rev-list command. +package revlist + +import ( + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +// Objects applies a complementary set. It gets all the hashes from all +// the reachable objects from the given commits. Ignore param are object hashes +// that we want to ignore on the result. It is a list because is +// easier to interact with other porcelain elements, but internally it is +// converted to a map. All that objects must be accessible from the object +// storer. +func Objects( + s storer.EncodedObjectStorer, + commits []*object.Commit, + ignore []plumbing.Hash) ([]plumbing.Hash, error) { + + seen := hashListToSet(ignore) + result := make(map[plumbing.Hash]bool) + for _, c := range commits { + err := reachableObjects(s, c, seen, func(h plumbing.Hash) error { + if !seen[h] { + result[h] = true + seen[h] = true + } + + return nil + }) + + if err != nil { + return nil, err + } + } + + return hashSetToList(result), nil +} + +// reachableObjects returns, using the callback function, all the reachable +// objects from the specified commit. To avoid to iterate over seen commits, +// if a commit hash is into the 'seen' set, we will not iterate all his trees +// and blobs objects. +func reachableObjects( + s storer.EncodedObjectStorer, + commit *object.Commit, + seen map[plumbing.Hash]bool, + cb func(h plumbing.Hash) error) error { + + return iterateCommits(commit, func(commit *object.Commit) error { + if seen[commit.Hash] { + return nil + } + + if err := cb(commit.Hash); err != nil { + return err + } + + return iterateCommitTrees(s, commit, func(h plumbing.Hash) error { + return cb(h) + }) + }) +} + +// iterateCommits iterate all reachable commits from the given one +func iterateCommits(commit *object.Commit, cb func(c *object.Commit) error) error { + if err := cb(commit); err != nil { + return err + } + + return object.WalkCommitHistory(commit, func(c *object.Commit) error { + return cb(c) + }) +} + +// iterateCommitTrees iterate all reachable trees from the given commit +func iterateCommitTrees( + s storer.EncodedObjectStorer, + commit *object.Commit, + cb func(h plumbing.Hash) error) error { + + tree, err := commit.Tree() + if err != nil { + return err + } + if err := cb(tree.Hash); err != nil { + return err + } + + treeWalker := object.NewTreeWalker(tree, true) + + for { + _, e, err := treeWalker.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if err := cb(e.Hash); err != nil { + return err + } + } + + return nil +} + +func hashSetToList(hashes map[plumbing.Hash]bool) []plumbing.Hash { + var result []plumbing.Hash + for key := range hashes { + result = append(result, key) + } + + return result +} + +func hashListToSet(hashes []plumbing.Hash) map[plumbing.Hash]bool { + result := make(map[plumbing.Hash]bool) + for _, h := range hashes { + result[h] = true + } + + return result +} |