// Package revlist provides support to access the ancestors of commits, in a // similar way as the git-rev-list command. package revlist import ( "fmt" "io" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" "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 objects. Ignore param are object hashes // that we want to ignore on the result. All that objects must be accessible // from the object storer. func Objects( s storer.EncodedObjectStorer, objs, ignore []plumbing.Hash, ) ([]plumbing.Hash, error) { return ObjectsWithStorageForIgnores(s, s, objs, ignore) } // ObjectsWithStorageForIgnores is the same as Objects, but a // secondary storage layer can be provided, to be used to finding the // full set of objects to be ignored while finding the reachable // objects. This is useful when the main `s` storage layer is slow // and/or remote, while the ignore list is available somewhere local. func ObjectsWithStorageForIgnores( s, ignoreStore storer.EncodedObjectStorer, objs, ignore []plumbing.Hash, ) ([]plumbing.Hash, error) { ignore, err := objects(ignoreStore, ignore, nil, true) if err != nil { return nil, err } return objects(s, objs, ignore, false) } func objects( s storer.EncodedObjectStorer, objects, ignore []plumbing.Hash, allowMissingObjects bool, ) ([]plumbing.Hash, error) { seen := hashListToSet(ignore) result := make(map[plumbing.Hash]bool) visited := make(map[plumbing.Hash]bool) walkerFunc := func(h plumbing.Hash) { if !seen[h] { result[h] = true seen[h] = true } } for _, h := range objects { if err := processObject(s, h, seen, visited, ignore, walkerFunc); err != nil { if allowMissingObjects && err == plumbing.ErrObjectNotFound { continue } return nil, err } } return hashSetToList(result), nil } // processObject obtains the object using the hash an process it depending of its type func processObject( s storer.EncodedObjectStorer, h plumbing.Hash, seen map[plumbing.Hash]bool, visited map[plumbing.Hash]bool, ignore []plumbing.Hash, walkerFunc func(h plumbing.Hash), ) error { if seen[h] { return nil } o, err := s.EncodedObject(plumbing.AnyObject, h) if err != nil { return err } do, err := object.DecodeObject(s, o) if err != nil { return err } switch do := do.(type) { case *object.Commit: return reachableObjects(do, seen, visited, ignore, walkerFunc) case *object.Tree: return iterateCommitTrees(seen, do, walkerFunc) case *object.Tag: walkerFunc(do.Hash) return processObject(s, do.Target, seen, visited, ignore, walkerFunc) case *object.Blob: walkerFunc(do.Hash) default: return fmt.Errorf("object type not valid: %s. "+ "Object reference: %s", o.Type(), o.Hash()) } return 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( commit *object.Commit, seen map[plumbing.Hash]bool, visited map[plumbing.Hash]bool, ignore []plumbing.Hash, cb func(h plumbing.Hash), ) error { i := object.NewCommitPreorderIter(commit, seen, ignore) pending := make(map[plumbing.Hash]bool) addPendingParents(pending, visited, commit) for { commit, err := i.Next() if err == io.EOF { break } if err != nil { return err } if pending[commit.Hash] { delete(pending, commit.Hash) } addPendingParents(pending, visited, commit) if visited[commit.Hash] && len(pending) == 0 { break } if seen[commit.Hash] { continue } cb(commit.Hash) tree, err := commit.Tree() if err != nil { return err } if err := iterateCommitTrees(seen, tree, cb); err != nil { return err } } return nil } func addPendingParents(pending, visited map[plumbing.Hash]bool, commit *object.Commit) { for _, p := range commit.ParentHashes { if !visited[p] { pending[p] = true } } } // iterateCommitTrees iterate all reachable trees from the given commit func iterateCommitTrees( seen map[plumbing.Hash]bool, tree *object.Tree, cb func(h plumbing.Hash), ) error { if seen[tree.Hash] { return nil } cb(tree.Hash) treeWalker := object.NewTreeWalker(tree, true, seen) for { _, e, err := treeWalker.Next() if err == io.EOF { break } if err != nil { return err } if e.Mode == filemode.Submodule { continue } if seen[e.Hash] { continue } cb(e.Hash) } 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 }