mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-10 22:43:37 +00:00
fix(cli): prevent manifest git push prompts
Disable terminal prompts for manifest pre-run push attempts and cover the behavior with an offline remote-helper regression test.
This commit is contained in:
parent
8c3d3c340a
commit
2cbd3768b8
2 changed files with 115 additions and 19 deletions
|
|
@ -15,7 +15,9 @@ use fabro_graphviz::graph::AttrValue;
|
|||
use fabro_graphviz::parser;
|
||||
use fabro_types::settings::run::{ResolvedGoalSource, ResolvedRunGoal};
|
||||
use fabro_types::{DirtyStatus, GitContext, PreRunPushOutcome, RunId, WorkflowSettings};
|
||||
use fabro_workflow::git::{GitSyncStatus, branch_needs_push, head_sha, push_branch, sync_status};
|
||||
use fabro_workflow::git::{
|
||||
GitSyncStatus, branch_needs_push, head_sha, push_branch_noninteractive, sync_status,
|
||||
};
|
||||
|
||||
use crate::args::{PreflightArgs, RunArgs};
|
||||
|
||||
|
|
@ -589,7 +591,7 @@ fn build_manifest_push_outcome(
|
|||
return PreRunPushOutcome::NotAttempted;
|
||||
}
|
||||
|
||||
match push_branch(repo_path, "origin", branch) {
|
||||
match push_branch_noninteractive(repo_path, "origin", branch) {
|
||||
Ok(()) => PreRunPushOutcome::Succeeded {
|
||||
remote: "origin".to_string(),
|
||||
branch: branch.to_string(),
|
||||
|
|
@ -901,11 +903,13 @@ file = "prompts/goal.md"
|
|||
"workspace-branch",
|
||||
"https://github.com/example/workspace.git",
|
||||
);
|
||||
mark_origin_branch_synced(workspace, "workspace-branch");
|
||||
init_git_repo(
|
||||
&target,
|
||||
"target-branch",
|
||||
"https://github.com/example/target.git",
|
||||
);
|
||||
mark_origin_branch_synced(&target, "target-branch");
|
||||
|
||||
let workflow_dir = workspace.join(".fabro/workflows/demo");
|
||||
std::fs::create_dir_all(&workflow_dir).unwrap();
|
||||
|
|
@ -946,6 +950,7 @@ working_dir = "repos/target"
|
|||
.expect("manifest git info should be detected");
|
||||
assert_eq!(git.branch, "target-branch");
|
||||
assert_eq!(git.origin_url, "https://github.com/example/target");
|
||||
assert_eq!(git.push_outcome, PreRunPushOutcome::NotAttempted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1005,28 +1010,84 @@ repository = "target"
|
|||
});
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn build_manifest_push_attempt_disables_terminal_prompts() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let workspace = temp.path().join("workspace");
|
||||
std::fs::create_dir_all(&workspace).unwrap();
|
||||
init_git_repo(&workspace, "feature", "fabro-prompt-test::target");
|
||||
|
||||
let helper_dir = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&helper_dir).unwrap();
|
||||
let helper_path = helper_dir.join("git-remote-fabro-prompt-test");
|
||||
std::fs::write(
|
||||
&helper_path,
|
||||
r#"#!/bin/sh
|
||||
printf '%s\n' "${GIT_TERMINAL_PROMPT-unset}" > "$FABRO_PROMPT_ENV_LOG"
|
||||
echo "helper saw GIT_TERMINAL_PROMPT=${GIT_TERMINAL_PROMPT-unset}" >&2
|
||||
exit 1
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut permissions = std::fs::metadata(&helper_path).unwrap().permissions();
|
||||
permissions.set_mode(0o755);
|
||||
std::fs::set_permissions(&helper_path, permissions).unwrap();
|
||||
|
||||
let workflow_dir = workspace.join(".fabro/workflows/demo");
|
||||
std::fs::create_dir_all(&workflow_dir).unwrap();
|
||||
std::fs::write(workspace.join(".fabro/project.toml"), "_version = 1\n").unwrap();
|
||||
std::fs::write(
|
||||
workflow_dir.join("workflow.toml"),
|
||||
"_version = 1\n\n[workflow]\ngraph = \"workflow.fabro\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
workflow_dir.join("workflow.fabro"),
|
||||
r"digraph Demo { start [shape=Mdiamond] exit [shape=Msquare] start -> exit }",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let helper_log = temp.path().join("prompt-env.txt");
|
||||
let mut path_entries = vec![helper_dir];
|
||||
if let Some(path) = std::env::var_os("PATH") {
|
||||
path_entries.extend(std::env::split_paths(&path));
|
||||
}
|
||||
let path = std::env::join_paths(path_entries).unwrap();
|
||||
temp_env::with_var("PATH", Some(path), || {
|
||||
temp_env::with_var("FABRO_PROMPT_ENV_LOG", Some(helper_log.as_os_str()), || {
|
||||
let built = build_run_manifest(ManifestBuildInput {
|
||||
workflow: PathBuf::from(".fabro/workflows/demo/workflow.toml"),
|
||||
cwd: workspace.to_path_buf(),
|
||||
run_overrides: None,
|
||||
cli_overrides: None,
|
||||
args: None,
|
||||
run_id: None,
|
||||
user_settings_path: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let git = built
|
||||
.manifest
|
||||
.git
|
||||
.expect("manifest git info should be detected");
|
||||
assert!(matches!(git.push_outcome, PreRunPushOutcome::Failed { .. }));
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(std::fs::read_to_string(helper_log).unwrap(), "0\n");
|
||||
}
|
||||
|
||||
fn init_git_repo(path: &Path, branch: &str, origin_url: &str) {
|
||||
use std::process::Command;
|
||||
let run = |args: &[&str]| {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(path)
|
||||
.output()
|
||||
.unwrap_or_else(|e| panic!("failed to spawn git {args:?}: {e}"));
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"git {args:?} failed: stdout={} stderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
};
|
||||
run(&[
|
||||
run_git(path, &[
|
||||
"-c",
|
||||
&format!("init.defaultBranch={branch}"),
|
||||
"init",
|
||||
"--quiet",
|
||||
]);
|
||||
run(&[
|
||||
run_git(path, &[
|
||||
"-c",
|
||||
"user.name=test",
|
||||
"-c",
|
||||
|
|
@ -1037,6 +1098,26 @@ repository = "target"
|
|||
"-m",
|
||||
"init",
|
||||
]);
|
||||
run(&["remote", "add", "origin", origin_url]);
|
||||
run_git(path, &["remote", "add", "origin", origin_url]);
|
||||
}
|
||||
|
||||
fn mark_origin_branch_synced(path: &Path, branch: &str) {
|
||||
let remote_ref = format!("refs/remotes/origin/{branch}");
|
||||
run_git(path, &["update-ref", &remote_ref, "HEAD"]);
|
||||
}
|
||||
|
||||
fn run_git(path: &Path, args: &[&str]) {
|
||||
use std::process::Command;
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(path)
|
||||
.output()
|
||||
.unwrap_or_else(|e| panic!("failed to spawn git {args:?}: {e}"));
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"git {args:?} failed: stdout={} stderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,21 @@ pub fn push_branch(repo: &Path, remote: &str, branch: &str) -> Result<()> {
|
|||
run_git_push(git_cmd(repo).args(["push", remote, branch]))
|
||||
}
|
||||
|
||||
/// Push a local branch to the named remote without allowing Git to prompt.
|
||||
pub fn push_branch_noninteractive(repo: &Path, remote: &str, branch: &str) -> Result<()> {
|
||||
tracing::info!(
|
||||
repo_dir = %repo.display(),
|
||||
remote,
|
||||
branch,
|
||||
"Pushing branch to remote without terminal prompts"
|
||||
);
|
||||
run_git_push(
|
||||
git_cmd(repo)
|
||||
.env("GIT_TERMINAL_PROMPT", "0")
|
||||
.args(["push", remote, branch]),
|
||||
)
|
||||
}
|
||||
|
||||
/// Push run and metadata branches to origin if a remote tracking branch exists.
|
||||
///
|
||||
/// Callers supply pre-built refspecs so they control force-push (`+` prefix).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue