@@ -4,9 +4,10 @@ pub struct Options {
4
4
/// If true, also show attributes
5
5
pub attributes : Option < Attributes > ,
6
6
pub statistics : bool ,
7
+ pub simple : bool ,
7
8
}
8
9
9
- #[ derive( Debug ) ]
10
+ #[ derive( Debug , Copy , Clone ) ]
10
11
pub enum Attributes {
11
12
/// Look at worktree attributes and index as fallback.
12
13
WorktreeAndIndex ,
@@ -15,6 +16,7 @@ pub enum Attributes {
15
16
}
16
17
17
18
pub ( crate ) mod function {
19
+ use std:: collections:: BTreeSet ;
18
20
use std:: {
19
21
borrow:: Cow ,
20
22
io:: { BufWriter , Write } ,
@@ -26,9 +28,11 @@ pub(crate) mod function {
26
28
27
29
pub fn entries (
28
30
repo : gix:: Repository ,
31
+ pathspecs : Vec < gix:: pathspec:: Pattern > ,
29
32
out : impl std:: io:: Write ,
30
33
mut err : impl std:: io:: Write ,
31
34
Options {
35
+ simple,
32
36
format,
33
37
attributes,
34
38
statistics,
@@ -37,6 +41,12 @@ pub(crate) mod function {
37
41
use crate :: OutputFormat :: * ;
38
42
let index = repo. index_or_load_from_head ( ) ?;
39
43
let mut cache = attributes
44
+ . or_else ( || {
45
+ pathspecs
46
+ . iter ( )
47
+ . any ( |spec| !spec. attributes . is_empty ( ) )
48
+ . then_some ( Attributes :: Index )
49
+ } )
40
50
. map ( |attrs| {
41
51
repo. attributes (
42
52
& index,
@@ -70,52 +80,111 @@ pub(crate) mod function {
70
80
..Default :: default ( )
71
81
} ;
72
82
73
- let mut out = BufWriter :: new ( out) ;
83
+ let mut out = BufWriter :: with_capacity ( 64 * 1024 , out) ;
74
84
#[ cfg( feature = "serde" ) ]
75
85
if let Json = format {
76
86
out. write_all ( b"[\n " ) ?;
77
87
}
78
- let mut entries = index. entries ( ) . iter ( ) . peekable ( ) ;
79
- while let Some ( entry) = entries. next ( ) {
80
- let attrs = cache
81
- . as_mut ( )
82
- . map ( |( attrs, cache) | {
83
- cache
84
- . at_entry ( entry. path ( & index) , None , |id, buf| repo. objects . find_blob ( id, buf) )
85
- . map ( |entry| {
86
- let is_excluded = entry. is_excluded ( ) ;
87
- stats. excluded += usize:: from ( is_excluded) ;
88
- let attributes: Vec < _ > = {
89
- entry. matching_attributes ( attrs) ;
90
- attrs. iter ( ) . map ( |m| m. assignment . to_owned ( ) ) . collect ( )
91
- } ;
92
- stats. with_attributes += usize:: from ( !attributes. is_empty ( ) ) ;
93
- Attrs {
94
- is_excluded,
95
- attributes,
96
- }
88
+ let mut search = gix:: pathspec:: Search :: from_specs (
89
+ pathspecs,
90
+ repo. prefix ( ) ?. as_deref ( ) ,
91
+ gix:: path:: realpath ( repo. work_dir ( ) . unwrap_or_else ( || repo. git_dir ( ) ) ) ?. as_ref ( ) , // TODO(pathspec): this setup needs `gix`.
92
+ ) ?;
93
+ let mut all_attrs = statistics. then ( BTreeSet :: new) ;
94
+ if let Some ( entries) = index. prefixed_entries ( search. common_prefix ( ) ) {
95
+ stats. entries_after_prune = entries. len ( ) ;
96
+ let mut entries = entries. iter ( ) . peekable ( ) ;
97
+ while let Some ( entry) = entries. next ( ) {
98
+ let mut last_match = None ;
99
+ let attrs = cache
100
+ . as_mut ( )
101
+ . and_then ( |( attrs, cache) | {
102
+ // If the user wants to see assigned attributes, we always have to match.
103
+ attributes. is_some ( ) . then ( || {
104
+ cache
105
+ . at_entry ( entry. path ( & index) , None , |id, buf| repo. objects . find_blob ( id, buf) )
106
+ . map ( |entry| {
107
+ let is_excluded = entry. is_excluded ( ) ;
108
+ stats. excluded += usize:: from ( is_excluded) ;
109
+ let attributes: Vec < _ > = {
110
+ last_match = Some ( entry. matching_attributes ( attrs) ) ;
111
+ attrs. iter ( ) . map ( |m| m. assignment . to_owned ( ) ) . collect ( )
112
+ } ;
113
+ stats. with_attributes += usize:: from ( !attributes. is_empty ( ) ) ;
114
+ stats. max_attributes_per_path = stats. max_attributes_per_path . max ( attributes. len ( ) ) ;
115
+ if let Some ( attrs) = all_attrs. as_mut ( ) {
116
+ attributes. iter ( ) . for_each ( |attr| {
117
+ attrs. insert ( attr. clone ( ) ) ;
118
+ } ) ;
119
+ }
120
+ Attrs {
121
+ is_excluded,
122
+ attributes,
123
+ }
124
+ } )
97
125
} )
98
- } )
99
- . transpose ( ) ?;
100
- match format {
101
- Human => to_human ( & mut out, & index, entry, attrs) ?,
102
- #[ cfg( feature = "serde" ) ]
103
- Json => to_json ( & mut out, & index, entry, attrs, entries. peek ( ) . is_none ( ) ) ?,
126
+ } )
127
+ . transpose ( ) ?;
128
+
129
+ // Note that we intentionally ignore `_case` so that we act like git does, attribute matching case is determined
130
+ // by the repository, not the pathspec.
131
+ if search
132
+ . pattern_matching_relative_path ( entry. path ( & index) , Some ( false ) , |rela_path, _case, is_dir, out| {
133
+ cache
134
+ . as_mut ( )
135
+ . map ( |( attrs, cache) | {
136
+ match last_match {
137
+ // The user wants the attributes for display, so the match happened already.
138
+ Some ( matched) => {
139
+ attrs. copy_into ( cache. attributes_collection ( ) , out) ;
140
+ matched
141
+ }
142
+ // The user doesn't want attributes, so we set the cache position on demand only
143
+ None => cache
144
+ . at_entry ( rela_path, Some ( is_dir) , |id, buf| repo. objects . find_blob ( id, buf) )
145
+ . ok ( )
146
+ . map ( |platform| platform. matching_attributes ( out) )
147
+ . unwrap_or_default ( ) ,
148
+ }
149
+ } )
150
+ . unwrap_or_default ( )
151
+ } )
152
+ . map_or ( true , |m| m. is_excluded ( ) )
153
+ {
154
+ continue ;
155
+ }
156
+ match format {
157
+ Human => {
158
+ if simple {
159
+ to_human_simple ( & mut out, & index, entry, attrs)
160
+ } else {
161
+ to_human ( & mut out, & index, entry, attrs)
162
+ } ?
163
+ }
164
+ #[ cfg( feature = "serde" ) ]
165
+ Json => to_json ( & mut out, & index, entry, attrs, entries. peek ( ) . is_none ( ) ) ?,
166
+ }
104
167
}
105
- }
106
168
107
- #[ cfg( feature = "serde" ) ]
108
- if format == Json {
109
- out. write_all ( b"]\n " ) ?;
110
- out. flush ( ) ?;
111
- if statistics {
112
- serde_json:: to_writer_pretty ( & mut err, & stats) ?;
169
+ #[ cfg( feature = "serde" ) ]
170
+ if format == Json {
171
+ out. write_all ( b"]\n " ) ?;
172
+ out. flush ( ) ?;
173
+ if statistics {
174
+ serde_json:: to_writer_pretty ( & mut err, & stats) ?;
175
+ }
176
+ }
177
+ if format == Human && statistics {
178
+ out. flush ( ) ?;
179
+ stats. cache = cache. map ( |c| * c. 1 . statistics ( ) ) ;
180
+ writeln ! ( err, "{stats:#?}" ) ?;
181
+ if let Some ( attrs) = all_attrs. filter ( |a| !a. is_empty ( ) ) {
182
+ writeln ! ( err, "All encountered attributes:" ) ?;
183
+ for attr in attrs {
184
+ writeln ! ( err, "\t {attr}" , attr = attr. as_ref( ) ) ?;
185
+ }
186
+ }
113
187
}
114
- }
115
- if format == Human && statistics {
116
- out. flush ( ) ?;
117
- stats. cache = cache. map ( |c| * c. 1 . statistics ( ) ) ;
118
- writeln ! ( err, "{stats:#?}" ) ?;
119
188
}
120
189
Ok ( ( ) )
121
190
}
@@ -131,8 +200,10 @@ pub(crate) mod function {
131
200
struct Statistics {
132
201
#[ allow( dead_code) ] // Not really dead, but Debug doesn't count for it even though it's crucial.
133
202
pub entries : usize ,
203
+ pub entries_after_prune : usize ,
134
204
pub excluded : usize ,
135
205
pub with_attributes : usize ,
206
+ pub max_attributes_per_path : usize ,
136
207
pub cache : Option < gix:: worktree:: cache:: Statistics > ,
137
208
}
138
209
@@ -175,6 +246,22 @@ pub(crate) mod function {
175
246
Ok ( ( ) )
176
247
}
177
248
249
+ fn to_human_simple (
250
+ out : & mut impl std:: io:: Write ,
251
+ file : & gix:: index:: File ,
252
+ entry : & gix:: index:: Entry ,
253
+ attrs : Option < Attrs > ,
254
+ ) -> std:: io:: Result < ( ) > {
255
+ match attrs {
256
+ Some ( attrs) => {
257
+ out. write_all ( entry. path ( file) ) ?;
258
+ out. write_all ( print_attrs ( Some ( attrs) ) . as_bytes ( ) )
259
+ }
260
+ None => out. write_all ( entry. path ( file) ) ,
261
+ } ?;
262
+ out. write_all ( b"\n " )
263
+ }
264
+
178
265
fn to_human (
179
266
out : & mut impl std:: io:: Write ,
180
267
file : & gix:: index:: File ,
@@ -198,24 +285,28 @@ pub(crate) mod function {
198
285
entry. mode,
199
286
entry. id,
200
287
entry. path( file) ,
201
- attrs. map_or( Cow :: Borrowed ( "" ) , |a| {
202
- let mut buf = String :: new( ) ;
203
- if a. is_excluded {
204
- buf. push_str( " ❌" ) ;
205
- }
206
- if !a. attributes. is_empty( ) {
207
- buf. push_str( " (" ) ;
208
- for assignment in a. attributes {
209
- use std:: fmt:: Write ;
210
- write!( & mut buf, "{}" , assignment. as_ref( ) ) . ok( ) ;
211
- buf. push_str( ", " ) ;
212
- }
213
- buf. pop( ) ;
214
- buf. pop( ) ;
215
- buf. push( ')' ) ;
216
- }
217
- buf. into( )
218
- } )
288
+ print_attrs( attrs)
219
289
)
220
290
}
291
+
292
+ fn print_attrs ( attrs : Option < Attrs > ) -> Cow < ' static , str > {
293
+ attrs. map_or ( Cow :: Borrowed ( "" ) , |a| {
294
+ let mut buf = String :: new ( ) ;
295
+ if a. is_excluded {
296
+ buf. push_str ( " ❌" ) ;
297
+ }
298
+ if !a. attributes . is_empty ( ) {
299
+ buf. push_str ( " (" ) ;
300
+ for assignment in a. attributes {
301
+ use std:: fmt:: Write ;
302
+ write ! ( & mut buf, "{}" , assignment. as_ref( ) ) . ok ( ) ;
303
+ buf. push_str ( ", " ) ;
304
+ }
305
+ buf. pop ( ) ;
306
+ buf. pop ( ) ;
307
+ buf. push ( ')' ) ;
308
+ }
309
+ buf. into ( )
310
+ } )
311
+ }
221
312
}
0 commit comments