package object import ( "io" "gopkg.in/src-d/go-git.v4/plumbing/storer" ) type commitFileIter struct { fileName string sourceIter CommitIter currentCommit *Commit all bool } // 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, all bool) CommitIter { iterator := new(commitFileIter) iterator.sourceIter = commitIter iterator.fileName = fileName iterator.all = all 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 { continue } // filename matches, now check if source iterator contains all commits (from all refs) if c.all { // for `git log --all` also check if the next commit comes from the same parent for _, h := range c.currentCommit.ParentHashes { if h == parentCommit.Hash { foundChangeForFile = true break } } } else { foundChangeForFile = true } if foundChangeForFile { 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() }