11package  git
22
33import  (
4- 	"bufio " 
4+ 	"bytes " 
55	"context" 
66	"fmt" 
7+ 	"iter" 
78	"strconv" 
89	"strings" 
910	"time" 
1011)
1112
1213// ListCommits returns a list of commits matched by the given range. 
13- func  (r  * Repository ) ListCommits (ctx  context.Context , commits  CommitRange ) ([]Hash , error ) {
14- 	lines , err  :=  r .listCommitsFormat (ctx , commits , "" )
15- 	if  err  !=  nil  {
16- 		return  nil , err 
17- 	}
14+ func  (r  * Repository ) ListCommits (ctx  context.Context , commits  CommitRange ) iter.Seq2 [Hash , error ] {
15+ 	return  func (yield  func (Hash , error ) bool ) {
16+ 		for  line , err  :=  range  r .listCommitsFormat (ctx , commits , "" ) {
17+ 			if  err  !=  nil  {
18+ 				yield (Hash ("" ), err )
19+ 				return 
20+ 			}
1821
19- 	hashes  :=  make ([]Hash , len (lines ))
20- 	for  i , line  :=  range  lines  {
21- 		hashes [i ] =  Hash (line )
22+ 			if  ! yield (Hash (line ), nil ) {
23+ 				return 
24+ 			}
25+ 		}
2226	}
23- 
24- 	return  hashes , nil 
2527}
2628
2729// CommitDetail contains information about a commit. 
@@ -44,103 +46,96 @@ func (cd *CommitDetail) String() string {
4446}
4547
4648// ListCommitsDetails returns details about commits matched by the given range. 
47- func  (r  * Repository ) ListCommitsDetails (ctx  context.Context , commits  CommitRange ) ([]CommitDetail , error ) {
48- 	lines , err  :=  r .listCommitsFormat (ctx , commits , "%H %h %at %s" )
49- 	if  err  !=  nil  {
50- 		return  nil , err 
51- 	}
49+ func  (r  * Repository ) ListCommitsDetails (ctx  context.Context , commits  CommitRange ) iter.Seq2 [CommitDetail , error ] {
50+ 	return  func (yield  func (CommitDetail , error ) bool ) {
51+ 		for  line , err  :=  range  r .listCommitsFormat (ctx , commits , "%H %h %at %s" ) {
52+ 			if  err  !=  nil  {
53+ 				yield (CommitDetail {}, err )
54+ 				return 
55+ 			}
5256
53- 	details  :=  make ([]CommitDetail , len (lines ))
54- 	for  i , line  :=  range  lines  {
55- 		hash , line , ok  :=  strings .Cut (line , " " )
56- 		if  ! ok  {
57- 			r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing a hash" )
58- 			continue 
59- 		}
57+ 			hash , line , ok  :=  strings .Cut (line , " " )
58+ 			if  ! ok  {
59+ 				r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing a hash" )
60+ 				continue 
61+ 			}
6062
61- 		shortHash , line , ok  :=  strings .Cut (line , " " )
62- 		if  ! ok  {
63- 			r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing a short hash" )
64- 			continue 
65- 		}
63+ 			 shortHash , line , ok  :=  strings .Cut (line , " " )
64+ 			 if  ! ok  {
65+ 				 r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing a short hash" )
66+ 				 continue 
67+ 			 }
6668
67- 		epochstr , subject , ok  :=  strings .Cut (line , " " )
68- 		if  ! ok  {
69- 			r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing an time" )
70- 			continue 
71- 		}
72- 		epoch , err  :=  strconv .ParseInt (epochstr , 10 , 64 )
73- 		if  err  !=  nil  {
74- 			r .log .Warn ("Bad rev-list output" , "line" , line , "error" , err )
75- 			continue 
76- 		}
69+ 			 epochstr , subject , ok  :=  strings .Cut (line , " " )
70+ 			 if  ! ok  {
71+ 				 r .log .Warn ("Bad rev-list output" , "line" , line , "error" , "missing an time" )
72+ 				 continue 
73+ 			 }
74+ 			 epoch , err  :=  strconv .ParseInt (epochstr , 10 , 64 )
75+ 			 if  err  !=  nil  {
76+ 				 r .log .Warn ("Bad rev-list output" , "line" , line , "error" , err )
77+ 				 continue 
78+ 			 }
7779
78- 		details [i ] =  CommitDetail {
79- 			Hash :       Hash (hash ),
80- 			ShortHash :  Hash (shortHash ),
81- 			Subject :    subject ,
82- 			AuthorDate : time .Unix (epoch , 0 ),
80+ 			if  ! yield (CommitDetail {
81+ 				Hash :       Hash (hash ),
82+ 				ShortHash :  Hash (shortHash ),
83+ 				Subject :    subject ,
84+ 				AuthorDate : time .Unix (epoch , 0 ),
85+ 			}, nil ) {
86+ 				return 
87+ 			}
8388		}
8489	}
85- 
86- 	return  details , nil 
8790}
8891
8992// ListCommitsFormat lists commits matched by the given range, 
9093// formatted according to the given format string. 
9194// 
9295// See git-log(1) for details on the format string. 
93- func  (r  * Repository ) listCommitsFormat (ctx  context.Context , commits  CommitRange , format  string ) ([] string , error )  {
96+ func  (r  * Repository ) listCommitsFormat (ctx  context.Context , commits  CommitRange , format  string ) iter. Seq2 [ string , error ]  {
9497	args  :=  make ([]string , 0 , len (commits )+ 3 )
9598	args  =  append (args , "rev-list" )
9699	if  format  !=  ""  {
97100		args  =  append (args , "--format=" + format )
98101	}
99102	args  =  append (args , []string (commits )... )
100103
101- 	cmd  :=  r .gitCmd (ctx , args ... )
102- 	out , err  :=  cmd .StdoutPipe ()
103- 	if  err  !=  nil  {
104- 		return  nil , err 
105- 	}
106- 
107- 	if  err  :=  cmd .Start (r .exec ); err  !=  nil  {
108- 		return  nil , fmt .Errorf ("start rev-list: %w" , err )
109- 	}
104+ 	return  func (yield  func (string , error ) bool ) {
105+ 		cmd  :=  r .gitCmd (ctx , args ... )
110106
111- 	// TODO: Return a string iterator 
112- 	var  lines  []string 
113- 	scanner  :=  bufio .NewScanner (out )
114- 	for  scanner .Scan () {
115- 		line  :=  scanner .Text ()
116- 
117- 		// With --format, rev-list output is in the form: 
118- 		// 
119- 		//    commit <hash> 
120- 		//    <formatted message> 
121- 		// 
122- 		// We'll need to ignore the first line. 
123- 		// 
124- 		// This is a bit of a hack, but the --no-commit-header flag 
125- 		// that suppresses this line is only available in git 2.33+. 
126- 		if  format  !=  ""  &&  strings .HasPrefix (line , "commit " ) {
127- 			if  ! scanner .Scan () {
128- 				break 
107+ 		var  sawCommitHeader  bool 
108+ 		for  bs , err  :=  range  cmd .ScanLines (r .exec ) {
109+ 			if  err  !=  nil  {
110+ 				yield ("" , fmt .Errorf ("git rev-list: %w" , err ))
111+ 				return 
129112			}
130- 		}
131- 
132- 		lines  =  append (lines , scanner .Text ())
133- 	}
134113
135- 	if  err  :=  scanner .Err (); err  !=  nil  {
136- 		return  nil , fmt .Errorf ("scan: %w" , err )
137- 	}
114+ 			// With --format, rev-list output is in the form: 
115+ 			// 
116+ 			//    commit <hash> 
117+ 			//    <formatted message> 
118+ 			// 
119+ 			// We'll need to ignore the first line. 
120+ 			// 
121+ 			// This is a bit of a hack, but the --no-commit-header flag 
122+ 			// that suppresses this line is only available in git 2.33+. 
123+ 			if  format  ==  ""  ||  sawCommitHeader  {
124+ 				sawCommitHeader  =  false 
125+ 				if  ! yield (string (bs ), nil ) {
126+ 					return 
127+ 				}
128+ 				continue 
129+ 			}
138130
139- 	if  err  :=  cmd .Wait (r .exec ); err  !=  nil  {
140- 		return  nil , fmt .Errorf ("rev-list: %w" , err )
131+ 			if  format  !=  ""  &&  ! sawCommitHeader  {
132+ 				if  bytes .HasPrefix (bs , []byte ("commit " )) {
133+ 					sawCommitHeader  =  true 
134+ 					continue 
135+ 				}
136+ 			}
137+ 		}
141138	}
142- 
143- 	return  lines , nil 
144139}
145140
146141// CountCommits reports the number of commits matched by the given range. 
0 commit comments