git_workspace/commands/
mod.rs1pub mod add_provider;
2pub mod archive;
3pub mod fetch;
4pub mod list;
5pub mod lock;
6pub mod run;
7pub mod switch_and_pull;
8pub mod update;
9
10pub use add_provider::add_provider_to_config;
11pub use archive::archive;
12pub use fetch::fetch;
13pub use list::list;
14pub use lock::lock;
15pub use run::execute_cmd;
16pub use switch_and_pull::pull_all_repositories;
17pub use update::update;
18
19use crate::repository::Repository;
20use anyhow::{anyhow, Context};
21use atomic_counter::{AtomicCounter, RelaxedCounter};
22use indicatif::{MultiProgress, ParallelProgressIterator, ProgressBar, ProgressStyle};
23use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
24use std::collections::HashSet;
25use std::path::{Path, PathBuf};
26use std::sync::Arc;
27use std::time::Duration;
28use walkdir::WalkDir;
29
30pub fn map_repositories<F>(repositories: &[Repository], threads: usize, f: F) -> anyhow::Result<()>
34where
35 F: Fn(&Repository, &ProgressBar) -> anyhow::Result<()> + std::marker::Sync,
36{
37 let progress = Arc::new(MultiProgress::new());
40 let total_bar = progress.add(ProgressBar::new(repositories.len() as u64));
42 total_bar.set_style(
43 ProgressStyle::default_bar()
44 .template("[{elapsed_precise}] {percent}% [{wide_bar:.cyan/blue}] {pos}/{len} (ETA: {eta_precise})").expect("Invalid template")
45 .progress_chars("#>-"),
46 );
47
48 let is_attended = console::user_attended();
50 let total_repositories = repositories.len();
51 let counter = RelaxedCounter::new(1);
54
55 let pool = rayon::ThreadPoolBuilder::new()
58 .num_threads(threads)
59 .build()
60 .with_context(|| "Error creating the thread pool")?;
61
62 let errors: Vec<(&Repository, anyhow::Error)> = pool.install(|| {
64 repositories
65 .par_iter()
66 .map(|repo| {
68 let progress_bar = progress.add(ProgressBar::new_spinner());
70 progress_bar.set_message("waiting...");
71 progress_bar.enable_steady_tick(Duration::from_millis(500));
72 let idx = counter.inc();
74 if !is_attended {
75 println!("[{}/{}] Starting {}", idx, total_repositories, repo.name());
76 }
77 let result = match f(repo, &progress_bar) {
80 Ok(_) => Ok(()),
81 Err(e) => Err((repo, e)),
82 };
83 if !is_attended {
84 println!("[{}/{}] Finished {}", idx, total_repositories, repo.name());
85 }
86 progress_bar.finish_and_clear();
88 result
89 })
90 .progress_with(total_bar)
91 .filter_map(Result::err)
93 .collect()
95 });
96
97 if !errors.is_empty() {
99 eprintln!("{} repositories failed:", errors.len());
100 for (repo, error) in errors {
101 eprintln!("{}:", repo.name());
102 error
103 .chain()
104 .for_each(|cause| eprintln!("because: {}", cause));
105 }
106 }
107
108 Ok(())
109}
110
111pub fn get_all_repositories_to_archive(
113 workspace: &Path,
114 repositories: Vec<Repository>,
115) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
116 let archive_directory = if cfg!(windows) {
125 workspace.join("_archive")
126 } else {
127 workspace.join(".archive")
128 };
129
130 let mut repository_paths: HashSet<PathBuf> = repositories
132 .iter()
133 .filter(|r| r.exists(workspace))
134 .map(|r| r.get_path(workspace))
135 .filter_map(Result::ok)
136 .collect();
137
138 if !archive_directory.exists() {
140 fs_extra::dir::create(&archive_directory, false).with_context(|| {
141 format!(
142 "Error creating archive directory {}",
143 archive_directory.display()
144 )
145 })?;
146 }
147
148 repository_paths.insert(
151 archive_directory
152 .canonicalize()
153 .with_context(|| "Error canoncalizing archive directory")?,
154 );
155
156 let mut to_archive = Vec::new();
157 let mut it = WalkDir::new(workspace).into_iter();
158
159 loop {
162 let entry = match it.next() {
165 None => break,
166 Some(Err(err)) => return Err(anyhow!("Error iterating through directory: {}", err)),
167 Some(Ok(entry)) => entry,
168 };
169 if repository_paths.contains(entry.path()) {
171 it.skip_current_dir();
172 continue;
173 }
174 if entry.path().join(".git").is_dir() {
177 let path = entry.path();
178 let relative_dir = path.strip_prefix(workspace).with_context(|| {
181 format!(
182 "Failed to strip the prefix '{}' from {}",
183 workspace.display(),
184 path.display()
185 )
186 })?;
187 let to_dir = archive_directory.join(relative_dir);
189 to_archive.push((path.to_path_buf(), to_dir));
190 it.skip_current_dir();
191 continue;
192 }
193 }
194
195 Ok(to_archive)
196}