aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/object/commitnode.go
blob: a613eb4643e4e879c88ef2600554055577ab318c (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package object

import (
	"fmt"
	"io"
	"time"

	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

// CommitNode is generic interface encapsulating either Commit object or

// graphCommitNode object

type CommitNode interface {
	ID() plumbing.Hash
	Tree() (*Tree, error)
	CommitTime() time.Time
}

// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects

// and accessor methods for walking it as a directed graph

type CommitNodeIndex interface {
	NumParents(node CommitNode) int
	ParentNodes(node CommitNode) CommitNodeIter
	ParentNode(node CommitNode, i int) (CommitNode, error)
	ParentHashes(node CommitNode) []plumbing.Hash

	Get(hash plumbing.Hash) (CommitNode, error)

	// Commit returns the full commit object from the node

	Commit(node CommitNode) (*Commit, error)
}

// CommitNodeIter is a generic closable interface for iterating over commit nodes.

type CommitNodeIter interface {
	Next() (CommitNode, error)
	ForEach(func(CommitNode) error) error
	Close()
}

// graphCommitNode is a reduced representation of Commit as presented in the commit

// graph file (commitgraph.Node). It is merely useful as an optimization for walking

// the commit graphs.

//

// graphCommitNode implements the CommitNode interface.

type graphCommitNode struct {
	// Hash for the Commit object

	hash plumbing.Hash
	// Index of the node in the commit graph file

	index int

	node *commitgraph.Node
	gci  *graphCommitNodeIndex
}

// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit

// graph files and the object store.

//

// graphCommitNodeIndex implements the CommitNodeIndex interface

type graphCommitNodeIndex struct {
	commitGraph commitgraph.Index
	s           storer.EncodedObjectStorer
}

// objectCommitNode is a representation of Commit as presented in the GIT object format.

//

// objectCommitNode implements the CommitNode interface.

type objectCommitNode struct {
	commit *Commit
}

// objectCommitNodeIndex is an index that can load CommitNode objects only from the

// object store.

//

// objectCommitNodeIndex implements the CommitNodeIndex interface

type objectCommitNodeIndex struct {
	s storer.EncodedObjectStorer
}

// ID returns the Commit object id referenced by the commit graph node.

func (c *graphCommitNode) ID() plumbing.Hash {
	return c.hash
}

// Tree returns the Tree referenced by the commit graph node.

func (c *graphCommitNode) Tree() (*Tree, error) {
	return GetTree(c.gci.s, c.node.TreeHash)
}

// CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node.

func (c *graphCommitNode) CommitTime() time.Time {
	return c.node.When
}

func (c *graphCommitNode) String() string {
	return fmt.Sprintf(
		"%s %s\nDate:   %s",
		plumbing.CommitObject, c.ID(),
		c.CommitTime().Format(DateFormat),
	)
}

func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex {
	return &graphCommitNodeIndex{commitGraph, s}
}

// NumParents returns the number of parents in a commit.

func (gci *graphCommitNodeIndex) NumParents(node CommitNode) int {
	if cgn, ok := node.(*graphCommitNode); ok {
		return len(cgn.node.ParentIndexes)
	}
	co := node.(*objectCommitNode)
	return co.commit.NumParents()
}

// ParentNodes return a CommitNodeIter for parents of specified node.

func (gci *graphCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter {
	return newParentgraphCommitNodeIter(gci, node)
}

// ParentNode returns the ith parent of a commit.

func (gci *graphCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) {
	if cgn, ok := node.(*graphCommitNode); ok {
		if len(cgn.node.ParentIndexes) == 0 || i >= len(cgn.node.ParentIndexes) {
			return nil, ErrParentNotFound
		}

		parent, err := gci.commitGraph.GetNodeByIndex(cgn.node.ParentIndexes[i])
		if err != nil {
			return nil, err
		}

		return &graphCommitNode{
			hash:  cgn.node.ParentHashes[i],
			index: cgn.node.ParentIndexes[i],
			node:  parent,
			gci:   gci,
		}, nil
	}

	co := node.(*objectCommitNode)
	if len(co.commit.ParentHashes) == 0 || i >= len(co.commit.ParentHashes) {
		return nil, ErrParentNotFound
	}

	parentHash := co.commit.ParentHashes[i]
	return gci.Get(parentHash)
}

// ParentHashes returns hashes of the parent commits for a specified node

func (gci *graphCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash {
	if cgn, ok := node.(*graphCommitNode); ok {
		return cgn.node.ParentHashes
	}
	co := node.(*objectCommitNode)
	return co.commit.ParentHashes
}

// NodeFromHash looks up a commit node by it's object hash

func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) {
	// Check the commit graph first

	parentIndex, err := gci.commitGraph.GetIndexByHash(hash)
	if err == nil {
		parent, err := gci.commitGraph.GetNodeByIndex(parentIndex)
		if err != nil {
			return nil, err
		}

		return &graphCommitNode{
			hash:  hash,
			index: parentIndex,
			node:  parent,
			gci:   gci,
		}, nil
	}

	// Fallback to loading full commit object

	commit, err := GetCommit(gci.s, hash)
	if err != nil {
		return nil, err
	}

	return &objectCommitNode{commit: commit}, nil
}

// Commit returns the full Commit object representing the commit graph node.

func (gci *graphCommitNodeIndex) Commit(node CommitNode) (*Commit, error) {
	if cgn, ok := node.(*graphCommitNode); ok {
		return GetCommit(gci.s, cgn.ID())
	}
	co := node.(*objectCommitNode)
	return co.commit, nil
}

// CommitTime returns the time when the commit was performed.

//

// CommitTime is present to fulfill the CommitNode interface.

func (c *objectCommitNode) CommitTime() time.Time {
	return c.commit.Committer.When
}

// ID returns the Commit object id referenced by the node.

func (c *objectCommitNode) ID() plumbing.Hash {
	return c.commit.ID()
}

// Tree returns the Tree referenced by the node.

func (c *objectCommitNode) Tree() (*Tree, error) {
	return c.commit.Tree()
}

func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex {
	return &objectCommitNodeIndex{s}
}

// NumParents returns the number of parents in a commit.

func (oci *objectCommitNodeIndex) NumParents(node CommitNode) int {
	co := node.(*objectCommitNode)
	return co.commit.NumParents()
}

// ParentNodes return a CommitNodeIter for parents of specified node.

func (oci *objectCommitNodeIndex) ParentNodes(node CommitNode) CommitNodeIter {
	return newParentgraphCommitNodeIter(oci, node)
}

// ParentNode returns the ith parent of a commit.

func (oci *objectCommitNodeIndex) ParentNode(node CommitNode, i int) (CommitNode, error) {
	co := node.(*objectCommitNode)
	parent, err := co.commit.Parent(i)
	if err != nil {
		return nil, err
	}
	return &objectCommitNode{commit: parent}, nil
}

// ParentHashes returns hashes of the parent commits for a specified node

func (oci *objectCommitNodeIndex) ParentHashes(node CommitNode) []plumbing.Hash {
	co := node.(*objectCommitNode)
	return co.commit.ParentHashes
}

// NodeFromHash looks up a commit node by it's object hash

func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) {
	commit, err := GetCommit(oci.s, hash)
	if err != nil {
		return nil, err
	}

	return &objectCommitNode{commit: commit}, nil
}

// Commit returns the full Commit object representing the commit graph node.

func (oci *objectCommitNodeIndex) Commit(node CommitNode) (*Commit, error) {
	co := node.(*objectCommitNode)
	return co.commit, nil
}

// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex.

type parentCommitNodeIter struct {
	gci  CommitNodeIndex
	node CommitNode
	i    int
}

func newParentgraphCommitNodeIter(gci CommitNodeIndex, node CommitNode) CommitNodeIter {
	return &parentCommitNodeIter{gci, node, 0}
}

// Next moves the iterator to the next commit and returns a pointer to it. If

// there are no more commits, it returns io.EOF.

func (iter *parentCommitNodeIter) Next() (CommitNode, error) {
	obj, err := iter.gci.ParentNode(iter.node, iter.i)
	if err == ErrParentNotFound {
		return nil, io.EOF
	}
	if err == nil {
		iter.i++
	}

	return obj, err
}

// ForEach call the cb function for each commit contained on this iter until

// an error appends or the end of the iter is reached. If ErrStop is sent

// the iteration is stopped but no error is returned. The iterator is closed.

func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error {
	for {
		obj, err := iter.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}

			return err
		}

		if err := cb(obj); err != nil {
			if err == storer.ErrStop {
				return nil
			}

			return err
		}
	}
}

func (iter *parentCommitNodeIter) Close() {
}