Skip to content

Commit a2f8e04

Browse files
committed
✅ test(01-04): phase-1 crosswise suite with parity fixes
New phase1_crosswise target (12 tests, twin-dir hermetic harness against the tree 2.55 oracle): reflog show after mutations plus @{n} recovery, update-ref single/stdin-NUL batches plus lock contention, packed-refs both directions, config local/scope/blob/ stdin reads, sequential mutations, includes honored. Registered phase1-crosswise in xtask suites. Parity fixes forced green by the suite (all probed on ./git): - update-ref to the same value writes nothing and logs nothing, except the HEAD log-only entry on the current branch - config --no-includes now skips include-following on the layered read (follow_includes threaded through the git-config loader) - scope origins: global/system absolute verbatim, repo files worktree-root-relative (.git/config from anywhere, config bare) - fresh-file appends end in exactly one newline; new trailing sections land flush without a blank separator
1 parent 4560234 commit a2f8e04

7 files changed

Lines changed: 697 additions & 19 deletions

File tree

‎crates/git-command/src/config_cmd.rs‎

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,35 @@ fn display_path(path: &Path) -> String {
824824
path.display().to_string()
825825
}
826826

827+
/// C's display name for a scope-selected file: global/system files
828+
/// print absolute and verbatim (never relativized, even under the
829+
/// cwd); repo files render relative to the work-tree root
830+
/// (`.git/config` from anywhere) or the git dir when bare
831+
/// (`config`), matching C's post-chdir layout.
832+
fn scope_display_name(
833+
repo: Option<&git_core::Repository>,
834+
scope: ConfigScope,
835+
path: &std::path::Path,
836+
) -> String {
837+
if matches!(scope, ConfigScope::Global | ConfigScope::System) {
838+
return path.display().to_string();
839+
}
840+
if let Some(r) = repo {
841+
if let Some(wt) = &r.work_tree {
842+
if let Ok(rel) = path.strip_prefix(wt) {
843+
if !rel.as_os_str().is_empty() {
844+
return rel.display().to_string();
845+
}
846+
}
847+
} else if let Ok(rel) = path.strip_prefix(&r.git_dir) {
848+
if !rel.as_os_str().is_empty() {
849+
return rel.display().to_string();
850+
}
851+
}
852+
}
853+
display_path(path)
854+
}
855+
827856
/// C `die_errno` reason for config-file reads: the bare `strerror` text.
828857
fn io_reason(e: &std::io::Error) -> String {
829858
match e.kind() {
@@ -1008,7 +1037,7 @@ fn load_read_source(
10081037
let repo = repo.ok_or_else(|| CommandError::fatal("fatal: not in a git directory"))?;
10091038
scope_file(repo, loc)?
10101039
};
1011-
let display = display_path(&path);
1040+
let display = scope_display_name(repo, scope, &path);
10121041
// Existence check first: missing reads as empty (list dies on the
10131042
// marker below), unreadable dies — like C.
10141043
let data = match std::fs::read(&path) {
@@ -1066,7 +1095,9 @@ fn load_read_source(
10661095
worktree: repo.work_tree.clone(),
10671096
git_dir_verbatim: repo.git_dir_specified.clone(),
10681097
};
1069-
let scoped = ConfigSet::load_repo_scoped(&scopes).map_err(|e| match e {
1098+
// C skips include-following on the layered read under
1099+
// `--no-includes` (the flag defaults on only with no selector).
1100+
let scoped = ConfigSet::load_repo_scoped(&scopes, respect).map_err(|e| match e {
10701101
ConfigError::BadLine { line, file } => {
10711102
let name = file
10721103
.map(|p| display_path(&p))
@@ -1080,7 +1111,7 @@ fn load_read_source(
10801111
.entry
10811112
.origin
10821113
.as_ref()
1083-
.map(|p| display_path(p))
1114+
.map(|p| scope_display_name(Some(repo), s.scope, p))
10841115
.unwrap_or_default();
10851116
entries.push(LoadedEntry { scope: s.scope, origin: OriginType::File, filename, entry: s.entry });
10861117
}
@@ -1095,7 +1126,7 @@ fn load_read_source(
10951126
roots.push((ConfigScope::Global, g));
10961127
}
10971128
for (scope, path) in roots {
1098-
let display = display_path(&path);
1129+
let display = scope_display_name(repo, scope, &path);
10991130
if !path.exists() {
11001131
continue;
11011132
}
@@ -2567,8 +2598,15 @@ fn write_display_name(ctx: &RepoContext, repo: Option<&git_core::Repository>, lo
25672598
if let Some(file) = &loc.file {
25682599
return file.clone();
25692600
}
2601+
let scope = if loc.global {
2602+
ConfigScope::Global
2603+
} else if loc.system {
2604+
ConfigScope::System
2605+
} else {
2606+
ConfigScope::Local
2607+
};
25702608
match write_target_file(ctx, repo, loc) {
2571-
Ok(p) => display_path(&p),
2609+
Ok(p) => scope_display_name(repo, scope, &p),
25722610
Err(_) => ".git/config".to_string(),
25732611
}
25742612
}

‎crates/git-command/src/update_ref.rs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ fn log_single_update(
294294
) {
295295
let target = store.deref_name_opt(name, deref);
296296
let new_now = store.resolve(&target).unwrap_or(*new);
297+
// C logs (and writes) nothing for a no-op oid update: old ==
298+
// new on a non-symref target, even with --create-reflog — except
299+
// that a no-op on the current branch still appends the HEAD
300+
// log-only entry (C `split_head_update`).
301+
if old == &new_now && read_symref_target(repo, &target).is_none() {
302+
if store.head_symbolic_target().as_deref() == Some(target.as_str()) && target != "HEAD" {
303+
if let Ok(ident) = crate::checkout_core::committer_ident(repo) {
304+
git_refs::reflog::log_update(repo, "HEAD", old, &new_now, &ident, msg);
305+
}
306+
}
307+
return;
308+
}
297309
let Ok(ident) = crate::checkout_core::committer_ident(repo) else { return };
298310
// C forces the log with `--create-reflog`; otherwise the standard
299311
// gating applies (`--no-create-reflog` is an accepted no-op).
@@ -796,6 +808,18 @@ impl<'x> BatchRunner<'x> {
796808
}
797809
None => continue,
798810
};
811+
// C logs (and writes) nothing for a no-op oid update —
812+
// except the HEAD log-only entry when the current branch
813+
// is the target (C `split_head_update`).
814+
if !plog.is_symref_write
815+
&& new == plog.old
816+
&& read_symref_target(&self.repo, &plog.target).is_none()
817+
{
818+
if head_target.as_deref() == Some(plog.target.as_str()) && plog.target != "HEAD" {
819+
self.append_log("HEAD", &plog.old, &new, &ident);
820+
}
821+
continue;
822+
}
799823
self.append_log(&plog.target, &plog.old, &new, &ident);
800824
if plog.target != plog.name {
801825
self.append_log(&plog.name, &plog.old, &new, &ident);

0 commit comments

Comments
 (0)