aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/object/commit_walker_file.go
blob: 84e738ac6c499f3da24aedf3d84aafe08a3f081e (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
package object

import (
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
	"io"
)

type commitFileIter struct {
	fileName      string
	sourceIter    CommitIter
	currentCommit *Commit
}

// NewCommitFileIterFromIter returns a commit iterator which performs diffTree between
// successive trees returned from the commit iterator from the argument. The purpose of this is
// to find the commits that explain how the files that match the path came to be.
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter) CommitIter {
	iterator := new(commitFileIter)
	iterator.sourceIter = commitIter
	iterator.fileName = fileName
	return iterator
}

func (c *commitFileIter) Next() (*Commit, error) {
	if c.currentCommit == nil {
		var err error
		c.currentCommit, err = c.sourceIter.Next()
		if err != nil {
			return nil, err
		}
	}
	commit, commitErr := c.getNextFileCommit()

	// Setting current-commit to nil to prevent unwanted states when errors are raised
	if commitErr != nil {
		c.currentCommit = nil
	}
	return commit, commitErr
}

func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
	for {
		// Parent-commit can be nil if the current-commit is the initial commit
		parentCommit, parentCommitErr := c.sourceIter.Next()
		if parentCommitErr != nil {
			// If the parent-commit is beyond the initial commit, keep it nil
			if parentCommitErr != io.EOF {
				return nil, parentCommitErr
			}
			parentCommit = nil
		}

		// Fetch the trees of the current and parent commits
		currentTree, currTreeErr := c.currentCommit.Tree()
		if currTreeErr != nil {
			return nil, currTreeErr
		}

		var parentTree *Tree
		if parentCommit != nil {
			var parentTreeErr error
			parentTree, parentTreeErr = parentCommit.Tree()
			if parentTreeErr != nil {
				return nil, parentTreeErr
			}
		}

		// Find diff between current and parent trees
		changes, diffErr := DiffTree(currentTree, parentTree)
		if diffErr != nil {
			return nil, diffErr
		}

		foundChangeForFile := false
		for _, change := range changes {
			if change.name() == c.fileName {
				foundChangeForFile = true
				break
			}
		}

		// Storing the current-commit in-case a change is found, and
		// Updating the current-commit for the next-iteration
		prevCommit := c.currentCommit
		c.currentCommit = parentCommit

		if foundChangeForFile == true {
			return prevCommit, nil
		}

		// If not matches found and if parent-commit is beyond the initial commit, then return with EOF
		if parentCommit == nil {
			return nil, io.EOF
		}
	}
}

func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
	for {
		commit, nextErr := c.Next()
		if nextErr != nil {
			return nextErr
		}
		err := cb(commit)
		if err == storer.ErrStop {
			return nil
		} else if err != nil {
			return err
		}
	}
}

func (c *commitFileIter) Close() {
	c.sourceIter.Close()
}