Skip to content
10 changes: 6 additions & 4 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -224,21 +224,23 @@


[git]
# How many repos to pull at max in parallel
# How many repos to pull or fetch at max in parallel
# max_concurrency = 5

# Additional git repositories to pull
# Additional git repositories to pull or fetch
# repos = [
# "~/src/*/",
# "~/.config/something"
# ]

# Don't pull the predefined git repos
# Don't pull/fetch the predefined git repos
# pull_predefined = false

# Arguments to pass Git when pulling Repositories
# Arguments to pass Git when pulling/fetching Repositories
# arguments = "--rebase --autostash"

# Whether to perform a `git fetch` instead of `git pull`
# fetch_only = false

[windows]
# Manually select Windows updates
Expand Down
24 changes: 24 additions & 0 deletions locales/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ _version: 2
zh_CN: "正在拉取"
zh_TW: "正在拉取"
de: "Abrufen"
"Fetching":
en: "Fetching"
lt: "Gaunama"
es: "Obteniendo"
fr: "Récupération"
zh_CN: "正在获取"
zh_TW: "正在獲取"
de: "Abrufen"
"No Breaking changes":
en: "No Breaking changes"
lt: "Nėra esminių pakeitimų"
Expand Down Expand Up @@ -152,6 +160,14 @@ _version: 2
zh_CN: "正在拉取"
zh_TW: "正在拉取"
de: "abrufen"
"fetching":
en: "fetching"
lt: "gaunama"
es: "obteniendo"
fr: "récupération"
zh_CN: "正在获取"
zh_TW: "正在獲取"
de: "abrufen"
"Changed":
en: "Changed"
lt: "Pakeista"
Expand Down Expand Up @@ -942,6 +958,14 @@ _version: 2
zh_CN: "拉取 %{repo}"
zh_TW: "拉取 %{repo}"
de: "Würde %{repo} abrufen"
"Would fetch {repo}":
en: "Would fetch %{repo}"
lt: "Gautų %{repo}"
es: "Obteniendo %{repo}"
fr: "Récupérerait %{repo}"
zh_CN: "获取 %{repo}"
zh_TW: "獲取 %{repo}"
de: "Würde %{repo} abrufen"
"Node Package Manager":
en: "Node Package Manager"
lt: "Node paketų tvarkyklė (npm)"
Expand Down
11 changes: 11 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ pub struct Git {
repos: Option<Vec<String>>,

pull_predefined: Option<bool>,

fetch_only: Option<bool>,
}

#[derive(Deserialize, Default, Debug, Merge)]
Expand Down Expand Up @@ -1047,6 +1049,15 @@ impl Config {
self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref())
}

/// Only fetch repositories instead of pulling
pub fn git_fetch_only(&self) -> bool {
self.config_file
.git
.as_ref()
.and_then(|git| git.fetch_only)
.unwrap_or(false)
}

