Skip to content

Commit 895bfd5

Browse files
committed
feat(effects): add with_progress method to iterators
In situations where we `continue` a loop, the naive approach of incrementing the iterator progress at the end of the loop won't work as it'll skip the increment. This extension method aims to improve the ergonomics and correctness of that use-case.
1 parent e2c3abf commit 895bfd5

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

git-branchless-lib/src/core/effects.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1000,7 +1000,7 @@ impl Drop for ProgressHandle<'_> {
10001000
}
10011001
}
10021002

1003-
impl ProgressHandle<'_> {
1003+
impl<'a> ProgressHandle<'a> {
10041004
/// Notify the progress meter that the current operation has `total`
10051005
/// discrete units of work, and it's currently `current` units of the way
10061006
/// through the operation.
@@ -1025,6 +1025,46 @@ impl ProgressHandle<'_> {
10251025
}
10261026
}
10271027

1028+
/// A wrapper around an iterator that reports progress as it iterates.
1029+
pub struct ProgressIter<'a, TItem, TInner: Iterator<Item = TItem>> {
1030+
index: usize,
1031+
inner: TInner,
1032+
progress: ProgressHandle<'a>,
1033+
}
1034+
1035+
impl<TItem, TInner: Iterator<Item = TItem>> Iterator for ProgressIter<'_, TItem, TInner> {
1036+
type Item = TItem;
1037+
1038+
fn next(&mut self) -> Option<Self::Item> {
1039+
let (lower, upper) = self.inner.size_hint();
1040+
let size_guess = upper.unwrap_or(lower);
1041+
self.progress.notify_progress(self.index, size_guess);
1042+
self.index += 1;
1043+
self.inner.next()
1044+
}
1045+
}
1046+
1047+
/// Extension trait for iterators that adds a `with_progress` method.
1048+
pub trait WithProgress<'a, TItem>: Iterator<Item = TItem> {
1049+
/// The type of the iterator returned by `with_progress`.
1050+
type Iter: Iterator<Item = TItem> + 'a;
1051+
1052+
/// Wrap the iterator into an iterator that reports progress as it consumes items.
1053+
fn with_progress(self, progress: ProgressHandle<'a>) -> Self::Iter;
1054+
}
1055+
1056+
impl<'a, TItem: 'a, TIter: Iterator<Item = TItem> + 'a> WithProgress<'a, TItem> for TIter {
1057+
type Iter = ProgressIter<'a, TItem, TIter>;
1058+
1059+
fn with_progress(self, progress: ProgressHandle<'a>) -> Self::Iter {
1060+
ProgressIter {
1061+
index: 0,
1062+
inner: self,
1063+
progress,
1064+
}
1065+
}
1066+
}
1067+
10281068
#[cfg(test)]
10291069
mod tests {
10301070
use super::*;

git-branchless-lib/src/core/rewrite/plan.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rayon::{prelude::*, ThreadPool};
1111
use tracing::{instrument, warn};
1212

1313
use crate::core::dag::{sorted_commit_set, union_all, CommitSet, Dag};
14-
use crate::core::effects::{Effects, OperationType};
14+
use crate::core::effects::{Effects, OperationType, WithProgress};
1515
use crate::core::formatting::Pluralize;
1616
use crate::core::rewrite::{RepoPool, RepoResource};
1717
use crate::core::task::ResourcePool;
@@ -1368,12 +1368,15 @@ impl<'a> RebasePlanBuilder<'a> {
13681368
let path: Vec<CacheLookupResult<Option<NonZeroOid>, NonZeroOid>> = {
13691369
let (effects, progress) = effects.start_operation(OperationType::ReadingFromCache);
13701370
let _effects = effects;
1371-
progress.notify_progress(0, path.len());
13721371
if touched_paths_cache.is_empty() {
13731372
// Fast path for when the cache hasn't been populated.
1374-
path.into_iter().map(CacheLookupResult::NotCached).collect()
1373+
path.into_iter()
1374+
.with_progress(progress)
1375+
.map(CacheLookupResult::NotCached)
1376+
.collect()
13751377
} else {
13761378
path.into_iter()
1379+
.with_progress(progress)
13771380
.map(|commit_oid| match touched_paths_cache.get(&commit_oid) {
13781381
Some(upstream_touched_paths) => {
13791382
if Self::should_check_patch_id(
@@ -1387,15 +1390,14 @@ impl<'a> RebasePlanBuilder<'a> {
13871390
}
13881391
None => CacheLookupResult::NotCached(commit_oid),
13891392
})
1390-
.inspect(|_| progress.notify_progress_inc(1))
13911393
.collect()
13921394
}
13931395
};
13941396

1395-
let (_effects, progress) = effects.start_operation(OperationType::FilterByTouchedPaths);
1396-
progress.notify_progress(0, path.len());
1397-
1397+
let (effects, progress) = effects.start_operation(OperationType::FilterByTouchedPaths);
1398+
let _effects = effects;
13981399
pool.install(|| {
1400+
progress.notify_progress(0, path.len());
13991401
path.into_par_iter()
14001402
.map(|commit_oid| {
14011403
let commit_oid = match commit_oid {

git-branchless-submit/src/phabricator.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use itertools::Itertools;
1919
use lazy_static::lazy_static;
2020
use lib::core::check_out::CheckOutCommitOptions;
2121
use lib::core::dag::{CommitSet, Dag};
22-
use lib::core::effects::{Effects, OperationType};
22+
use lib::core::effects::{Effects, OperationType, WithProgress};
2323
use lib::core::eventlog::EventLogDb;
2424
use lib::core::formatting::StyledStringBuilder;
2525
use lib::core::rewrite::{
@@ -882,8 +882,7 @@ impl PhabricatorForge<'_> {
882882
// Newly-created commits won't have been observed by the DAG, so add them in manually here.
883883
let draft_commits = self.dag.query_draft_commits()?.union(newly_created_commits);
884884

885-
progress.notify_progress(0, commit_oids.len());
886-
for commit_oid in commit_oids {
885+
for commit_oid in commit_oids.into_iter().with_progress(progress) {
887886
let id = match self.get_revision_id(commit_oid)? {
888887
Some(id) => id,
889888
None => {
@@ -929,7 +928,6 @@ impl PhabricatorForge<'_> {
929928
Ok(()) => {}
930929
Err(exit_code) => return Ok(Err(exit_code)),
931930
}
932-
progress.notify_progress_inc(1);
933931
}
934932
Ok(Ok(()))
935933
}

git-branchless/src/commands/repair.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fmt::Write;
22
use std::{collections::HashSet, time::SystemTime};
33

44
use itertools::Itertools;
5+
use lib::core::effects::WithProgress;
56
use lib::git::{CategorizedReferenceName, MaybeZeroOid};
67
use lib::util::EyreExitOr;
78
use lib::{
@@ -24,13 +25,11 @@ pub fn repair(effects: &Effects, dry_run: bool) -> EyreExitOr<()> {
2425
let (effects, progress) = effects.start_operation(OperationType::RepairCommits);
2526
let _effects = effects;
2627
let cursor_oids = event_replayer.get_cursor_oids(event_cursor);
27-
progress.notify_progress(0, cursor_oids.len());
2828
let mut result = HashSet::new();
29-
for oid in cursor_oids {
29+
for oid in cursor_oids.into_iter().with_progress(progress) {
3030
if repo.find_commit(oid)?.is_none() {
3131
result.insert(oid);
3232
}
33-
progress.notify_progress_inc(1);
3433
}
3534
result
3635
};
@@ -48,13 +47,11 @@ pub fn repair(effects: &Effects, dry_run: bool) -> EyreExitOr<()> {
4847
.map(move |reference_name| (oid, reference_name))
4948
})
5049
.collect_vec();
51-
progress.notify_progress(0, branch_names.len());
5250
let mut result = HashSet::new();
53-
for (oid, reference_name) in branch_names {
51+
for (oid, reference_name) in branch_names.into_iter().with_progress(progress) {
5452
if repo.find_reference(&reference_name)?.is_none() {
5553
result.insert((oid, reference_name));
5654
}
57-
progress.notify_progress_inc(1);
5855
}
5956
result
6057
};

git-branchless/src/commands/sync.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use git_branchless_opts::{MoveOptions, ResolveRevsetOptions, Revset};
1515
use git_branchless_revset::{check_revset_syntax, resolve_commits};
1616
use lib::core::config::get_restack_preserve_timestamps;
1717
use lib::core::dag::{sorted_commit_set, union_all, CommitSet, Dag};
18-
use lib::core::effects::{Effects, OperationType};
18+
use lib::core::effects::{Effects, OperationType, WithProgress};
1919
use lib::core::eventlog::{EventLogDb, EventReplayer};
2020
use lib::core::formatting::{Pluralize, StyledStringBuilder};
2121
use lib::core::rewrite::{
@@ -403,9 +403,9 @@ fn execute_plans(
403403
let mut skipped_commits: Vec<Commit> = Vec::new();
404404

405405
let (effects, progress) = effects.start_operation(OperationType::SyncCommits);
406-
progress.notify_progress(0, root_commit_and_plans.len());
407-
408-
for (root_commit_oid, rebase_plan) in root_commit_and_plans {
406+
for (root_commit_oid, rebase_plan) in
407+
root_commit_and_plans.into_iter().with_progress(progress)
408+
{
409409
let root_commit = repo.find_commit_or_fail(root_commit_oid)?;
410410
let rebase_plan = match rebase_plan {
411411
Some(rebase_plan) => rebase_plan,
@@ -423,7 +423,6 @@ fn execute_plans(
423423
&rebase_plan,
424424
execute_options,
425425
)?;
426-
progress.notify_progress_inc(1);
427426
match result {
428427
ExecuteRebasePlanResult::Succeeded { rewritten_oids: _ } => {
429428
success_commits.push(root_commit);

0 commit comments

Comments
 (0)