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