aboutsummaryrefslogtreecommitdiffstats
path: root/tree_walker.go
blob: 1692c2f2ed0581d9439d605c3b739528fd97027b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package git

import (
	"io"
	"path"

	"gopkg.in/src-d/go-git.v4/core"
)

const (
	startingStackSize = 8
)

// TreeWalker provides a means of walking through all of the entries in a Tree.
type TreeWalker struct {
	stack []treeEntryIter
	base  string

	r *Repository
	t *Tree
}

// NewTreeWalker returns a new TreeWalker for the given repository and tree.
//
// It is the caller's responsibility to call Close() when finished with the
// tree walker.
func NewTreeWalker(r *Repository, t *Tree) *TreeWalker {
	w := TreeWalker{
		stack: make([]treeEntryIter, 0, startingStackSize),
		base:  "",
		r:     r,
		t:     t,
	}
	w.stack = append(w.stack, treeEntryIter{t, 0})
	return &w
}

// Next returns the next object from the tree. Objects are returned in order
// and subtrees are included. After the last object has been returned further
// calls to Next() will return io.EOF.
//
// In the current implementation any objects which cannot be found in the
// underlying repository will be skipped automatically. It is possible that this
// may change in future versions.
func (w *TreeWalker) Next() (name string, entry TreeEntry, obj Object, err error) {
	for {
		current := len(w.stack) - 1
		if current < 0 {
			// Nothing left on the stack so we're finished
			err = io.EOF
			return
		}
		if current > maxTreeDepth {
			// We're probably following bad data or some self-referencing tree
			err = ErrMaxTreeDepth
			return
		}

		entry, err = w.stack[current].Next()
		if err == io.EOF {
			// Finished with the current tree, move back up to the parent
			w.stack = w.stack[:current]
			w.base, _ = path.Split(w.base)
			w.base = path.Clean(w.base) // Remove trailing slash
			continue
		}
		if err != nil {
			return
		}

		obj, err = w.r.Object(entry.Hash)
		if err == ErrObjectNotFound {
			// FIXME: Avoid doing this here in case the caller actually cares about
			//        missing objects.
			err = nil
			continue // ignore entries without hash (= submodule dirs)
		}

		name = path.Join(w.base, entry.Name)

		if err != nil {
			return
		}

		break
	}

	if t, ok := obj.(*Tree); ok {
		w.stack = append(w.stack, treeEntryIter{t, 0})
		w.base = path.Join(w.base, entry.Name)
	}

	return
}

func (w *TreeWalker) ForEach(cb func(fullpath string, e TreeEntry) error) error {
	for {
		path, e, _, err := w.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		if err := cb(path, e); err != nil {
			if err == core.ErrStop {
				return nil
			}

			return err
		}
	}
}

// Tree returns the tree that the tree walker most recently operated on.
func (w *TreeWalker) Tree() *Tree {
	current := len(w.stack) - 1
	if current < 0 {
		return nil
	}
	return w.stack[current].t
}

// Close releases any resources used by the TreeWalker.
func (w *TreeWalker) Close() {
	w.stack = nil
}