Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions hubs/cli-git.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
summary: Best-effort git queries for scoping and rename-following — advisory only, the gate never depends on them.
anchors:
- claim: >
changed_files returns the repo-relative paths changed between the merge base of base..HEAD
and the working tree, used to diff-scope the check. A missing merge base (shallow clone)
falls back to diffing the ref directly; if git can't answer at all it returns None.
changed_files returns the workspace-root-relative paths changed between the merge base of
base..HEAD and the working tree (via git diff --relative), used to diff-scope the check —
so the set intersects workspace-relative anchors even when the workspace is a repo
subdirectory. A missing merge base (shallow clone) falls back to diffing the ref directly;
if git can't answer at all it returns None.
at: surf-cli/src/git.rs > changed_files
hash: 9f422d548239
hash: 454e65cc8aa3
- claim: >
show returns the contents of a file at a git ref (git show <base>:<path>), used to recover
the previous source for advisory old_code/magnitude. None when unavailable — the verdict is
Expand Down
54 changes: 54 additions & 0 deletions surf-cli/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,60 @@ mod tests {
assert!(unmatched.is_empty(), "glob matched an anchored file");
}

#[test]
fn base_scope_works_when_workspace_is_a_repo_subdir() {
// The workspace (surf.toml) sits in `proj/`, a subdirectory of the git repo. A real
// logic drift in an anchored file must still fail `check --base`. Regression for the
// silent bypass where `git diff` emitted repo-root-relative paths (`proj/src/m.rs`)
// that never matched the workspace-relative anchor (`src/m.rs`), scoping the gate to
// zero claims and exiting clean.
let tmp = tempfile::tempdir().unwrap();
let repo = tmp.path();
let proj = repo.join("proj");

let v1 = "pub fn add(a: i64, b: i64) -> i64 { a + b }\n";
let h = stored_hash(v1, "src/m.rs > add");
write(&proj, "surf.toml", "");
write(&proj, "src/m.rs", v1);
write(
&proj,
"hubs/a.md",
&format!("---\nsummary: x\nanchors:\n - claim: add sums\n at: src/m.rs > add\n hash: {h}\n---\n"),
);

// Initialize the repo at the parent, not the workspace.
git(repo, &["init", "-q"]);
git(
repo,
&["-c", "user.email=t@t", "-c", "user.name=t", "add", "."],
);
git(
repo,
&[
"-c",
"user.email=t@t",
"-c",
"user.name=t",
"commit",
"-q",
"-m",
"v1",
],
);

// Diverge the anchored span in the working tree.
write(
&proj,
"src/m.rs",
"pub fn add(a: i64, b: i64) -> i64 { a - b }\n",
);

let ws = ws_at(proj.clone());
let scoped = check_workspace(&ws, Some("HEAD"), &[]).unwrap().0;
assert_eq!(scoped.len(), 1, "subdir --base must still catch the drift");
assert_eq!(scoped[0].kind, DivergenceKind::Changed);
}

#[test]
fn no_flags_checks_everything() {
let tmp = tempfile::tempdir().unwrap();
Expand Down
11 changes: 8 additions & 3 deletions surf-cli/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ use std::path::Path;
use std::process::Command;

/// Files changed between the merge base of `base`..HEAD and the working tree. Paths are
/// repo-root-relative; they match `Anchor.file` (workspace-root-relative) when the workspace
/// root is the repo root, the normal case. `None` if git can't answer.
/// emitted relative to `root` (the workspace root) via `--relative`, so they match
/// `Anchor.file` (also workspace-root-relative) even when the workspace is a *subdirectory*
/// of the git repo — without `--relative`, `git diff` reports repo-root-relative paths
/// (e.g. `proj/src/x.rs`) that never intersect a workspace-relative anchor (`src/x.rs`),
/// silently scoping the `--base` gate to zero claims and passing real drift (exit 0).
/// `--relative` also drops changes outside the workspace, which can never be anchored anyway.
/// `None` if git can't answer.
pub fn changed_files(root: &Path, base: &str) -> Option<HashSet<String>> {
let merge_base = Command::new("git")
.current_dir(root)
Expand All @@ -23,7 +28,7 @@ pub fn changed_files(root: &Path, base: &str) -> Option<HashSet<String>> {

let output = Command::new("git")
.current_dir(root)
.args(["diff", "--name-only", &merge_base])
.args(["diff", "--name-only", "--relative", &merge_base])
.output()
.ok()?;
output.status.success().then(|| {
Expand Down
Loading