Skip to main content

git_same/output/progress/
sync.rs

1use crate::git::{FetchResult, PullResult};
2use crate::operations::sync::SyncProgress;
3use crate::output::Verbosity;
4use crate::types::OwnedRepo;
5use console::style;
6use indicatif::{MultiProgress, ProgressBar};
7use std::path::Path;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::sync::Arc;
10
11use super::styles::progress_style;
12
13/// Progress reporter for sync operations.
14pub struct SyncProgressBar {
15    #[allow(dead_code)]
16    multi: MultiProgress,
17    main_bar: ProgressBar,
18    verbosity: Verbosity,
19    updates_count: Arc<AtomicUsize>,
20}
21
22impl SyncProgressBar {
23    /// Creates a new sync progress bar.
24    pub fn new(total: usize, verbosity: Verbosity, operation: &str) -> Self {
25        let multi = MultiProgress::new();
26        let main_bar = multi.add(ProgressBar::new(total as u64));
27        main_bar.set_style(progress_style());
28        main_bar.set_message(format!("{}ing repositories...", operation));
29        main_bar.enable_steady_tick(std::time::Duration::from_millis(100));
30
31        Self {
32            multi,
33            main_bar,
34            verbosity,
35            updates_count: Arc::new(AtomicUsize::new(0)),
36        }
37    }
38
39    /// Finishes the progress bar.
40    pub fn finish(&self, success: usize, failed: usize, skipped: usize) {
41        let updates = self.updates_count.load(Ordering::SeqCst);
42        let msg = format!(
43            "{} {} synced ({} with updates), {} failed, {} skipped",
44            style("✓").green(),
45            success,
46            updates,
47            failed,
48            skipped
49        );
50        self.main_bar.finish_with_message(msg);
51    }
52}
53
54impl SyncProgress for SyncProgressBar {
55    fn on_start(&self, repo: &OwnedRepo, _path: &Path, _index: usize, _total: usize) {
56        if self.verbosity >= Verbosity::Verbose {
57            self.main_bar
58                .set_message(format!("Syncing {}...", style(repo.full_name()).cyan()));
59        }
60    }
61
62    fn on_fetch_complete(
63        &self,
64        repo: &OwnedRepo,
65        result: &FetchResult,
66        _index: usize,
67        _total: usize,
68    ) {
69        self.main_bar.inc(1);
70        if result.updated {
71            self.updates_count.fetch_add(1, Ordering::SeqCst);
72        }
73        if self.verbosity >= Verbosity::Debug {
74            let status = if result.updated {
75                "updated"
76            } else {
77                "up to date"
78            };
79            self.main_bar.suspend(|| {
80                println!(
81                    "{} {} {}",
82                    style("✓").green(),
83                    repo.full_name(),
84                    style(status).dim()
85                );
86            });
87        }
88    }
89
90    fn on_pull_complete(
91        &self,
92        repo: &OwnedRepo,
93        result: &PullResult,
94        _index: usize,
95        _total: usize,
96    ) {
97        self.main_bar.inc(1);
98        if result.updated {
99            self.updates_count.fetch_add(1, Ordering::SeqCst);
100        }
101        if self.verbosity >= Verbosity::Debug {
102            let status = if !result.updated {
103                "up to date"
104            } else if result.fast_forward {
105                "fast-forward"
106            } else {
107                "merged"
108            };
109            self.main_bar.suspend(|| {
110                println!(
111                    "{} {} {}",
112                    style("✓").green(),
113                    repo.full_name(),
114                    style(status).dim()
115                );
116            });
117        }
118    }
119
120    fn on_error(&self, repo: &OwnedRepo, error: &str, _index: usize, _total: usize) {
121        self.main_bar.inc(1);
122        if self.verbosity >= Verbosity::Normal {
123            self.main_bar.suspend(|| {
124                eprintln!(
125                    "{} Failed to sync {}: {}",
126                    style("✗").red(),
127                    repo.full_name(),
128                    error
129                );
130            });
131        }
132    }
133
134    fn on_skip(&self, repo: &OwnedRepo, reason: &str, _index: usize, _total: usize) {
135        self.main_bar.inc(1);
136        if self.verbosity >= Verbosity::Verbose {
137            self.main_bar.suspend(|| {
138                println!(
139                    "{} Skipped {}: {}",
140                    style("→").dim(),
141                    repo.full_name(),
142                    reason
143                );
144            });
145        }
146    }
147}
148
149#[cfg(test)]
150#[path = "sync_tests.rs"]
151mod tests;