pub fn tmux_config(&self) -> Result<TmuxConfig> {
let args = self.tmux_arguments()?;
Ok(TmuxConfig {
Expand Down
2 changes: 1 addition & 1 deletion src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ impl Step {
Gcloud => runner.execute(*self, "gcloud", || generic::run_gcloud_components_update(ctx))?,
Gem => runner.execute(*self, "gem", || generic::run_gem(ctx))?,
Ghcup => runner.execute(*self, "ghcup", || generic::run_ghcup_update(ctx))?,
GitRepos => runner.execute(*self, "Git Repositories", || git::run_git_pull(ctx))?,
GitRepos => runner.execute(*self, "Git Repositories", || git::run_git_pull_or_fetch(ctx))?,
GithubCliExtensions => runner.execute(*self, "GitHub CLI Extensions", || {
generic::run_ghcli_extensions_upgrade(ctx)
})?,
Expand Down
85 changes: 56 additions & 29 deletions src/steps/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::XDG_DIRS;
#[cfg(windows)]
use crate::WINDOWS_DIRS;

pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
pub fn run_git_pull_or_fetch(ctx: &ExecutionContext) -> Result<()> {
let mut repos = RepoStep::try_new()?;
let config = ctx.config();

Expand Down Expand Up @@ -115,7 +115,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {

print_separator(t!("Git repositories"));

repos.pull_repos(ctx)
repos.pull_or_fetch_repos(ctx)
}

#[cfg(windows)]
Expand Down Expand Up @@ -298,41 +298,61 @@ impl RepoStep {
debug_assert!(_removed);
}

/// Try to pull a repo.
async fn pull_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
/// Try to pull a repo, or fetch it if `fetch_only` is enabled.
async fn pull_or_fetch_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
let before_revision = get_head_revision(&self.git, &repo);

if ctx.config().verbose() {
println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
}
let is_fetch_only = ctx.config().git_fetch_only();

let mut command = AsyncCommand::new(&self.git);

command
.stdin(Stdio::null())
.current_dir(&repo)
.args(["pull", "--ff-only"]);
if is_fetch_only {
if ctx.config().verbose() {
println!("{} {}", style(t!("Fetching")).cyan().bold(), repo.as_ref().display());
}

command.stdin(Stdio::null()).current_dir(&repo).args(["fetch"]);
} else {
if ctx.config().verbose() {
println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
}

command
.stdin(Stdio::null())
.current_dir(&repo)
.args(["pull", "--ff-only"]);
}

if let Some(extra_arguments) = ctx.config().git_arguments() {
command.args(extra_arguments.split_whitespace());
}

let pull_output = command.output().await?;
let submodule_output = AsyncCommand::new(&self.git)
.args(["submodule", "update", "--recursive"])
.current_dir(&repo)
.stdin(Stdio::null())
.output()
.await?;
let result = output_checked_utf8(pull_output)
.and_then(|()| output_checked_utf8(submodule_output))
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
let output = command.output().await?;

let result = if is_fetch_only {
output_checked_utf8(output)
} else {
let submodule_output = AsyncCommand::new(&self.git)
.args(["submodule", "update", "--recursive"])
.current_dir(&repo)
.stdin(Stdio::null())
.output()
.await?;

output_checked_utf8(output).and_then(|()| output_checked_utf8(submodule_output))
}
.wrap_err_with(|| {
format!(
"Failed to {} {}",
if is_fetch_only { "fetch" } else { "pull" },
repo.as_ref().display()
)
});

if result.is_err() {
println!(
"{} {} {}",
style(t!("Failed")).red().bold(),
t!("pulling"),
if is_fetch_only { t!("fetching") } else { t!("pulling") },
repo.as_ref().display()
);
} else {
Expand Down Expand Up @@ -366,16 +386,23 @@ impl RepoStep {
result
}

/// Pull the repositories specified in `self.repos`.
/// Pulls or fetches the repositories specified in `self.repos`, depending on `fetch_only`.
///
/// # NOTE
/// This function will create an async runtime and do the real job so the
/// function itself is not async.
fn pull_repos(&self, ctx: &ExecutionContext) -> Result<()> {
fn pull_or_fetch_repos(&self, ctx: &ExecutionContext) -> Result<()> {
let is_fetch_only = ctx.config().git_fetch_only();

if ctx.run_type().dry() {
self.repos
.iter()
.for_each(|repo| println!("{}", t!("Would pull {repo}", repo = repo.display())));
self.repos.iter().for_each(|repo| {
let message = if is_fetch_only {
t!("Would fetch {repo}", repo = repo.display())
} else {
t!("Would pull {repo}", repo = repo.display())
};
println!("{}", message);
});

return Ok(());
}
Expand Down Expand Up @@ -403,7 +430,7 @@ impl RepoStep {
}
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
})
.map(|repo| self.pull_repo(ctx, repo));
.map(|repo| self.pull_or_fetch_repo(ctx, repo));

let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
iter(futures_iterator).buffer_unordered(limit).boxed()
Expand Down