1
- use std:: sync:: atomic:: Ordering ;
1
+ use std:: sync:: atomic:: { AtomicU64 , AtomicUsize , Ordering } ;
2
2
use std:: { io, marker:: PhantomData , path:: Path } ;
3
3
4
4
use bstr:: BStr ;
@@ -10,21 +10,29 @@ use crate::{
10
10
traits,
11
11
traits:: { CompareBlobs , SubmoduleStatus } ,
12
12
types:: { Error , Options } ,
13
- Change , VisitEntry ,
13
+ Change , Outcome , VisitEntry ,
14
14
} ,
15
15
read, Pathspec ,
16
16
} ;
17
17
18
18
/// Calculates the changes that need to be applied to an `index` to match the state of the `worktree` and makes them
19
- /// observable in `collector`, along with information produced by `compare` which gets to see blobs that may have changes.
19
+ /// observable in `collector`, along with information produced by `compare` which gets to see blobs that may have changes, and
20
+ /// `submodule` which can take a look at submodules in detail to produce status information.
20
21
/// `options` are used to configure the operation.
21
22
///
22
23
/// Note that `index` is updated with the latest seen stat information from the worktree, and its timestamp is adjusted to
23
- /// the current time for which it will be considered fresh.
24
+ /// the current time for which it will be considered fresh as long as it is included which depends on `pathspec` .
24
25
///
25
- /// Note that this isn't technically quite what this function does as this also provides some additional information,
26
- /// like whether a file has conflicts, and files that were added with `git add` are shown as a special
27
- /// changes despite not technically requiring a change to the index since `git add` already added the file to the index.
26
+ /// ### Note
27
+ ///
28
+ /// Technically, this function does more as this also provides additional information, like whether a file has conflicts,
29
+ /// and files that were added with `git add` are shown as a special as well. It also updates index entry stats like `git status` would
30
+ /// if it had to determine the hash. If that happened, the index should be written back, see [Outcome::skipped]
31
+ /// The latter is a 'change' that is not technically requiring a change to the index since `git add` already added the
32
+ /// file to the index, but didn't hash it.
33
+ ///
34
+ /// Thus some care has to be taken to do the right thing when letting the index match the worktree by evaluating the changes observed
35
+ /// by the `collector`.
28
36
#[ allow( clippy:: too_many_arguments) ]
29
37
pub fn index_as_worktree < ' index , T , U , Find , E1 , E2 > (
30
38
index : & ' index mut gix_index:: State ,
@@ -36,7 +44,7 @@ pub fn index_as_worktree<'index, T, U, Find, E1, E2>(
36
44
progress : & mut dyn gix_features:: progress:: Progress ,
37
45
pathspec : impl Pathspec + Send + Clone ,
38
46
options : Options ,
39
- ) -> Result < ( ) , Error >
47
+ ) -> Result < Outcome , Error >
40
48
where
41
49
T : Send ,
42
50
U : Send ,
60
68
. prefixed_entries_range ( pathspec. common_prefix ( ) )
61
69
. unwrap_or ( 0 ..index. entries ( ) . len ( ) ) ;
62
70
let ( entries, path_backing) = index. entries_mut_and_pathbacking ( ) ;
71
+ let num_entries = entries. len ( ) ;
63
72
let entries = & mut entries[ range] ;
73
+
74
+ let _span = gix_features:: trace:: detail!( "gix_status::index_as_worktree" ,
75
+ num_entries = entries. len( ) ,
76
+ chunk_size = chunk_size,
77
+ thread_limit = ?thread_limit) ;
78
+
79
+ let entries_skipped_by_common_prefix = num_entries - entries. len ( ) ;
80
+ let ( skipped_by_pathspec, skipped_by_entry_flags, symlink_metadata_calls, entries_updated) = Default :: default ( ) ;
81
+ let ( worktree_bytes, worktree_reads, odb_bytes, odb_reads, racy_clean) = Default :: default ( ) ;
82
+
64
83
progress. init ( entries. len ( ) . into ( ) , gix_features:: progress:: count ( "files" ) ) ;
65
84
let count = progress. counter ( ) ;
66
85
70
89
thread_limit,
71
90
{
72
91
let options = & options;
92
+ let ( skipped_by_pathspec, skipped_by_entry_flags) = ( & skipped_by_pathspec, & skipped_by_entry_flags) ;
93
+ let ( symlink_metadata_calls, entries_updated) = ( & symlink_metadata_calls, & entries_updated) ;
94
+ let ( racy_clean, worktree_bytes) = ( & racy_clean, & worktree_bytes) ;
95
+ let ( worktree_reads, odb_bytes, odb_reads) = ( & worktree_reads, & odb_bytes, & odb_reads) ;
73
96
move |_| {
74
97
(
75
98
State {
@@ -79,6 +102,16 @@ where
79
102
timestamp,
80
103
path_backing,
81
104
options,
105
+
106
+ skipped_by_pathspec,
107
+ skipped_by_entry_flags,
108
+ symlink_metadata_calls,
109
+ entries_updated,
110
+ racy_clean,
111
+ worktree_reads,
112
+ worktree_bytes,
113
+ odb_reads,
114
+ odb_bytes,
82
115
} ,
83
116
compare,
84
117
submodule,
@@ -101,7 +134,20 @@ where
101
134
collector,
102
135
phantom : PhantomData ,
103
136
} ,
104
- )
137
+ ) ?;
138
+
139
+ Ok ( Outcome {
140
+ entries_skipped_by_common_prefix,
141
+ entries_skipped_by_pathspec : skipped_by_pathspec. load ( Ordering :: Relaxed ) ,
142
+ entries_skipped_by_entry_flags : skipped_by_entry_flags. load ( Ordering :: Relaxed ) ,
143
+ entries_updated : entries_updated. load ( Ordering :: Relaxed ) ,
144
+ symlink_metadata_calls : symlink_metadata_calls. load ( Ordering :: Relaxed ) ,
145
+ racy_clean : racy_clean. load ( Ordering :: Relaxed ) ,
146
+ worktree_files_read : worktree_reads. load ( Ordering :: Relaxed ) ,
147
+ worktree_bytes : worktree_bytes. load ( Ordering :: Relaxed ) ,
148
+ odb_objects_read : odb_reads. load ( Ordering :: Relaxed ) ,
149
+ odb_bytes : odb_bytes. load ( Ordering :: Relaxed ) ,
150
+ } )
105
151
}
106
152
107
153
struct State < ' a , ' b > {
@@ -111,6 +157,16 @@ struct State<'a, 'b> {
111
157
path_stack : crate :: SymlinkCheck ,
112
158
path_backing : & ' b [ u8 ] ,
113
159
options : & ' a Options ,
160
+
161
+ skipped_by_pathspec : & ' a AtomicUsize ,
162
+ skipped_by_entry_flags : & ' a AtomicUsize ,
163
+ symlink_metadata_calls : & ' a AtomicUsize ,
164
+ entries_updated : & ' a AtomicUsize ,
165
+ racy_clean : & ' a AtomicUsize ,
166
+ worktree_bytes : & ' a AtomicU64 ,
167
+ worktree_reads : & ' a AtomicUsize ,
168
+ odb_bytes : & ' a AtomicU64 ,
169
+ odb_reads : & ' a AtomicUsize ,
114
170
}
115
171
116
172
type StatusResult < ' index , T , U > = Result < ( & ' index gix_index:: Entry , & ' index BStr , Option < Change < T , U > > , bool ) , Error > ;
@@ -140,10 +196,12 @@ impl<'index> State<'_, 'index> {
140
196
| gix_index:: entry:: Flags :: ASSUME_VALID
141
197
| gix_index:: entry:: Flags :: FSMONITOR_VALID ,
142
198
) {
199
+ self . skipped_by_entry_flags . fetch_add ( 1 , Ordering :: Relaxed ) ;
143
200
return None ;
144
201
}
145
202
let path = entry. path_in ( self . path_backing ) ;
146
203
if !pathspec. is_included ( path, Some ( false ) ) {
204
+ self . skipped_by_pathspec . fetch_add ( 1 , Ordering :: Relaxed ) ;
147
205
return None ;
148
206
}
149
207
let status = self . compute_status ( & mut * entry, path, diff, submodule, find) ;
@@ -208,6 +266,7 @@ impl<'index> State<'_, 'index> {
208
266
Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => return Ok ( Some ( Change :: Removed ) ) ,
209
267
Err ( err) => return Err ( Error :: Io ( err) ) ,
210
268
} ;
269
+ self . symlink_metadata_calls . fetch_add ( 1 , Ordering :: Relaxed ) ;
211
270
let metadata = match worktree_path. symlink_metadata ( ) {
212
271
Ok ( metadata) if metadata. is_dir ( ) => {
213
272
// index entries are normally only for files/symlinks
@@ -265,21 +324,33 @@ impl<'index> State<'_, 'index> {
265
324
racy_clean = new_stat. is_racy ( self . timestamp , self . options . stat ) ;
266
325
if !racy_clean {
267
326
return Ok ( None ) ;
327
+ } else {
328
+ self . racy_clean . fetch_add ( 1 , Ordering :: Relaxed ) ;
268
329
}
269
330
}
270
331
332
+ self . buf . clear ( ) ;
271
333
let read_file = WorktreeBlob {
272
334
buf : & mut self . buf ,
273
335
path : worktree_path,
274
336
entry,
275
337
options : self . options ,
276
338
} ;
339
+ self . odb_buf . clear ( ) ;
277
340
let read_blob = OdbBlob {
278
341
buf : & mut self . odb_buf ,
279
342
id : & entry. id ,
280
343
find,
281
344
} ;
282
345
let content_change = diff. compare_blobs ( entry, metadata. len ( ) as usize , read_file, read_blob) ?;
346
+ if !self . buf . is_empty ( ) {
347
+ self . worktree_reads . fetch_add ( 1 , Ordering :: Relaxed ) ;
348
+ self . worktree_bytes . fetch_add ( self . buf . len ( ) as u64 , Ordering :: Relaxed ) ;
349
+ }
350
+ if !self . odb_buf . is_empty ( ) {
351
+ self . odb_reads . fetch_add ( 1 , Ordering :: Relaxed ) ;
352
+ self . odb_bytes . fetch_add ( self . odb_buf . len ( ) as u64 , Ordering :: Relaxed ) ;
353
+ }
283
354
// This file is racy clean! Set the size to 0 so we keep detecting this as the file is updated.
284
355
if content_change. is_some ( ) && racy_clean {
285
356
entry. stat . size = 0 ;
@@ -292,6 +363,7 @@ impl<'index> State<'_, 'index> {
292
363
} else {
293
364
// don't diff against this file next time since we know the file is unchanged.
294
365
entry. stat = new_stat;
366
+ self . entries_updated . fetch_add ( 1 , Ordering :: Relaxed ) ;
295
367
Ok ( None )
296
368
}
297
369
}
0 commit comments