fix: better validate for dev_brain and 50k reminder not triggering rebuild

This commit is contained in:
Georg Bauer
2026-07-27 23:08:47 +02:00
parent d371d9d659
commit 90a445cafd
2 changed files with 119 additions and 20 deletions

View File

@@ -72,12 +72,13 @@ sources:
- project: Registered project name
path: src/example.rs
symbol: optional_symbol
revision: exact-clean-git-revision
# Use hash instead of revision when the worktree is dirty or is not Git.
revision: full-or-unique-short-clean-git-revision
# Use `git rev-parse HEAD`, or a unique lowercase hex prefix of at least 7 characters.
# Use hash instead when the worktree is dirty or is not Git.
---
```
Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. Paths are project-relative and may not escape the registered project.
Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. A revision is the current clean commit's full object ID or a unique lowercase hexadecimal prefix of at least 7 characters. Paths are project-relative and may not escape the registered project.
## Compilation
@@ -1081,9 +1082,9 @@ fn validate_source(source: &SourceRecord, projects: &[RegisteredProject]) -> Res
));
}
if let Some(revision) = &source.revision {
return Ok(
git_state(&project.root).is_some_and(|(current, clean)| clean && current == *revision)
);
return Ok(git_state(&project.root).is_some_and(|(current, clean)| {
clean && git_revision_matches(&project.root, revision, &current)
}));
}
let expected = source.hash.as_deref().unwrap();
if expected.len() != 64
@@ -1100,6 +1101,29 @@ fn validate_source(source: &SourceRecord, projects: &[RegisteredProject]) -> Res
&& !git_state(&project.root).is_some_and(|(_, clean)| clean))
}
fn git_revision_matches(root: &Path, revision: &str, current: &str) -> bool {
if revision == current {
return true;
}
if revision.len() < 7
|| revision.len() >= current.len()
|| !revision
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return false;
}
Command::new("git")
.args(["-C"])
.arg(root)
.args(["rev-parse", "--verify"])
.arg(format!("{revision}^{{commit}}"))
.output()
.ok()
.filter(|output| output.status.success())
.is_some_and(|output| String::from_utf8_lossy(&output.stdout).trim() == current)
}
fn git_state(root: &Path) -> Option<(String, bool)> {
let revision = Command::new("git")
.args(["-C"])
@@ -1560,7 +1584,7 @@ mod tests {
}
#[test]
fn clean_git_revisions_become_stale_when_the_worktree_changes() {
fn clean_full_and_short_git_revisions_become_stale_when_the_worktree_changes() {
let fixture = Fixture::new();
for arguments in [
vec!["init"],
@@ -1586,11 +1610,21 @@ mod tests {
);
}
let revision = git_state(&fixture.project).unwrap().0;
assert!(git_revision_matches(
&fixture.project,
&revision[..7],
&revision
));
assert!(!git_revision_matches(
&fixture.project,
&revision[..6],
&revision
));
let topic = fixture
.topic("verified", &"0".repeat(64), "Revision-backed knowledge.")
.replace(
&format!("hash: {}", "0".repeat(64)),
&format!("revision: {revision}"),
&format!("revision: {}", &revision[..7]),
);
let mut brain = fixture.brain();
write_topic(&fixture, &mut brain, &topic).unwrap();