@@ -6,6 +6,24 @@ use std::sync::atomic::AtomicBool;
6
6
mod delegate;
7
7
use delegate:: Delegate ;
8
8
9
+ /// The order we maintain for the produced changes.
10
+ #[ derive( Debug , Copy , Clone , Eq , PartialEq ) ]
11
+ pub enum Order {
12
+ /// Compare provided trees or commits without applying any other logic, with the order being influenced by
13
+ /// factors like hashmaps.
14
+ ///
15
+ /// The benefit is mode is the optimal performance as only one diff is created.
16
+ ImplementationDefined ,
17
+ /// If the provided revisions are commits, single step through the history that connects them to maintain
18
+ /// the order in which changes were submitted to the crates-index for all user-defined changes.
19
+ ///
20
+ /// Admin changes are still implementation defined, but typically involve only deletions.
21
+ ///
22
+ /// The shortcomings of this approach is that each pair of commits has to be diffed individually, increasing
23
+ /// the amount of work linearly.
24
+ AsInCratesIndex ,
25
+ }
26
+
9
27
/// The error returned by methods dealing with obtaining index changes.
10
28
#[ derive( Debug , thiserror:: Error ) ]
11
29
#[ allow( missing_docs) ]
@@ -49,9 +67,22 @@ pub enum Error {
49
67
50
68
/// Find changes without modifying the underling repository
51
69
impl Index {
52
- /// As `peek_changes_with_options`, but without the options.
70
+ /// As `peek_changes_with_options() `, but without the options.
53
71
pub fn peek_changes ( & self ) -> Result < ( Vec < Change > , git:: hash:: ObjectId ) , Error > {
54
- self . peek_changes_with_options ( git:: progress:: Discard , & AtomicBool :: default ( ) )
72
+ self . peek_changes_with_options (
73
+ git:: progress:: Discard ,
74
+ & AtomicBool :: default ( ) ,
75
+ Order :: ImplementationDefined ,
76
+ )
77
+ }
78
+
79
+ /// As `peek_changes()` but provides changes similar to those in the crates index.
80
+ pub fn peek_changes_ordered ( & self ) -> Result < ( Vec < Change > , git:: hash:: ObjectId ) , Error > {
81
+ self . peek_changes_with_options (
82
+ git:: progress:: Discard ,
83
+ & AtomicBool :: default ( ) ,
84
+ Order :: ImplementationDefined ,
85
+ )
55
86
}
56
87
57
88
/// Return all `Change`s that are observed between the last time `peek_changes*(…)` was called
@@ -63,6 +94,9 @@ impl Index {
63
94
/// If one would set the `last_seen_reference()` to that object, the effect is exactly the same
64
95
/// as if `fetch_changes(…)` had been called.
65
96
///
97
+ /// The `progress` and `should_interrupt` parameters are used to provide progress for fetches and allow
98
+ /// these operations to be interrupted gracefully.
99
+ ///
66
100
/// # Resource Usage
67
101
///
68
102
/// As this method fetches the git repository, loose objects or small packs may be created. Over time,
@@ -75,6 +109,7 @@ impl Index {
75
109
& self ,
76
110
progress : P ,
77
111
should_interrupt : & AtomicBool ,
112
+ order : Order ,
78
113
) -> Result < ( Vec < Change > , git:: hash:: ObjectId ) , Error >
79
114
where
80
115
P : git:: Progress ,
@@ -159,15 +194,21 @@ impl Index {
159
194
. detach ( )
160
195
} ;
161
196
162
- Ok ( ( self . changes_between_commits ( from, to) ?, to) )
197
+ Ok ( (
198
+ match order {
199
+ Order :: ImplementationDefined => self . changes_between_commits ( from, to) ?,
200
+ Order :: AsInCratesIndex => self . changes_between_ancestor_commits ( from, to) ?. 0 ,
201
+ } ,
202
+ to,
203
+ ) )
163
204
}
164
205
165
206
/// Similar to `changes()`, but requires `from` and `to` objects to be provided. They may point
166
207
/// to either `Commit`s or `Tree`s.
167
208
///
168
209
/// # Returns
169
210
///
170
- /// A list of atomic chanes that were performed on the index
211
+ /// A list of atomic changes that were performed on the index
171
212
/// between the two revisions.
172
213
/// The changes are grouped by the crate they belong to.
173
214
/// The order of the changes for each crate are **non-deterministic**.
@@ -194,13 +235,106 @@ impl Index {
194
235
. for_each_to_obtain_tree ( & to, |change| delegate. handle ( change) ) ?;
195
236
delegate. into_result ( )
196
237
}
238
+
239
+ /// Similar to `changes()`, but requires `ancestor_commit` and `current_commit` objects to be provided
240
+ /// with `ancestor_commit` being in the ancestry of `current_commit`.
241
+ ///
242
+ /// If the invariants regarding `ancestor_commit` and `current_commit` are not upheld, we fallback
243
+ /// to `changes_between_commits()` which doesn't have such restrictions.
244
+ /// This can happen if the crates-index was squashed for instance.
245
+ ///
246
+ /// # Returns
247
+ ///
248
+ /// A list of atomic changes that were performed on the index
249
+ /// between the two revisions, but looking at it one commit at a time, along with the `Order`
250
+ /// that the changes are actually in in case one of the invariants wasn't met.
251
+ pub fn changes_between_ancestor_commits (
252
+ & self ,
253
+ ancestor_commit : impl Into < git:: hash:: ObjectId > ,
254
+ current_commit : impl Into < git:: hash:: ObjectId > ,
255
+ ) -> Result < ( Vec < Change > , Order ) , Error > {
256
+ let from_commit = ancestor_commit. into ( ) ;
257
+ let to_commit = current_commit. into ( ) ;
258
+ match self . commit_ancestry ( from_commit, to_commit) {
259
+ Some ( commits) => {
260
+ let mut changes = Vec :: new ( ) ;
261
+ for from_to in commits. windows ( 2 ) {
262
+ let from = from_to[ 0 ] ;
263
+ let to = from_to[ 1 ] ;
264
+ changes. extend ( self . changes_between_commits ( from, to) ?) ;
265
+ }
266
+ Ok ( ( changes, Order :: AsInCratesIndex ) )
267
+ }
268
+ None => self
269
+ . changes_between_commits ( from_commit, to_commit)
270
+ . map ( |c| ( c, Order :: ImplementationDefined ) ) ,
271
+ }
272
+ }
273
+
274
+ /// Return a list of commits like `from_commit..=to_commits`.
275
+ fn commit_ancestry (
276
+ & self ,
277
+ ancestor_commit : git:: hash:: ObjectId ,
278
+ current_commit : git:: hash:: ObjectId ,
279
+ ) -> Option < Vec < git:: hash:: ObjectId > > {
280
+ let time_in_seconds_since_epoch = ancestor_commit
281
+ . attach ( & self . repo )
282
+ . object ( )
283
+ . ok ( ) ?
284
+ . try_into_commit ( )
285
+ . ok ( ) ?
286
+ . committer ( )
287
+ . ok ( ) ?
288
+ . time
289
+ . seconds_since_unix_epoch ;
290
+ let mut commits = current_commit
291
+ . attach ( & self . repo )
292
+ . ancestors ( )
293
+ . sorting (
294
+ git:: traverse:: commit:: Sorting :: ByCommitTimeNewestFirstCutoffOlderThan {
295
+ time_in_seconds_since_epoch,
296
+ } ,
297
+ )
298
+ . first_parent_only ( )
299
+ . all ( )
300
+ . ok ( ) ?
301
+ . map ( |c| c. map ( |c| c. detach ( ) ) )
302
+ . collect :: < Result < Vec < _ > , _ > > ( )
303
+ . ok ( ) ?;
304
+
305
+ commits. reverse ( ) ;
306
+ if * commits. first ( ) ? != ancestor_commit {
307
+ // try harder, commit resolution is just a second.
308
+ let pos = commits. iter ( ) . position ( |c| * c == ancestor_commit) ?;
309
+ commits = commits[ pos..] . into ( ) ;
310
+ }
311
+ assert_eq ! (
312
+ commits[ commits. len( ) - 1 ] ,
313
+ current_commit,
314
+ "the iterator includes the tips"
315
+ ) ;
316
+ Some ( commits)
317
+ }
197
318
}
198
319
199
320
/// Find changes while changing the underlying repository in one way or another.
200
321
impl Index {
201
- /// As `fetch_changes_with_options`, but without the options.
322
+ /// As `fetch_changes_with_options() `, but without the options.
202
323
pub fn fetch_changes ( & self ) -> Result < Vec < Change > , Error > {
203
- self . fetch_changes_with_options ( git:: progress:: Discard , & AtomicBool :: default ( ) )
324
+ self . fetch_changes_with_options (
325
+ git:: progress:: Discard ,
326
+ & AtomicBool :: default ( ) ,
327
+ Order :: ImplementationDefined ,
328
+ )
329
+ }
330
+
331
+ /// As `fetch_changes()`, but returns an ordered result.
332
+ pub fn fetch_changes_ordered ( & self ) -> Result < Vec < Change > , Error > {
333
+ self . fetch_changes_with_options (
334
+ git:: progress:: Discard ,
335
+ & AtomicBool :: default ( ) ,
336
+ Order :: AsInCratesIndex ,
337
+ )
204
338
}
205
339
206
340
/// Return all `Change`s that are observed between the last time this method was called
@@ -209,6 +343,11 @@ impl Index {
209
343
/// The `last_seen_reference()` will be created or adjusted to point to the latest fetched
210
344
/// state, which causes this method to have a different result each time it is called.
211
345
///
346
+ /// The `progress` and `should_interrupt` parameters are used to provide progress for fetches and allow
347
+ /// these operations to be interrupted gracefully.
348
+ ///
349
+ /// `order` configures how changes should be ordered.
350
+ ///
212
351
/// # Resource Usage
213
352
///
214
353
/// As this method fetches the git repository, loose objects or small packs may be created. Over time,
@@ -220,12 +359,13 @@ impl Index {
220
359
& self ,
221
360
progress : P ,
222
361
should_interrupt : & AtomicBool ,
362
+ order : Order ,
223
363
) -> Result < Vec < Change > , Error >
224
364
where
225
365
P : git:: Progress ,
226
366
P :: SubProgress : ' static ,
227
367
{
228
- let ( changes, to) = self . peek_changes_with_options ( progress, should_interrupt) ?;
368
+ let ( changes, to) = self . peek_changes_with_options ( progress, should_interrupt, order ) ?;
229
369
self . set_last_seen_reference ( to) ?;
230
370
Ok ( changes)
231
371
}
0 commit comments