@@ -4,9 +4,10 @@ pub struct Options {
44 /// If true, also show attributes
55 pub attributes : Option < Attributes > ,
66 pub statistics : bool ,
7+ pub simple : bool ,
78}
89
9- #[ derive( Debug ) ]
10+ #[ derive( Debug , Copy , Clone ) ]
1011pub enum Attributes {
1112 /// Look at worktree attributes and index as fallback.
1213 WorktreeAndIndex ,
@@ -15,6 +16,7 @@ pub enum Attributes {
1516}
1617
1718pub ( crate ) mod function {
19+ use std:: collections:: BTreeSet ;
1820 use std:: {
1921 borrow:: Cow ,
2022 io:: { BufWriter , Write } ,
@@ -26,9 +28,11 @@ pub(crate) mod function {
2628
2729 pub fn entries (
2830 repo : gix:: Repository ,
31+ pathspecs : Vec < gix:: pathspec:: Pattern > ,
2932 out : impl std:: io:: Write ,
3033 mut err : impl std:: io:: Write ,
3134 Options {
35+ simple,
3236 format,
3337 attributes,
3438 statistics,
@@ -37,6 +41,12 @@ pub(crate) mod function {
3741 use crate :: OutputFormat :: * ;
3842 let index = repo. index_or_load_from_head ( ) ?;
3943 let mut cache = attributes
44+ . or_else ( || {
45+ pathspecs
46+ . iter ( )
47+ . any ( |spec| !spec. attributes . is_empty ( ) )
48+ . then_some ( Attributes :: Index )
49+ } )
4050 . map ( |attrs| {
4151 repo. attributes (
4252 & index,
@@ -70,52 +80,111 @@ pub(crate) mod function {
7080 ..Default :: default ( )
7181 } ;
7282
73- let mut out = BufWriter :: new ( out) ;
83+ let mut out = BufWriter :: with_capacity ( 64 * 1024 , out) ;
7484 #[ cfg( feature = "serde" ) ]
7585 if let Json = format {
7686 out. write_all ( b"[\n " ) ?;
7787 }
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+ } )
97125 } )
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+ }
104167 }
105- }
106168
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+ }
113187 }
114- }
115- if format == Human && statistics {
116- out. flush ( ) ?;
117- stats. cache = cache. map ( |c| * c. 1 . statistics ( ) ) ;
118- writeln ! ( err, "{stats:#?}" ) ?;
119188 }
120189 Ok ( ( ) )
121190 }
@@ -131,8 +200,10 @@ pub(crate) mod function {
131200 struct Statistics {
132201 #[ allow( dead_code) ] // Not really dead, but Debug doesn't count for it even though it's crucial.
133202 pub entries : usize ,
203+ pub entries_after_prune : usize ,
134204 pub excluded : usize ,
135205 pub with_attributes : usize ,
206+ pub max_attributes_per_path : usize ,
136207 pub cache : Option < gix:: worktree:: cache:: Statistics > ,
137208 }
138209
@@ -175,6 +246,22 @@ pub(crate) mod function {
175246 Ok ( ( ) )
176247 }
177248
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+
178265 fn to_human (
179266 out : & mut impl std:: io:: Write ,
180267 file : & gix:: index:: File ,
@@ -198,24 +285,28 @@ pub(crate) mod function {
198285 entry. mode,
199286 entry. id,
200287 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)
219289 )
220290 }
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+ }
221312}
0 commit comments