@@ -4,9 +4,10 @@ use anyhow::{Context, Result};
4
4
use byte_unit:: Byte ;
5
5
use git2:: Time ;
6
6
use git2:: {
7
- BranchType , Commit , Repository , RepositoryOpenFlags , Signature , Status , StatusOptions ,
8
- StatusShow ,
7
+ BranchType , Repository , RepositoryOpenFlags , Signature , Status , StatusOptions , StatusShow ,
9
8
} ;
9
+ use git_repository as git;
10
+ use git_repository:: bstr:: ByteSlice ;
10
11
use regex:: Regex ;
11
12
use std:: collections:: HashMap ;
12
13
use std:: path:: Path ;
@@ -16,8 +17,9 @@ use time::OffsetDateTime;
16
17
use time_humanize:: HumanTime ;
17
18
18
19
pub struct Repo < ' a > {
19
- repo : & ' a Repository ,
20
- logs : Vec < Commit < ' a > > ,
20
+ git2_repo : & ' a Repository ,
21
+ repo : git:: Repository ,
22
+ commits : Vec < git:: DetachedObject > ,
21
23
}
22
24
23
25
#[ derive( Hash , PartialEq , Eq ) ]
@@ -36,52 +38,68 @@ impl From<Signature<'_>> for Sig {
36
38
37
39
impl < ' a > Repo < ' a > {
38
40
pub fn new (
39
- repo : & ' a Repository ,
41
+ git2_repo : & ' a Repository ,
40
42
no_merges : bool ,
41
43
bot_regex_pattern : & Option < Regex > ,
42
44
) -> Result < Self > {
43
- let logs = Self :: get_logs ( repo, no_merges, bot_regex_pattern) ?;
44
- Ok ( Self { repo, logs } )
45
+ let mut repo = git:: open ( git2_repo. path ( ) ) ?;
46
+ let logs = Self :: get_logs ( & mut repo, no_merges, bot_regex_pattern) ?;
47
+ Ok ( Self {
48
+ git2_repo,
49
+ repo,
50
+ commits : logs,
51
+ } )
45
52
}
46
53
54
+ // TODO: avoid allocating/copying buffers. Instead gather the desired information
55
+ // during traversal and keep only author names for processing.
47
56
fn get_logs (
48
- repo : & ' a Repository ,
57
+ repo : & mut git :: Repository ,
49
58
no_merges : bool ,
50
59
bot_regex_pattern : & Option < Regex > ,
51
- ) -> Result < Vec < Commit < ' a > > > {
52
- let mut revwalk = repo. revwalk ( ) ?;
53
- revwalk. push_head ( ) ?;
54
- let logs: Vec < Commit < ' a > > = revwalk
55
- . filter_map ( |r| match r {
56
- Err ( _) => None ,
57
- Ok ( r) => repo
58
- . find_commit ( r)
59
- . ok ( )
60
- . filter ( |commit| !( no_merges && commit. parents ( ) . len ( ) > 1 ) )
61
- . filter ( |commit| {
62
- !( bot_regex_pattern. is_some ( ) && is_bot ( commit. author ( ) , bot_regex_pattern) )
63
- } ) ,
60
+ ) -> Result < Vec < git:: DetachedObject > > {
61
+ // assure that objects we just traversed are coming from cache
62
+ // when we read the commit right after.
63
+ repo. object_cache_size ( 128 * 1024 ) ;
64
+ let commits = repo
65
+ . head ( ) ?
66
+ . peel_to_commit_in_place ( ) ?
67
+ . ancestors ( )
68
+ . all ( )
69
+ . filter_map ( |commit_id| {
70
+ let commit: git:: Commit = commit_id
71
+ . ok ( ) ?
72
+ . object ( )
73
+ . expect ( "commit is still present/comes from cache" )
74
+ . into_commit ( ) ;
75
+ if no_merges && commit. parent_ids ( ) . take ( 2 ) . count ( ) > 1 {
76
+ return None ;
77
+ }
78
+ if is_bot ( commit. iter ( ) . author ( ) , bot_regex_pattern) {
79
+ return None ;
80
+ }
81
+ commit. detach ( ) . into ( )
64
82
} )
65
83
. collect ( ) ;
66
-
67
- Ok ( logs)
84
+ Ok ( commits)
68
85
}
69
86
70
87
pub fn get_creation_date ( & self , iso_time : bool ) -> Result < String > {
71
- let first_commit = self . logs . last ( ) ;
72
- let output = match first_commit {
73
- Some ( commit) => {
74
- let time = commit. time ( ) ;
75
- git_time_to_formatted_time ( & time, iso_time)
76
- }
77
- None => "" . into ( ) ,
78
- } ;
79
-
80
- Ok ( output)
88
+ let first_commit = self . commits . last ( ) ;
89
+ todo ! ( )
90
+ // let output = match first_commit {
91
+ // Some(commit) => {
92
+ // let time = commit.time();
93
+ // git_time_to_formatted_time(&time, iso_time)
94
+ // }
95
+ // None => "".into(),
96
+ // };
97
+ //
98
+ // Ok(output)
81
99
}
82
100
83
101
pub fn get_number_of_commits ( & self ) -> String {
84
- let number_of_commits = self . logs . len ( ) ;
102
+ let number_of_commits = self . commits . len ( ) ;
85
103
number_of_commits. to_string ( )
86
104
}
87
105
@@ -92,16 +110,16 @@ impl<'a> Repo<'a> {
92
110
) -> Result < ( Vec < Author > , usize ) > {
93
111
let mut author_to_number_of_commits: HashMap < Sig , usize > = HashMap :: new ( ) ;
94
112
let mut total_nbr_of_commits = 0 ;
95
- let mailmap = self . repo . mailmap ( ) ?;
96
- for commit in & self . logs {
97
- let author = match commit. author_with_mailmap ( & mailmap) {
98
- Ok ( val) => val,
99
- Err ( _) => commit. author ( ) ,
100
- } ;
101
- let author_nbr_of_commits = author_to_number_of_commits
102
- . entry ( Sig :: from ( author) )
103
- . or_insert ( 0 ) ;
104
- * author_nbr_of_commits += 1 ;
113
+ let mailmap = self . git2_repo . mailmap ( ) ?;
114
+ for commit in & self . commits {
115
+ // let author = match commit.author_with_mailmap(&mailmap) {
116
+ // Ok(val) => val,
117
+ // Err(_) => commit.author(),
118
+ // };
119
+ // let author_nbr_of_commits = author_to_number_of_commits
120
+ // .entry(Sig::from(author))
121
+ // .or_insert(0);
122
+ // *author_nbr_of_commits += 1;
105
123
total_nbr_of_commits += 1 ;
106
124
}
107
125
@@ -132,17 +150,18 @@ impl<'a> Repo<'a> {
132
150
}
133
151
134
152
pub fn get_date_of_last_commit ( & self , iso_time : bool ) -> String {
135
- let last_commit = self . logs . first ( ) ;
153
+ let last_commit = self . commits . first ( ) ;
136
154
137
- match last_commit {
138
- Some ( commit) => git_time_to_formatted_time ( & commit. time ( ) , iso_time) ,
139
- None => "" . into ( ) ,
140
- }
155
+ todo ! ( )
156
+ // match last_commit {
157
+ // Some(commit) => git_time_to_formatted_time(&commit.time(), iso_time),
158
+ // None => "".into(),
159
+ // }
141
160
}
142
161
143
162
// This collects the repo size excluding .git
144
163
pub fn get_repo_size ( & self ) -> ( String , u64 ) {
145
- let ( repo_size, file_count) = match self . repo . index ( ) {
164
+ let ( repo_size, file_count) = match self . git2_repo . index ( ) {
146
165
Ok ( index) => index. iter ( ) . fold (
147
166
( 0 , 0 ) ,
148
167
|( repo_size, file_count) : ( u128 , u64 ) , index_entry| -> ( u128 , u64 ) {
@@ -164,11 +183,11 @@ impl<'a> Repo<'a> {
164
183
}
165
184
166
185
pub fn get_number_of_tags ( & self ) -> Result < usize > {
167
- Ok ( self . repo . tag_names ( None ) ?. len ( ) )
186
+ Ok ( self . git2_repo . tag_names ( None ) ?. len ( ) )
168
187
}
169
188
170
189
pub fn get_number_of_branches ( & self ) -> Result < usize > {
171
- let mut number_of_branches = self . repo . branches ( Some ( BranchType :: Remote ) ) ?. count ( ) ;
190
+ let mut number_of_branches = self . git2_repo . branches ( Some ( BranchType :: Remote ) ) ?. count ( ) ;
172
191
if number_of_branches > 0 {
173
192
//Exclude origin/HEAD -> origin/main
174
193
number_of_branches -= 1 ;
@@ -177,7 +196,7 @@ impl<'a> Repo<'a> {
177
196
}
178
197
179
198
pub fn get_git_username ( & self ) -> Result < String > {
180
- let config = self . repo . config ( ) ?;
199
+ let config = self . git2_repo . config ( ) ?;
181
200
let username = match config. get_entry ( "user.name" ) {
182
201
Ok ( v) => v. value ( ) . unwrap_or ( "" ) . into ( ) ,
183
202
Err ( _) => "" . into ( ) ,
@@ -190,14 +209,14 @@ impl<'a> Repo<'a> {
190
209
let mut version_name = String :: new ( ) ;
191
210
let mut most_recent: i64 = 0 ;
192
211
193
- self . repo . tag_foreach ( |id, name| {
212
+ self . git2_repo . tag_foreach ( |id, name| {
194
213
if let Ok ( name) = String :: from_utf8 ( name[ 10 ..] . into ( ) ) {
195
214
let mut current_time: i64 = 0 ;
196
- if let Ok ( tag) = self . repo . find_tag ( id) {
197
- if let Ok ( c) = self . repo . find_commit ( tag. target_id ( ) ) {
215
+ if let Ok ( tag) = self . git2_repo . find_tag ( id) {
216
+ if let Ok ( c) = self . git2_repo . find_commit ( tag. target_id ( ) ) {
198
217
current_time = c. time ( ) . seconds ( ) ;
199
218
}
200
- } else if let Ok ( c) = self . repo . find_commit ( id) {
219
+ } else if let Ok ( c) = self . git2_repo . find_commit ( id) {
201
220
current_time = c. time ( ) . seconds ( ) ;
202
221
}
203
222
if current_time > most_recent {
@@ -214,7 +233,7 @@ impl<'a> Repo<'a> {
214
233
}
215
234
216
235
pub fn get_pending_changes ( & self ) -> Result < String > {
217
- let statuses = self . repo . statuses ( Some (
236
+ let statuses = self . git2_repo . statuses ( Some (
218
237
StatusOptions :: default ( )
219
238
. show ( StatusShow :: Workdir )
220
239
. update_index ( true )
@@ -254,7 +273,7 @@ impl<'a> Repo<'a> {
254
273
}
255
274
256
275
pub fn get_name_and_url ( & self ) -> Result < ( String , String ) > {
257
- let config = self . repo . config ( ) ?;
276
+ let config = self . git2_repo . config ( ) ?;
258
277
let mut remote_origin_url: Option < String > = None ;
259
278
let mut remote_url_fallback = String :: new ( ) ;
260
279
let mut repository_name = String :: new ( ) ;
@@ -304,9 +323,9 @@ impl<'a> Repo<'a> {
304
323
}
305
324
306
325
pub fn get_head_refs ( & self ) -> Result < HeadRefs > {
307
- let head = self . repo . head ( ) ?;
326
+ let head = self . git2_repo . head ( ) ?;
308
327
let head_oid = head. target ( ) . with_context ( || "Could not read HEAD" ) ?;
309
- let refs = self . repo . references ( ) ?;
328
+ let refs = self . git2_repo . references ( ) ?;
310
329
let refs_info = refs
311
330
. filter_map ( |reference| match reference {
312
331
Ok ( reference) => match ( reference. target ( ) , reference. shorthand ( ) ) {
@@ -322,15 +341,20 @@ impl<'a> Repo<'a> {
322
341
}
323
342
324
343
fn work_dir ( & self ) -> Result < & Path > {
325
- self . repo
344
+ self . git2_repo
326
345
. workdir ( )
327
346
. with_context ( || "unable to query workdir" )
328
347
}
329
348
}
330
349
331
- fn is_bot ( author : Signature , bot_regex_pattern : & Option < Regex > ) -> bool {
332
- let author_name = String :: from_utf8_lossy ( author. name_bytes ( ) ) . into_owned ( ) ;
333
- bot_regex_pattern. as_ref ( ) . unwrap ( ) . is_match ( & author_name)
350
+ fn is_bot ( author : Option < git:: actor:: SignatureRef < ' _ > > , bot_regex_pattern : & Option < Regex > ) -> bool {
351
+ author
352
+ . and_then ( |author| {
353
+ bot_regex_pattern
354
+ . as_ref ( )
355
+ . map ( |regex| regex. is_match ( author. name . to_str_lossy ( ) . as_ref ( ) ) )
356
+ } )
357
+ . unwrap_or ( false )
334
358
}
335
359
336
360
fn bytes_to_human_readable ( bytes : u128 ) -> String {
0 commit comments