Skip to content

Commit b5d599e

Browse files
committed
fix: isolate startup Git configuration validation
1 parent e119ed8 commit b5d599e

3 files changed

Lines changed: 214 additions & 15 deletions

File tree

‎src-tauri/src/core.rs‎

Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,14 @@ impl AppCore {
330330
let _ = append_log(&self.paths.logs, "ERROR", message);
331331
}
332332

333+
pub fn configuration_refresh_failed(&self, message: &str) {
334+
let _ = append_log(
335+
&self.paths.logs,
336+
"ERROR",
337+
&format!("startup configuration refresh failed: {message}"),
338+
);
339+
}
340+
333341
pub fn set_usage_listening(&self, listening: bool) {
334342
self.usage_listening.store(listening, Ordering::Relaxed);
335343
}
@@ -988,7 +996,13 @@ impl AppCore {
988996
.as_file()
989997
.sync_all()
990998
.map_err(|error| format!("无法同步配置候选:{error}"))?;
991-
validate_configuration(candidate.path(), settings, selected, routes)?;
999+
validate_configuration(
1000+
candidate.path(),
1001+
settings,
1002+
selected,
1003+
routes,
1004+
ValidationEnvironment::ManagedConfigOnly,
1005+
)?;
9921006
}
9931007
let _ = backup_file(
9941008
&self.paths.gitconfig,
@@ -1003,9 +1017,13 @@ impl AppCore {
10031017
}
10041018
}
10051019
if settings.acceleration_enabled && settings.line_mode != LineMode::Direct {
1006-
if let Err(error) =
1007-
validate_configuration(&self.paths.gitconfig, settings, selected, routes)
1008-
{
1020+
if let Err(error) = validate_configuration(
1021+
&self.paths.gitconfig,
1022+
settings,
1023+
selected,
1024+
routes,
1025+
ValidationEnvironment::EffectiveGit,
1026+
) {
10091027
self.restore_git_state(previous.as_deref(), registered_before);
10101028
return Err(format!("写入后的 Git 配置验证失败:{error}"));
10111029
}
@@ -1316,11 +1334,18 @@ impl AppCore {
13161334
}
13171335
}
13181336

1337+
#[derive(Clone, Copy)]
1338+
enum ValidationEnvironment {
1339+
ManagedConfigOnly,
1340+
EffectiveGit,
1341+
}
1342+
13191343
fn validate_configuration(
13201344
config_path: &Path,
13211345
settings: &Settings,
13221346
selected: Option<&NodeDefinition>,
13231347
routes: &[RouteEntry],
1348+
environment: ValidationEnvironment,
13241349
) -> Result<(), String> {
13251350
let node = selected.ok_or_else(|| "没有通过检测的可用节点".to_string())?;
13261351
let mut checks = Vec::new();
@@ -1363,7 +1388,12 @@ fn validate_configuration(
13631388
}
13641389

13651390
for (original, expected_fetch) in checks {
1366-
let (fetch, push) = git::effective_urls(config_path, &original)?;
1391+
let (fetch, push) = match environment {
1392+
ValidationEnvironment::ManagedConfigOnly => {
1393+
git::effective_urls_isolated(config_path, &original)
1394+
}
1395+
ValidationEnvironment::EffectiveGit => git::effective_urls(config_path, &original),
1396+
}?;
13671397
if fetch != expected_fetch {
13681398
return Err(format!("配置未正确处理 fetch:{original}"));
13691399
}
@@ -1807,7 +1837,14 @@ mod tests {
18071837
)
18081838
.unwrap();
18091839
atomic_write(&core.paths.gitconfig, complete.as_bytes()).unwrap();
1810-
validate_configuration(&core.paths.gitconfig, &settings, Some(&node), &routes).unwrap();
1840+
validate_configuration(
1841+
&core.paths.gitconfig,
1842+
&settings,
1843+
Some(&node),
1844+
&routes,
1845+
ValidationEnvironment::ManagedConfigOnly,
1846+
)
1847+
.unwrap();
18111848

18121849
let incomplete = git::build_config(
18131850
&settings,
@@ -1817,11 +1854,107 @@ mod tests {
18171854
)
18181855
.unwrap();
18191856
atomic_write(&core.paths.gitconfig, incomplete.as_bytes()).unwrap();
1820-
let error = validate_configuration(&core.paths.gitconfig, &settings, Some(&node), &routes)
1821-
.unwrap_err();
1857+
let error = validate_configuration(
1858+
&core.paths.gitconfig,
1859+
&settings,
1860+
Some(&node),
1861+
&routes,
1862+
ValidationEnvironment::ManagedConfigOnly,
1863+
)
1864+
.unwrap_err();
18221865
assert!(error.contains("octocat/Hello-World"));
18231866
}
18241867

1868+
#[test]
1869+
fn configuration_update_isolates_the_candidate_but_validates_effective_git_rules() {
1870+
const MARKER: &str = "GITBOOST_VALIDATION_ENVIRONMENT_CHILD";
1871+
if rerun_with_isolated_git(
1872+
"core::tests::configuration_update_isolates_the_candidate_but_validates_effective_git_rules",
1873+
MARKER,
1874+
) {
1875+
return;
1876+
}
1877+
let directory = tempfile::tempdir().unwrap();
1878+
let core = AppCore::new(directory.path().to_path_buf()).unwrap();
1879+
let route = RouteEntry {
1880+
id: "route".into(),
1881+
repository_url: "https://github.com/openai/codex.git".into(),
1882+
created_at: Utc::now(),
1883+
};
1884+
let replacement = NodeDefinition {
1885+
id: "replacement".into(),
1886+
name: "Replacement".into(),
1887+
rewrite_base: "https://new.example/https://github.com/".into(),
1888+
enabled: true,
1889+
built_in: false,
1890+
};
1891+
let settings = Settings {
1892+
acceleration_enabled: true,
1893+
line_mode: LineMode::Fixed,
1894+
fixed_node_id: Some(replacement.id.clone()),
1895+
current_node_id: Some(replacement.id.clone()),
1896+
..Settings::default()
1897+
};
1898+
atomic_write_json(&core.paths.routes, &vec![route.clone()]).unwrap();
1899+
atomic_write_json(&core.paths.nodes, &vec![replacement.clone()]).unwrap();
1900+
atomic_write_json(
1901+
&core.paths.health,
1902+
&HashMap::from([(
1903+
replacement.id.clone(),
1904+
HealthSummary {
1905+
status: NodeStatus::Available,
1906+
success_count: 1,
1907+
attempt_count: 1,
1908+
..HealthSummary::default()
1909+
},
1910+
)]),
1911+
)
1912+
.unwrap();
1913+
atomic_write_json(&core.paths.settings, &settings).unwrap();
1914+
1915+
let old_config = git::build_config(
1916+
&Settings {
1917+
acceleration_enabled: true,
1918+
line_mode: LineMode::Fixed,
1919+
fixed_node_id: Some(FASTGIT_REWRITE_BASE.into()),
1920+
current_node_id: Some(FASTGIT_REWRITE_BASE.into()),
1921+
..Settings::default()
1922+
},
1923+
Some(&NodeDefinition::fastgit()),
1924+
std::slice::from_ref(&route),
1925+
Some(&core.paths.trace_socket),
1926+
)
1927+
.unwrap();
1928+
atomic_write(&core.paths.gitconfig, old_config.as_bytes()).unwrap();
1929+
git::register_include(&core.paths.gitconfig).unwrap();
1930+
1931+
core.refresh_registered_configuration().unwrap();
1932+
let (fetch, push) =
1933+
git::effective_urls(&core.paths.gitconfig, &route.repository_url).unwrap();
1934+
assert_eq!(
1935+
fetch,
1936+
"https://new.example/https://github.com/openai/codex.git"
1937+
);
1938+
assert_eq!(push, route.repository_url);
1939+
1940+
let previous_config = fs::read(&core.paths.gitconfig).unwrap();
1941+
let conflict = std::process::Command::new("git")
1942+
.args([
1943+
"config",
1944+
"--global",
1945+
"url.https://push-proxy.example/https://github.com/openai/codex.pushInsteadOf",
1946+
"https://github.com/openai/codex",
1947+
])
1948+
.output()
1949+
.unwrap();
1950+
assert!(conflict.status.success());
1951+
let error = core.refresh_registered_configuration().unwrap_err();
1952+
assert!(error.contains("配置未保持 push 直连"));
1953+
assert_eq!(fs::read(&core.paths.gitconfig).unwrap(), previous_config);
1954+
1955+
git::unregister_include(&core.paths.gitconfig).unwrap();
1956+
}
1957+
18251958
#[test]
18261959
fn startup_reconciles_missing_include_and_rolls_back_on_state_write_failure() {
18271960
const MARKER: &str = "GITBOOST_STARTUP_RECONCILE_CHILD";

‎src-tauri/src/git.rs‎

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,25 @@ fn affects_github_https(rewrite_prefix: &str) -> bool {
405405
}
406406

407407
pub fn effective_urls(config_path: &Path, original_url: &str) -> Result<(String, String), String> {
408+
effective_urls_with_global_config(config_path, original_url, None)
409+
}
410+
411+
pub fn effective_urls_isolated(
412+
config_path: &Path,
413+
original_url: &str,
414+
) -> Result<(String, String), String> {
415+
effective_urls_with_global_config(config_path, original_url, Some(config_path))
416+
}
417+
418+
fn effective_urls_with_global_config(
419+
config_path: &Path,
420+
original_url: &str,
421+
global_config: Option<&Path>,
422+
) -> Result<(String, String), String> {
408423
let temp = tempfile::tempdir().map_err(|error| format!("无法创建诊断目录:{error}"))?;
409424
let directory = temp.path().to_string_lossy();
410425
let include = format!("include.path={}", config_path.display());
411-
let init = run_git(
426+
let init = run_git_with_global_config(
412427
[
413428
"-c",
414429
include.as_str(),
@@ -419,11 +434,12 @@ pub fn effective_urls(config_path: &Path, original_url: &str) -> Result<(String,
419434
],
420435
None,
421436
Duration::from_secs(8),
437+
global_config,
422438
)?;
423439
if !init.status.success() {
424440
return Err(command_error(&init));
425441
}
426-
let add = run_git(
442+
let add = run_git_with_global_config(
427443
[
428444
"-c",
429445
include.as_str(),
@@ -436,11 +452,12 @@ pub fn effective_urls(config_path: &Path, original_url: &str) -> Result<(String,
436452
],
437453
None,
438454
Duration::from_secs(8),
455+
global_config,
439456
)?;
440457
if !add.status.success() {
441458
return Err(command_error(&add));
442459
}
443-
let fetch = run_git(
460+
let fetch = run_git_with_global_config(
444461
[
445462
"-c",
446463
include.as_str(),
@@ -452,8 +469,9 @@ pub fn effective_urls(config_path: &Path, original_url: &str) -> Result<(String,
452469
],
453470
None,
454471
Duration::from_secs(8),
472+
global_config,
455473
)?;
456-
let push = run_git(
474+
let push = run_git_with_global_config(
457475
[
458476
"-c",
459477
include.as_str(),
@@ -466,6 +484,7 @@ pub fn effective_urls(config_path: &Path, original_url: &str) -> Result<(String,
466484
],
467485
None,
468486
Duration::from_secs(8),
487+
global_config,
469488
)?;
470489
if !fetch.status.success() || !push.status.success() {
471490
return Err("无法解析 Git 有效地址".into());
@@ -581,6 +600,15 @@ fn run_git<const N: usize>(
581600
args: [&str; N],
582601
current_dir: Option<&Path>,
583602
timeout: Duration,
603+
) -> Result<Output, String> {
604+
run_git_with_global_config(args, current_dir, timeout, None)
605+
}
606+
607+
fn run_git_with_global_config<const N: usize>(
608+
args: [&str; N],
609+
current_dir: Option<&Path>,
610+
timeout: Duration,
611+
global_config: Option<&Path>,
584612
) -> Result<Output, String> {
585613
let mut command = Command::new("git");
586614
// GitBoost's own probes and configuration checks are operational noise, not user traffic.
@@ -590,6 +618,11 @@ fn run_git<const N: usize>(
590618
.env("GIT_TERMINAL_PROMPT", "0")
591619
.stdout(Stdio::piped())
592620
.stderr(Stdio::piped());
621+
if let Some(config) = global_config {
622+
command
623+
.env("GIT_CONFIG_GLOBAL", config)
624+
.env("GIT_CONFIG_NOSYSTEM", "1");
625+
}
593626
hide_console(&mut command);
594627
if let Some(directory) = current_dir {
595628
command.current_dir(directory);
@@ -688,6 +721,39 @@ mod tests {
688721
}
689722
}
690723

724+
#[test]
725+
fn isolated_effective_urls_ignore_the_registered_live_config() {
726+
let directory = tempfile::tempdir().unwrap();
727+
let live = directory.path().join("live.gitconfig");
728+
let candidate = directory.path().join("candidate.gitconfig");
729+
let global = directory.path().join("global.gitconfig");
730+
fs::write(
731+
&live,
732+
"[url \"https://old.example/https://github.com/openai/codex\"]\n\tinsteadOf = https://github.com/openai/codex\n",
733+
)
734+
.unwrap();
735+
fs::write(
736+
&candidate,
737+
"[url \"https://new.example/https://github.com/openai/codex\"]\n\tinsteadOf = https://github.com/openai/codex\n",
738+
)
739+
.unwrap();
740+
fs::write(&global, format!("[include]\n\tpath = {}\n", live.display())).unwrap();
741+
742+
let original = "https://github.com/openai/codex.git";
743+
let (conflicted, _) =
744+
effective_urls_with_global_config(&candidate, original, Some(&global)).unwrap();
745+
assert_eq!(
746+
conflicted,
747+
"https://old.example/https://github.com/openai/codex.git"
748+
);
749+
750+
let (fetch, _) = effective_urls_isolated(&candidate, original).unwrap();
751+
assert_eq!(
752+
fetch,
753+
"https://new.example/https://github.com/openai/codex.git"
754+
);
755+
}
756+
691757
#[test]
692758
fn configures_trace2_with_a_cross_platform_stream_socket() {
693759
let config = build_config(

‎src-tauri/src/lib.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,9 @@ pub fn run() {
472472
AppCore::new(data_dir).map_err(|error| format!("GitBoost 初始化失败:{error}"))?;
473473
app.manage(core);
474474
usage::start_listener(app.handle().clone());
475-
app.state::<AppCore>()
476-
.refresh_registered_configuration()
477-
.map_err(|error| format!("无法升级 GitBoost 配置:{error}"))?;
475+
if let Err(error) = app.state::<AppCore>().refresh_registered_configuration() {
476+
app.state::<AppCore>().configuration_refresh_failed(&error);
477+
}
478478
install_tray(app)?;
479479
start_system_node_monitor(app.handle().clone());
480480
start_health_monitor(app.handle().clone());

0 commit comments

Comments
 (0)