1package git
2
3import (
4 "fmt"
5
6 "gopkg.in/src-d/go-git.v4/plumbing"
7 "gopkg.in/src-d/go-git.v4/plumbing/filemode"
8 "gopkg.in/src-d/go-git.v4/plumbing/object"
9 "gopkg.in/src-d/go-git.v4/storage"
10)
11
12type objectWalker struct {
13 Storer storage.Storer
14 // seen is the set of objects seen in the repo.
15 // seen map can become huge if walking over large
16 // repos. Thus using struct{} as the value type.
17 seen map[plumbing.Hash]struct{}
18}
19
20func newObjectWalker(s storage.Storer) *objectWalker {
21 return &objectWalker{s, map[plumbing.Hash]struct{}{}}
22}
23
24// walkAllRefs walks all (hash) refererences from the repo.
25func (p *objectWalker) walkAllRefs() error {
26 // Walk over all the references in the repo.
27 it, err := p.Storer.IterReferences()
28 if err != nil {
29 return err
30 }
31 defer it.Close()
32 err = it.ForEach(func(ref *plumbing.Reference) error {
33 // Exit this iteration early for non-hash references.
34 if ref.Type() != plumbing.HashReference {
35 return nil
36 }
37 return p.walkObjectTree(ref.Hash())
38 })
39 return err
40}
41
42func (p *objectWalker) isSeen(hash plumbing.Hash) bool {
43 _, seen := p.seen[hash]
44 return seen
45}
46
47func (p *objectWalker) add(hash plumbing.Hash) {
48 p.seen[hash] = struct{}{}
49}
50
51// walkObjectTree walks over all objects and remembers references
52// to them in the objectWalker. This is used instead of the revlist
53// walks because memory usage is tight with huge repos.
54func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error {
55 // Check if we have already seen, and mark this object
56 if p.isSeen(hash) {
57 return nil
58 }
59 p.add(hash)
60 // Fetch the object.
61 obj, err := object.GetObject(p.Storer, hash)
62 if err != nil {
63 return fmt.Errorf("Getting object %s failed: %v", hash, err)
64 }
65 // Walk all children depending on object type.
66 switch obj := obj.(type) {
67 case *object.Commit:
68 err = p.walkObjectTree(obj.TreeHash)
69 if err != nil {
70 return err
71 }
72 for _, h := range obj.ParentHashes {
73 err = p.walkObjectTree(h)
74 if err != nil {
75 return err
76 }
77 }
78 case *object.Tree:
79 for i := range obj.Entries {
80 // Shortcut for blob objects:
81 // 'or' the lower bits of a mode and check that it
82 // it matches a filemode.Executable. The type information
83 // is in the higher bits, but this is the cleanest way
84 // to handle plain files with different modes.
85 // Other non-tree objects are somewhat rare, so they
86 // are not special-cased.
87 if obj.Entries[i].Mode|0755 == filemode.Executable {
88 p.add(obj.Entries[i].Hash)
89 continue
90 }
91 // Normal walk for sub-trees (and symlinks etc).
92 err = p.walkObjectTree(obj.Entries[i].Hash)
93 if err != nil {
94 return err
95 }
96 }
97 case *object.Tag:
98 return p.walkObjectTree(obj.Target)
99 default:
100 // Error out on unhandled object types.
101 return fmt.Errorf("Unknown object %X %s %T\n", obj.ID(), obj.Type(), obj)
102 }
103 return nil
104}