fork of go-git with some jj specific features
at main 167 lines 4.2 kB view raw
1package object 2 3import ( 4 "io" 5 6 "github.com/go-git/go-git/v5/plumbing" 7 "github.com/go-git/go-git/v5/plumbing/storer" 8) 9 10type commitPathIter struct { 11 pathFilter func(string) bool 12 sourceIter CommitIter 13 currentCommit *Commit 14 checkParent bool 15} 16 17// NewCommitPathIterFromIter returns a commit iterator which performs diffTree between 18// successive trees returned from the commit iterator from the argument. The purpose of this is 19// to find the commits that explain how the files that match the path came to be. 20// If checkParent is true then the function double checks if potential parent (next commit in a path) 21// is one of the parents in the tree (it's used by `git log --all`). 22// pathFilter is a function that takes path of file as argument and returns true if we want it 23func NewCommitPathIterFromIter(pathFilter func(string) bool, commitIter CommitIter, checkParent bool) CommitIter { 24 iterator := new(commitPathIter) 25 iterator.sourceIter = commitIter 26 iterator.pathFilter = pathFilter 27 iterator.checkParent = checkParent 28 return iterator 29} 30 31// NewCommitFileIterFromIter is kept for compatibility, can be replaced with NewCommitPathIterFromIter 32func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter { 33 return NewCommitPathIterFromIter( 34 func(path string) bool { 35 return path == fileName 36 }, 37 commitIter, 38 checkParent, 39 ) 40} 41 42func (c *commitPathIter) Next() (*Commit, error) { 43 if c.currentCommit == nil { 44 var err error 45 c.currentCommit, err = c.sourceIter.Next() 46 if err != nil { 47 return nil, err 48 } 49 } 50 commit, commitErr := c.getNextFileCommit() 51 52 // Setting current-commit to nil to prevent unwanted states when errors are raised 53 if commitErr != nil { 54 c.currentCommit = nil 55 } 56 return commit, commitErr 57} 58 59func (c *commitPathIter) getNextFileCommit() (*Commit, error) { 60 var parentTree, currentTree *Tree 61 62 for { 63 // Parent-commit can be nil if the current-commit is the initial commit 64 parentCommit, parentCommitErr := c.sourceIter.Next() 65 if parentCommitErr != nil { 66 // If the parent-commit is beyond the initial commit, keep it nil 67 if parentCommitErr != io.EOF { 68 return nil, parentCommitErr 69 } 70 parentCommit = nil 71 } 72 73 if parentTree == nil { 74 var currTreeErr error 75 currentTree, currTreeErr = c.currentCommit.Tree() 76 if currTreeErr != nil { 77 return nil, currTreeErr 78 } 79 } else { 80 currentTree = parentTree 81 parentTree = nil 82 } 83 84 if parentCommit != nil { 85 var parentTreeErr error 86 parentTree, parentTreeErr = parentCommit.Tree() 87 if parentTreeErr != nil { 88 return nil, parentTreeErr 89 } 90 } 91 92 // Find diff between current and parent trees 93 changes, diffErr := DiffTree(currentTree, parentTree) 94 if diffErr != nil { 95 return nil, diffErr 96 } 97 98 found := c.hasFileChange(changes, parentCommit) 99 100 // Storing the current-commit in-case a change is found, and 101 // Updating the current-commit for the next-iteration 102 prevCommit := c.currentCommit 103 c.currentCommit = parentCommit 104 105 if found { 106 return prevCommit, nil 107 } 108 109 // If not matches found and if parent-commit is beyond the initial commit, then return with EOF 110 if parentCommit == nil { 111 return nil, io.EOF 112 } 113 } 114} 115 116func (c *commitPathIter) hasFileChange(changes Changes, parent *Commit) bool { 117 for _, change := range changes { 118 if !c.pathFilter(change.name()) { 119 continue 120 } 121 122 // filename matches, now check if source iterator contains all commits (from all refs) 123 if c.checkParent { 124 // Check if parent is beyond the initial commit 125 if parent == nil || isParentHash(parent.Hash, c.currentCommit) { 126 return true 127 } 128 continue 129 } 130 131 return true 132 } 133 134 return false 135} 136 137func isParentHash(hash plumbing.Hash, commit *Commit) bool { 138 for _, h := range commit.ParentHashes { 139 if h == hash { 140 return true 141 } 142 } 143 return false 144} 145 146func (c *commitPathIter) ForEach(cb func(*Commit) error) error { 147 for { 148 commit, nextErr := c.Next() 149 if nextErr == io.EOF { 150 break 151 } 152 if nextErr != nil { 153 return nextErr 154 } 155 err := cb(commit) 156 if err == storer.ErrStop { 157 return nil 158 } else if err != nil { 159 return err 160 } 161 } 162 return nil 163} 164 165func (c *commitPathIter) Close() { 166 c.sourceIter.Close() 167}