feat(commit,tag): 推送环节感知远程配置并支持多远程选择推送
- 仓库未配置任何远程时跳过推送询问并打印说明(--push 显式指定同样跳过) - --remote 指定的目标不存在时回退到第一个可用远程(字母序)并明确提示 - 新增 --select-remote:多远程仓库弹出多选列表依次推送,失败即中止并汇总已推送/未尝试 - 优先级链:--yes > --select-remote > --push - commit/tag 推送段收敛为 commands::run_push_flow 共享流程 - GitRepo 新增 list_remotes/resolve_push_target,i18n 新文案覆盖 7 种语言
This commit is contained in:
@@ -5,3 +5,101 @@ pub mod credential;
|
||||
pub mod init;
|
||||
pub mod profile;
|
||||
pub mod tag;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use dialoguer::{Confirm, MultiSelect};
|
||||
|
||||
use crate::git::GitRepo;
|
||||
use crate::i18n::Messages;
|
||||
use crate::utils::{print_progress, print_success, print_warning};
|
||||
|
||||
/// Shared push flow for commit and tag: resolve the push target, honor the
|
||||
/// `--yes` > `--select-remote` > `--push` priority chain, then push to the
|
||||
/// chosen target(s). `progress_msg`/`success_msg` render the per-remote
|
||||
/// status lines; `refspec` is "HEAD" for commit, the tag ref for tag.
|
||||
pub(crate) fn run_push_flow(
|
||||
repo: &GitRepo,
|
||||
requested_remote: &str,
|
||||
select_remote: bool,
|
||||
push: bool,
|
||||
yes: bool,
|
||||
messages: &Messages,
|
||||
confirm_prompt: &str,
|
||||
refspec: &str,
|
||||
progress_msg: impl Fn(&str) -> String,
|
||||
success_msg: impl Fn(&str) -> String,
|
||||
) -> Result<()> {
|
||||
let Some((target, fell_back)) = repo.resolve_push_target(requested_remote)? else {
|
||||
print_warning(&messages.push_skipped_no_remote());
|
||||
return Ok(());
|
||||
};
|
||||
if fell_back && (push || !yes) {
|
||||
print_warning(&messages.push_target_fallback(requested_remote, &target));
|
||||
}
|
||||
|
||||
let remotes = if select_remote && !yes {
|
||||
Some(repo.list_remotes()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let use_multi_select = remotes.as_ref().is_some_and(|r| r.len() > 1);
|
||||
|
||||
let targets: Vec<String> = if use_multi_select {
|
||||
let remotes = remotes.unwrap();
|
||||
let items: Vec<(&String, bool)> = remotes.iter().map(|r| (r, *r == target)).collect();
|
||||
let selected = MultiSelect::new()
|
||||
.with_prompt(messages.select_push_remotes())
|
||||
.items_checked(&items)
|
||||
.interact()?;
|
||||
if selected.is_empty() {
|
||||
print_warning(&messages.no_remotes_selected());
|
||||
return Ok(());
|
||||
}
|
||||
selected.iter().map(|&i| remotes[i].clone()).collect()
|
||||
} else if push {
|
||||
vec![target]
|
||||
} else if !yes {
|
||||
if Confirm::new()
|
||||
.with_prompt(confirm_prompt)
|
||||
.default(false)
|
||||
.interact()?
|
||||
{
|
||||
vec![target]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
if targets.len() == 1 {
|
||||
print_progress(&progress_msg(&targets[0]));
|
||||
repo.push(&targets[0], refspec)?;
|
||||
print_success(&success_msg(&targets[0]));
|
||||
} else if targets.len() > 1 {
|
||||
let fmt_list = |items: &[String]| {
|
||||
if items.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
items.join(", ")
|
||||
}
|
||||
};
|
||||
for (i, remote_name) in targets.iter().enumerate() {
|
||||
print_progress(&progress_msg(remote_name));
|
||||
if let Err(e) = repo.push(remote_name, refspec) {
|
||||
bail!(
|
||||
"{}\n{}",
|
||||
messages.push_multi_failed(
|
||||
remote_name,
|
||||
&fmt_list(&targets[..i]),
|
||||
&fmt_list(&targets[i + 1..])
|
||||
),
|
||||
e
|
||||
);
|
||||
}
|
||||
print_success(&success_msg(remote_name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user