aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/object/commit_walker.go
blob: 681ea5ec8dc004d518993e8429b158b72ba171cf (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
package object

import (
	"io"

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

type commitWalker struct {
	seen  map[plumbing.Hash]bool
	stack []*CommitIter
	start *Commit
	cb    func(*Commit) error
}

// WalkCommitHistory walks the commit history, starting at the given commit and
// visiting its parents in pre-order. The given callback will be called for each
// visited commit. Each commit will be visited only once. If the callback returns
// an error, walking will stop and will return the error. Other errors might be
// returned if the history cannot be traversed (e.g. missing objects).
func WalkCommitHistory(c *Commit, cb func(*Commit) error) error {
	w := &commitWalker{
		seen:  make(map[plumbing.Hash]bool),
		stack: make([]*CommitIter, 0),
		start: c,
		cb:    cb,
	}

	return w.walk()
}

func (w *commitWalker) walk() error {
	var commit *Commit

	if w.start != nil {
		commit = w.start
		w.start = nil
	} else {
		current := len(w.stack) - 1
		if current < 0 {
			return nil
		}

		var err error
		commit, err = w.stack[current].Next()
		if err == io.EOF {
			w.stack = w.stack[:current]
			return w.walk()
		}

		if err != nil {
			return err
		}
	}

	// check and update seen
	if w.seen[commit.Hash] {
		return w.walk()
	}

	w.seen[commit.Hash] = true
	if commit.NumParents() > 0 {
		w.stack = append(w.stack, commit.Parents())
	}

	if err := w.cb(commit); err != nil {
		return err
	}

	return w.walk()
}

// WalkCommitHistoryPost walks the commit history like WalkCommitHistory
// but in post-order. This means that after walking a merge commit, the
// merged commit will be walked before the base it was merged on. This
// can be useful if you wish to see the history in chronological order.
func WalkCommitHistoryPost(c *Commit, cb func(*Commit) error) error {
	stack := []*Commit{c}
	seen := make(map[plumbing.Hash]bool)
	for len(stack) > 0 {
		c := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		if seen[c.Hash] {
			continue
		}
		seen[c.Hash] = true
		if err := cb(c); err != nil {
			return err
		}
		c.Parents().ForEach(func(pcm *Commit) error {
			stack = append(stack, pcm)
			return nil
		})
	}
	return nil
}