Skip to content

Commit 8597ed8

Browse files
committed
[log-file] plumbing: object, Add support for Log with filenames. Fixes src-d#826
Signed-off-by: Nithin <[email protected]>
1 parent 1fdd36c commit 8597ed8

File tree

4 files changed

+268
-6
lines changed

4 files changed

+268
-6
lines changed

options.go

+2
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ type LogOptions struct {
330330
// set Order=LogOrderCommitterTime for ordering by committer time (more compatible with `git log`)
331331
// set Order=LogOrderBSF for Breadth-first search
332332
Order LogOrder
333+
334+
FileName *string
333335
}
334336

335337
var (

plumbing/object/commit_walker_file.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package object
2+
3+
import (
4+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
5+
"io"
6+
)
7+
8+
type commitFileIter struct {
9+
fileName string
10+
sourceIter CommitIter
11+
currentCommit *Commit
12+
}
13+
14+
// NewCommitFileIterFromIter returns a commit iterator which performs diffTree between
15+
// successive trees returned from the commit iterator from the argument. The purpose of this is
16+
// to find the commits that explain how the files that match the path came to be.
17+
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter) CommitIter {
18+
iterator := new(commitFileIter)
19+
iterator.sourceIter = commitIter
20+
iterator.fileName = fileName
21+
return iterator
22+
}
23+
24+
func (c *commitFileIter) Next() (*Commit, error) {
25+
var err error
26+
if c.currentCommit == nil {
27+
c.currentCommit, err = c.sourceIter.Next()
28+
if err != nil {
29+
return nil, err
30+
}
31+
}
32+
33+
for {
34+
// Parent-commit can be nil if the current-commit is the initial commit
35+
parentCommit, parentCommitErr := c.sourceIter.Next()
36+
if parentCommitErr != nil {
37+
if parentCommitErr != io.EOF {
38+
err = parentCommitErr
39+
break
40+
}
41+
parentCommit = nil
42+
}
43+
44+
// Fetch the trees of the current and parent commits
45+
currentTree, currTreeErr := c.currentCommit.Tree()
46+
if currTreeErr != nil {
47+
err = currTreeErr
48+
break
49+
}
50+
51+
var parentTree *Tree
52+
if parentCommit != nil {
53+
var parentTreeErr error
54+
parentTree, parentTreeErr = parentCommit.Tree()
55+
if parentTreeErr != nil {
56+
err = parentTreeErr
57+
break
58+
}
59+
}
60+
61+
// Find diff between current and parent trees
62+
changes, diffErr := DiffTree(currentTree, parentTree)
63+
if diffErr != nil {
64+
err = diffErr
65+
break
66+
}
67+
68+
foundChangeForFile := false
69+
for _, change := range changes {
70+
if change.name() == c.fileName {
71+
foundChangeForFile = true
72+
break
73+
}
74+
}
75+
76+
// Storing the current-commit in-case a change is found, and
77+
// Updating the current-commit for the next-iteration
78+
prevCommit := c.currentCommit
79+
c.currentCommit = parentCommit
80+
81+
if foundChangeForFile == true {
82+
return prevCommit, nil
83+
}
84+
85+
// If there are no more commits to be found, then return with EOF
86+
if parentCommit == nil {
87+
err = io.EOF
88+
break
89+
}
90+
}
91+
92+
// Setting current-commit to nil to prevent unwanted states when errors are raised
93+
c.currentCommit = nil
94+
return nil, err
95+
}
96+
97+
func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
98+
for {
99+
commit, nextErr := c.Next()
100+
if nextErr != nil {
101+
return nextErr
102+
}
103+
err := cb(commit)
104+
if err == storer.ErrStop {
105+
return nil
106+
} else if err != nil {
107+
return err
108+
}
109+
}
110+
}
111+
112+
func (c *commitFileIter) Close() {
113+
c.sourceIter.Close()
114+
}

repository.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -965,19 +965,26 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
965965
return nil, err
966966
}
967967

968+
var commitIter object.CommitIter
968969
switch o.Order {
969970
case LogOrderDefault:
970-
return object.NewCommitPreorderIter(commit, nil, nil), nil
971+
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
971972
case LogOrderDFS:
972-
return object.NewCommitPreorderIter(commit, nil, nil), nil
973+
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
973974
case LogOrderDFSPost:
974-
return object.NewCommitPostorderIter(commit, nil), nil
975+
commitIter = object.NewCommitPostorderIter(commit, nil)
975976
case LogOrderBSF:
976-
return object.NewCommitIterBSF(commit, nil, nil), nil
977+
commitIter = object.NewCommitIterBSF(commit, nil, nil)
977978
case LogOrderCommitterTime:
978-
return object.NewCommitIterCTime(commit, nil, nil), nil
979+
commitIter = object.NewCommitIterCTime(commit, nil, nil)
980+
default:
981+
return nil, fmt.Errorf("invalid Order=%v", o.Order)
982+
}
983+
984+
if o.FileName == nil {
985+
return commitIter, nil
979986
}
980-
return nil, fmt.Errorf("invalid Order=%v", o.Order)
987+
return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil
981988
}
982989

983990
// Tags returns all the tag References in a repository.

repository_test.go

+139
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,145 @@ func (s *RepositorySuite) TestLogError(c *C) {
11431143
c.Assert(err, NotNil)
11441144
}
11451145

1146+
func (s *RepositorySuite) TestLogFileNext(c *C) {
1147+
r, _ := Init(memory.NewStorage(), nil)
1148+
err := r.clone(context.Background(), &CloneOptions{
1149+
URL: s.GetBasicLocalRepositoryURL(),
1150+
})
1151+
1152+
c.Assert(err, IsNil)
1153+
1154+
fileName := "vendor/foo.go"
1155+
cIter, err := r.Log(&LogOptions{FileName: &fileName})
1156+
1157+
c.Assert(err, IsNil)
1158+
1159+
commitOrder := []plumbing.Hash{
1160+
plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
1161+
}
1162+
1163+
for _, o := range commitOrder {
1164+
commit, err := cIter.Next()
1165+
c.Assert(err, IsNil)
1166+
c.Assert(commit.Hash, Equals, o)
1167+
}
1168+
_, err = cIter.Next()
1169+
c.Assert(err, Equals, io.EOF)
1170+
}
1171+
1172+
func (s *RepositorySuite) TestLogFileForEach(c *C) {
1173+
r, _ := Init(memory.NewStorage(), nil)
1174+
err := r.clone(context.Background(), &CloneOptions{
1175+
URL: s.GetBasicLocalRepositoryURL(),
1176+
})
1177+
1178+
c.Assert(err, IsNil)
1179+
1180+
fileName := "php/crappy.php"
1181+
cIter, err := r.Log(&LogOptions{FileName: &fileName})
1182+
1183+
c.Assert(err, IsNil)
1184+
1185+
commitOrder := []plumbing.Hash{
1186+
plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"),
1187+
}
1188+
1189+
expectedIndex := 0
1190+
cIter.ForEach(func(commit *object.Commit) error {
1191+
expectedCommitHash := commitOrder[expectedIndex]
1192+
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
1193+
expectedIndex += 1
1194+
return nil
1195+
})
1196+
c.Assert(expectedIndex, Equals, 1)
1197+
}
1198+
1199+
func (s *RepositorySuite) TestLogInvalidFile(c *C) {
1200+
r, _ := Init(memory.NewStorage(), nil)
1201+
err := r.clone(context.Background(), &CloneOptions{
1202+
URL: s.GetBasicLocalRepositoryURL(),
1203+
})
1204+
c.Assert(err, IsNil)
1205+
1206+
// Throwing in a file that does not exist
1207+
fileName := "vendor/foo12.go"
1208+
cIter, err := r.Log(&LogOptions{FileName: &fileName})
1209+
// Not raising an error since `git log -- vendor/foo12.go` responds silently
1210+
c.Assert(err, IsNil)
1211+
1212+
_, err = cIter.Next()
1213+
c.Assert(err, Equals, io.EOF)
1214+
}
1215+
1216+
func (s *RepositorySuite) TestLogFileInitialCommit(c *C) {
1217+
r, _ := Init(memory.NewStorage(), nil)
1218+
err := r.clone(context.Background(), &CloneOptions{
1219+
URL: s.GetBasicLocalRepositoryURL(),
1220+
})
1221+
c.Assert(err, IsNil)
1222+
1223+
fileName := "LICENSE"
1224+
cIter, err := r.Log(&LogOptions{
1225+
Order: LogOrderCommitterTime,
1226+
FileName: &fileName,
1227+
})
1228+
1229+
c.Assert(err, IsNil)
1230+
1231+
commitOrder := []plumbing.Hash{
1232+
plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
1233+
}
1234+
1235+
expectedIndex := 0
1236+
cIter.ForEach(func(commit *object.Commit) error {
1237+
expectedCommitHash := commitOrder[expectedIndex]
1238+
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
1239+
expectedIndex += 1
1240+
return nil
1241+
})
1242+
c.Assert(expectedIndex, Equals, 1)
1243+
}
1244+
1245+
func (s *RepositorySuite) TestLogFileWithOtherParamsFail(c *C) {
1246+
r, _ := Init(memory.NewStorage(), nil)
1247+
err := r.clone(context.Background(), &CloneOptions{
1248+
URL: s.GetBasicLocalRepositoryURL(),
1249+
})
1250+
c.Assert(err, IsNil)
1251+
1252+
fileName := "vendor/foo.go"
1253+
cIter, err := r.Log(&LogOptions{
1254+
Order: LogOrderCommitterTime,
1255+
FileName: &fileName,
1256+
From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
1257+
})
1258+
c.Assert(err, IsNil)
1259+
_, iterErr := cIter.Next()
1260+
c.Assert(iterErr, Equals, io.EOF)
1261+
}
1262+
1263+
func (s *RepositorySuite) TestLogFileWithOtherParamsPass(c *C) {
1264+
r, _ := Init(memory.NewStorage(), nil)
1265+
err := r.clone(context.Background(), &CloneOptions{
1266+
URL: s.GetBasicLocalRepositoryURL(),
1267+
})
1268+
c.Assert(err, IsNil)
1269+
1270+
fileName := "LICENSE"
1271+
cIter, err := r.Log(&LogOptions{
1272+
Order: LogOrderCommitterTime,
1273+
FileName: &fileName,
1274+
From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
1275+
})
1276+
c.Assert(err, IsNil)
1277+
commitVal, iterErr := cIter.Next()
1278+
c.Assert(iterErr, Equals, nil)
1279+
c.Assert(commitVal.Hash.String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")
1280+
1281+
_, iterErr = cIter.Next()
1282+
c.Assert(iterErr, Equals, io.EOF)
1283+
}
1284+
11461285
func (s *RepositorySuite) TestCommit(c *C) {
11471286
r, _ := Init(memory.NewStorage(), nil)
11481287
err := r.clone(context.Background(), &CloneOptions{

0 commit comments

Comments
 (0)