aboutsummaryrefslogtreecommitdiff
path: root/src/workspace/git/config_tests.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-30 23:58:15 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-30 23:58:34 +0300
commitfc4e1bbf6b0fa2ec37445a5b72356514c7c6be6a (patch)
tree93af04af44a4dcc062a7886c51d70641c1903144 /src/workspace/git/config_tests.rs
parentd477341e6af816ff4396042b084a0378ef437b4a (diff)
perf: reduce sidebar git polling overhead
refs #353
Diffstat (limited to 'src/workspace/git/config_tests.rs')
-rw-r--r--src/workspace/git/config_tests.rs733
1 files changed, 733 insertions, 0 deletions
diff --git a/src/workspace/git/config_tests.rs b/src/workspace/git/config_tests.rs
new file mode 100644
index 00000000..e789e1db
--- /dev/null
+++ b/src/workspace/git/config_tests.rs
@@ -0,0 +1,733 @@
+use super::config::*;
+use crate::workspace::git::{
+ discovery::git_worktree_info,
+ status::git_status_fingerprint,
+ test_support::{temp_test_dir, write_fake_tracked_repo},
+};
+
+#[test]
+fn git_status_fingerprint_honors_remote_fetch_refspec() {
+ let root = temp_test_dir("custom-fetch-refspec");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/upstream")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/upstream/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"origin\"]\n\tfetch = +refs/heads/*:refs/remotes/upstream/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.full_ref, "refs/remotes/upstream/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_included_config() {
+ let root = temp_test_dir("included-config");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[include]\n\tpath = included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_branch_config_reads_user_config_before_repo_config() {
+ let root = temp_test_dir("user-config");
+ write_fake_tracked_repo(&root);
+ let user_config = root.join("user.gitconfig");
+ std::fs::write(root.join(".git/config"), "").unwrap();
+ std::fs::write(
+ &user_config,
+ "[remote \"global\"]\n\tfetch = +refs/heads/*:refs/remotes/global/*\n[branch \"main\"]\n\tremote = global\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let info = git_worktree_info(&root).unwrap();
+ let config = read_branch_config_with_user_paths(&info, "main", vec![user_config]).unwrap();
+
+ assert_eq!(config.remote, "global");
+ assert_eq!(
+ upstream_full_ref(&config).as_deref(),
+ Some("refs/remotes/global/main")
+ );
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_branch_config_repo_config_overrides_user_config() {
+ let root = temp_test_dir("repo-overrides-user-config");
+ write_fake_tracked_repo(&root);
+ let user_config = root.join("user.gitconfig");
+ std::fs::write(
+ &user_config,
+ "[remote \"global\"]\n\tfetch = +refs/heads/*:refs/remotes/global/*\n[branch \"main\"]\n\tremote = global\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let info = git_worktree_info(&root).unwrap();
+ let config = read_branch_config_with_user_paths(&info, "main", vec![user_config]).unwrap();
+
+ assert_eq!(config.remote, "origin");
+ assert_eq!(
+ upstream_full_ref(&config).as_deref(),
+ Some("refs/remotes/origin/main")
+ );
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_applies_repeated_includes_in_order() {
+ let root = temp_test_dir("repeated-include");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[include]\n\tpath = included.cfg\n[branch \"main\"]\n\tremote = middle\n[include]\n\tpath = included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_matching_include_if_config() {
+ let root = temp_test_dir("include-if-config");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ format!(
+ "[includeIf \"gitdir:{}\"]\n\tpath = included.cfg\n",
+ root.join(".git").display()
+ ),
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_matches_gitdir_include_if_directory_pattern() {
+ let base = temp_test_dir("include-if-dir");
+ let root = base.join("work/repo");
+ std::fs::create_dir_all(&root).unwrap();
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ format!(
+ "[includeIf \"gitdir:{}/\"]\n\tpath = included.cfg\n",
+ base.join("work").display()
+ ),
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(base).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_case_insensitive_config_keys() {
+ let root = temp_test_dir("case-insensitive-config");
+ write_fake_tracked_repo(&root);
+ std::fs::write(
+ root.join(".git/config"),
+ "[Remote \"origin\"] # remote section\n\tFetch = +refs/heads/*:refs/remotes/origin/*\n[Branch \"main\"] ; branch section\n\tRemote = origin\n\tMerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "origin");
+ assert_eq!(upstream.full_ref, "refs/remotes/origin/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_keeps_refspecs_for_later_remote_override() {
+ let root = temp_test_dir("worktree-remote-override");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/fork")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/fork/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[extensions]\n\tworktreeConfig = true\n[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "fork");
+ assert_eq!(upstream.full_ref, "refs/remotes/fork/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_ignores_worktree_config_when_extension_disabled() {
+ let root = temp_test_dir("worktree-config-disabled");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/fork")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/fork/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "origin");
+ assert_eq!(upstream.full_ref, "refs/remotes/origin/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_accepts_git_boolean_worktree_config() {
+ let root = temp_test_dir("worktree-config-boolean");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/fork")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/fork/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[extensions]\n\tworktreeConfig\n[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "fork");
+ assert_eq!(upstream.full_ref, "refs/remotes/fork/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_uses_last_worktree_config_boolean() {
+ let root = temp_test_dir("worktree-config-duplicate-boolean");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/fork")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/fork/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[extensions]\n\tworktreeConfig = false\n\tworktreeConfig = true\n[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "fork");
+ assert_eq!(upstream.full_ref, "refs/remotes/fork/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_ignores_included_worktree_config_extension() {
+ let root = temp_test_dir("worktree-config-included-extension");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/fork")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/fork/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[include]\n\tpath = extension.cfg\n[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/extension.cfg"),
+ "[extensions]\n\tworktreeConfig = true\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "origin");
+ assert_eq!(upstream.full_ref, "refs/remotes/origin/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_onbranch_include_if_config() {
+ let root = temp_test_dir("include-if-onbranch");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[includeIf \"onbranch:main\"]\n\tpath = included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_hasconfig_include_if_config() {
+ let root = temp_test_dir("include-if-hasconfig");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"fork\"]\n\turl = https://example.test/fork.git\n[includeIf \"hasconfig:remote.*.url:*fork.git\"]\n\tpath = included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_matches_user_hasconfig_against_repo_remote_url() {
+ let root = temp_test_dir("include-if-hasconfig-user-repo-url");
+ let user_config = root.join("user.gitconfig");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"fork\"]\n\turl = https://example.test/fork.git\n",
+ )
+ .unwrap();
+ std::fs::write(
+ &user_config,
+ "[includeIf \"hasconfig:remote.*.url:*fork.git\"]\n\tpath = user-included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join("user-included.cfg"),
+ "[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let info = git_worktree_info(&root).unwrap();
+ let config = read_branch_config_with_user_paths(&info, "main", vec![user_config]).unwrap();
+
+ assert_eq!(config.remote, "included");
+ assert_eq!(config.merge_ref, "refs/heads/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_skips_hasconfig_include_that_defines_remote_url() {
+ let root = temp_test_dir("include-if-hasconfig-rejects-remote-url");
+ let user_config = root.join("user.gitconfig");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"fork\"]\n\turl = https://example.test/fork.git\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ &user_config,
+ "[includeIf \"hasconfig:remote.*.url:*fork.git\"]\n\tpath = user-included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join("user-included.cfg"),
+ "[remote \"included\"]\n\turl = https://example.test/included.git\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let info = git_worktree_info(&root).unwrap();
+ let config = read_branch_config_with_user_paths(&info, "main", vec![user_config]).unwrap();
+
+ assert_eq!(config.remote, "origin");
+ assert_eq!(config.merge_ref, "refs/heads/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_skips_hasconfig_include_chain_that_defines_remote_url() {
+ let root = temp_test_dir("include-if-hasconfig-rejects-nested-remote-url");
+ let user_config = root.join("user.gitconfig");
+ write_fake_tracked_repo(&root);
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "3333333333333333333333333333333333333333\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"fork\"]\n\turl = https://example.test/fork.git\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ &user_config,
+ "[includeIf \"hasconfig:remote.*.url:*fork.git\"]\n\tpath = user-included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join("user-included.cfg"),
+ "[include]\n\tpath = nested-remote.cfg\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join("nested-remote.cfg"),
+ "[remote \"included\"]\n\turl = https://example.test/included.git\n\tfetch = +refs/heads/*:refs/remotes/included/*\n",
+ )
+ .unwrap();
+
+ let info = git_worktree_info(&root).unwrap();
+ let config = read_branch_config_with_user_paths(&info, "main", vec![user_config]).unwrap();
+
+ assert_eq!(config.remote, "origin");
+ assert_eq!(config.merge_ref, "refs/heads/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_ignores_worktree_urls_for_hasconfig() {
+ let root = temp_test_dir("include-if-hasconfig-worktree-url");
+ write_fake_tracked_repo(&root);
+ std::fs::write(
+ root.join(".git/config"),
+ "[extensions]\n\tworktreeConfig = true\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config.worktree"),
+ "[remote \"fork\"]\n\turl = https://example.test/fork.git\n[includeIf \"hasconfig:remote.*.url:*fork.git\"]\n\tpath = included.cfg\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/included.cfg"),
+ "[branch \"main\"]\n\tremote = included\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "origin");
+ assert_eq!(upstream.full_ref, "refs/remotes/origin/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_stops_recursive_include_cycles() {
+ let root = temp_test_dir("include-cycle");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/included")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/included/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(root.join(".git/config"), "[include]\n\tpath = a.cfg\n").unwrap();
+ std::fs::write(root.join(".git/a.cfg"), "[include]\n\tpath = b.cfg\n").unwrap();
+ std::fs::write(
+ root.join(".git/b.cfg"),
+ "[include]\n\tpath = a.cfg\n[remote \"included\"]\n\tfetch = +refs/heads/*:refs/remotes/included/*\n[branch \"main\"]\n\tremote = included\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "included");
+ assert_eq!(upstream.full_ref, "refs/remotes/included/main");
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_reads_linked_worktree_config() {
+ let base = temp_test_dir("linked-worktree-config");
+ let common_dir = base.join("repo/.git");
+ let worktree = base.join("linked");
+ let git_dir = common_dir.join("worktrees/linked");
+ std::fs::create_dir_all(common_dir.join("refs/heads")).unwrap();
+ std::fs::create_dir_all(common_dir.join("refs/remotes/fork")).unwrap();
+ std::fs::create_dir_all(&git_dir).unwrap();
+ std::fs::create_dir_all(&worktree).unwrap();
+ std::fs::write(
+ worktree.join(".git"),
+ format!("gitdir: {}\n", git_dir.display()),
+ )
+ .unwrap();
+ std::fs::write(git_dir.join("commondir"), "../..\n").unwrap();
+ std::fs::write(git_dir.join("HEAD"), "ref: refs/heads/main\n").unwrap();
+ std::fs::write(
+ common_dir.join("refs/heads/main"),
+ "1111111111111111111111111111111111111111\n",
+ )
+ .unwrap();
+ std::fs::write(
+ common_dir.join("refs/remotes/fork/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ common_dir.join("config"),
+ "[extensions]\n\tworktreeConfig = TRUE\n[remote \"fork\"]\n\tfetch = +refs/heads/*:refs/remotes/fork/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+ std::fs::write(
+ git_dir.join("config.worktree"),
+ "[branch \"main\"]\n\tremote = fork\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&worktree).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.remote, "fork");
+ assert_eq!(upstream.full_ref, "refs/remotes/fork/main");
+
+ std::fs::remove_dir_all(base).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_ignores_inline_fetch_refspec_comment() {
+ let root = temp_test_dir("commented-fetch-refspec");
+ write_fake_tracked_repo(&root);
+ std::fs::remove_dir_all(root.join(".git/refs/remotes/origin")).unwrap();
+ std::fs::create_dir_all(root.join(".git/refs/remotes/upstream")).unwrap();
+ std::fs::write(
+ root.join(".git/refs/remotes/upstream/main"),
+ "2222222222222222222222222222222222222222\n",
+ )
+ .unwrap();
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"origin\"]\n\tfetch = +refs/heads/*:refs/remotes/upstream/* # custom map\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.full_ref, "refs/remotes/upstream/main");
+ assert_eq!(
+ upstream.oid.as_deref(),
+ Some("2222222222222222222222222222222222222222")
+ );
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_clears_upstream_for_unmapped_refspec() {
+ let root = temp_test_dir("unmapped-fetch-refspec");
+ write_fake_tracked_repo(&root);
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"origin\"]\n\tfetch = +refs/pull/*:refs/remotes/origin/pr/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ assert_eq!(fingerprint.upstream, None);
+
+ std::fs::remove_dir_all(root).unwrap();
+}
+
+#[test]
+fn git_status_fingerprint_honors_negative_fetch_refspec() {
+ let root = temp_test_dir("negative-fetch-refspec");
+ write_fake_tracked_repo(&root);
+ std::fs::write(
+ root.join(".git/config"),
+ "[remote \"origin\"]\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n\tfetch = ^refs/heads/main\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
+ )
+ .unwrap();
+
+ let fingerprint = git_status_fingerprint(&root).unwrap();
+
+ let upstream = fingerprint.upstream.unwrap();
+ assert_eq!(upstream.full_ref, "refs/remotes/origin/main");
+ assert_eq!(
+ upstream.oid.as_deref(),
+ Some("2222222222222222222222222222222222222222")
+ );
+
+ std::fs::remove_dir_all(root).unwrap();
+}