diff --git a/.gitignore b/.gitignore index 08cb97f60..1921454b2 100644 --- a/.gitignore +++ b/.gitignore @@ -197,7 +197,7 @@ mise.local.toml architecture/plans # Claude -.claude/settings.local.json.claude/worktrees/ +.claude/settings.local.json .claude/worktrees/ rfc.md .worktrees diff --git a/Cargo.lock b/Cargo.lock index 3f50e57b4..967cce7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2785,7 +2785,7 @@ dependencies = [ "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.4", "rand_xoshiro", "sketches-ddsketch", ] diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index 2f03159de..ceefa074b 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -661,7 +661,7 @@ mod tests { let (stream, _) = listener.accept().await.expect("test stub should accept"); let log = log_for_task.clone(); let queue = queue_for_task.clone(); - http1::Builder::new() + let result = http1::Builder::new() .serve_connection( TokioIo::new(stream), service_fn(move |req| { @@ -690,8 +690,12 @@ mod tests { } }), ) - .await - .expect("test stub should serve request"); + .await; + // The one-shot test client can close the Unix socket after the + // response, which Hyper reports as a shutdown error. Let the + // request log assertions below decide whether the stub served + // the expected API calls. + let _ = result; } let _ = std::fs::remove_file(&socket_path_for_task); }); diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index 95bf89d07..d11f95b78 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -253,7 +253,7 @@ mod tests { let (stream, _) = listener.accept().await.expect("test stub should accept"); let log = log_for_task.clone(); let queue = queue_for_task.clone(); - http1::Builder::new() + let result = http1::Builder::new() .serve_connection( TokioIo::new(stream), service_fn(move |req| { @@ -282,8 +282,12 @@ mod tests { } }), ) - .await - .expect("test stub should serve request"); + .await; + // The one-shot test client can close the Unix socket after the + // response, which Hyper reports as a shutdown error. Let the + // request log assertions below decide whether the stub served + // the expected API calls. + let _ = result; } let _ = std::fs::remove_file(&socket_path_for_task); }); diff --git a/scripts/update_license_headers.py b/scripts/update_license_headers.py index 11813dbf3..3775f4cb7 100755 --- a/scripts/update_license_headers.py +++ b/scripts/update_license_headers.py @@ -20,6 +20,7 @@ import argparse import os +import subprocess import sys from pathlib import Path @@ -113,11 +114,48 @@ def is_excluded(rel: Path) -> bool: return True # Prefix exclusions (CI config, editor config). - for prefix in EXCLUDE_DIR_PREFIXES: - if rel_str.startswith(prefix): - return True - - return False + return any(rel_str.startswith(prefix) for prefix in EXCLUDE_DIR_PREFIXES) + + +def git_candidate_files(root: Path) -> list[Path] | None: + """Return Git-tracked and unignored files, or None if Git is unavailable.""" + try: + result = subprocess.run( + [ + "git", + "-C", + str(root), + "ls-files", + "-z", + "--cached", + "--others", + "--exclude-standard", + ], + check=True, + capture_output=True, + ) + except (OSError, subprocess.CalledProcessError): + return None + + files = [] + for raw_path in result.stdout.split(b"\0"): + if raw_path: + files.append(Path(os.fsdecode(raw_path))) + return files + + +def is_git_ignored(root: Path, rel: Path) -> bool: + """Return True if Git ignore rules exclude a path.""" + try: + result = subprocess.run( + ["git", "-C", str(root), "check-ignore", "-q", "--", str(rel)], + check=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except OSError: + return False + return result.returncode == 0 def is_dockerfile(path: Path) -> bool: @@ -135,6 +173,19 @@ def get_comment_style(path: Path) -> str | None: def discover_files(root: Path) -> list[Path]: """Walk the repo and return all files that should have headers.""" results = [] + + git_files = git_candidate_files(root) + if git_files is not None: + for rel in git_files: + path = root / rel + if not path.is_file(): + continue + if is_excluded(rel): + continue + if get_comment_style(rel) is not None: + results.append(path) + return sorted(results) + for dirpath, dirnames, filenames in os.walk(root): rel_dir = Path(dirpath).relative_to(root) @@ -161,10 +212,7 @@ def discover_files(root: Path) -> list[Path]: def has_header(lines: list[str]) -> bool: """Check if the SPDX header is present in the first 10 lines.""" - for line in lines[:10]: - if SPDX_MARKER in line: - return True - return False + return any(SPDX_MARKER in line for line in lines[:10]) def find_insertion_point(lines: list[str], path: Path) -> int: @@ -276,8 +324,11 @@ def main() -> int: p = p.resolve() if not p.is_file(): continue - rel = p.relative_to(root) - if is_excluded(rel): + try: + rel = p.relative_to(root) + except ValueError: + continue + if is_excluded(rel) or is_git_ignored(root, rel): continue if get_comment_style(rel) is not None: files.append(p) @@ -301,7 +352,6 @@ def main() -> int: print("All files have SPDX headers.") return 0 - added = len(missing) # In non-check mode, missing list is empty; count via verbose print("Done.") return 0