Add support for pushing custom git repositories (#574)
This commit is contained in:
118
src/steps/git.rs
118
src/steps/git.rs
@@ -27,9 +27,16 @@ pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum GitAction {
|
||||
Push,
|
||||
Pull,
|
||||
}
|
||||
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
repositories: HashSet<String>,
|
||||
pull_repositories: HashSet<String>,
|
||||
push_repositories: HashSet<String>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
@@ -44,6 +51,36 @@ fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn push_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
|
||||
println!("{} {}", style("Pushing").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["push", "--porcelain"]);
|
||||
if let Some(extra_arguments) = ctx.config().push_git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let output = command.output().await?;
|
||||
let result = match output.status.success() {
|
||||
true => Ok(()),
|
||||
false => Err(format!("Failed to push {repo}")),
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
println!("{} pushing {}", style("Failed").red().bold(), &repo);
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(eyre!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
@@ -58,7 +95,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
if let Some(extra_arguments) = ctx.config().pull_git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
@@ -181,7 +218,7 @@ impl Git {
|
||||
|
||||
None
|
||||
}
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn multi_repo_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
@@ -192,12 +229,15 @@ impl Git {
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
|
||||
|
||||
if repositories.repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
if repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull or push")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
self.multi_pull(repositories, ctx)
|
||||
self.multi_push(repositories, ctx)?;
|
||||
self.multi_pull(repositories, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -205,7 +245,7 @@ impl Git {
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.repositories
|
||||
.pull_repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
@@ -213,7 +253,7 @@ impl Git {
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.repositories
|
||||
.pull_repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
@@ -240,6 +280,47 @@ impl Git {
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
|
||||
pub fn multi_push(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
let git = self.git.as_ref().unwrap();
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.push_repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would push {}", &repo));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.push_repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| push_repository(repo.clone(), git, ctx));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Repositories<'a> {
|
||||
@@ -252,22 +333,27 @@ impl<'a> Repositories<'a> {
|
||||
|
||||
Self {
|
||||
git,
|
||||
repositories: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
pull_repositories: HashSet::new(),
|
||||
push_repositories: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P, action: GitAction) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
self.repositories.insert(repo);
|
||||
match action {
|
||||
GitAction::Push => self.push_repositories.insert(repo),
|
||||
GitAction::Pull => self.pull_repositories.insert(repo),
|
||||
};
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
pub fn glob_insert(&mut self, pattern: &str, action: GitAction) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
for entry in glob {
|
||||
@@ -283,7 +369,7 @@ impl<'a> Repositories<'a> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if self.insert_if_repo(&path) {
|
||||
if self.insert_if_repo(&path, action) {
|
||||
last_git_repo = Some(path);
|
||||
}
|
||||
}
|
||||
@@ -301,14 +387,14 @@ impl<'a> Repositories<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.repositories.is_empty()
|
||||
self.pull_repositories.is_empty() && self.push_repositories.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
let _removed = self.pull_repositories.remove(path);
|
||||
let _removed = self.push_repositories.remove(path);
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user