relay_knowledge/domain/code/repository/
scope_identity.rs1const CODE_SNAPSHOT_FACT_VERSION: &str = "code-facts-js-ts-import-edges-v1-sbom-dependencies-v2-python-type-refs-v1-scope-compat-v1-workspace-imports-v1-generated-files-v1-web-routes-v1-syntax-failure-chunks-v1-bounded-config-chunks-v1-dense-source-windows-v1-c-composite-tags-v1";
2
3pub fn code_snapshot_scope_id(
5 repository_id: &str,
6 tree_hash: &str,
7 path_filters: &[String],
8 language_filters: &[String],
9) -> String {
10 let mut input = Vec::new();
11 append_hash_part(&mut input, "git_snapshot");
12 append_hash_part(&mut input, repository_id);
13 append_hash_part(&mut input, tree_hash);
14 append_hash_list(&mut input, path_filters);
15 append_hash_list(&mut input, language_filters);
16 append_hash_part(&mut input, CODE_SNAPSHOT_FACT_VERSION);
17
18 format!("git_snapshot:{:016x}", stable_hash64(&input))
19}
20
21pub fn code_snapshot_expected_scope_id(
22 repository_id: &str,
23 tree_hash: &str,
24 path_filters: &[String],
25 language_filters: &[String],
26) -> Option<String> {
27 Some(code_snapshot_scope_id(
28 repository_id,
29 tree_hash,
30 path_filters,
31 language_filters,
32 ))
33}
34
35pub fn code_snapshot_scope_is_fact_versioned(source_scope: &str) -> bool {
36 let Some(scope_hash) = source_scope.strip_prefix("git_snapshot:") else {
37 return false;
38 };
39 scope_hash.len() == 16
40 && scope_hash
41 .chars()
42 .all(|character| character.is_ascii_hexdigit())
43}
44
45fn append_hash_list(input: &mut Vec<u8>, values: &[String]) {
46 input.extend_from_slice(&(values.len() as u64).to_le_bytes());
47 for value in values {
48 append_hash_part(input, value);
49 }
50}
51
52fn append_hash_part(input: &mut Vec<u8>, value: &str) {
53 input.extend_from_slice(&(value.len() as u64).to_le_bytes());
54 input.extend_from_slice(value.as_bytes());
55}
56
57fn stable_hash64(bytes: &[u8]) -> u64 {
58 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
59 const FNV_PRIME: u64 = 0x100000001b3;
60
61 let mut hash = FNV_OFFSET_BASIS;
62 for byte in bytes {
63 hash ^= u64::from(*byte);
64 hash = hash.wrapping_mul(FNV_PRIME);
65 }
66
67 hash
68}
69
70#[cfg(test)]
71#[path = "scope_identity_tests.rs"]
72mod tests;