Skip to content

Commit be08b7f

Browse files
committed
imp(cache): initial implementation for repo change detection
1 parent bf8de1f commit be08b7f

6 files changed

Lines changed: 89 additions & 8 deletions

File tree

core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ ignore = "0.4.23"
1414
serde = { version = "1.0.219", features = ["derive"] }
1515
serde_json = "1.0.140"
1616
bincode = {version= "2.0.1", features = ["serde"] }
17+
git2 = { version = "0.20.2" }
18+
sha2 = { version = "0.10.9" }
1719

1820
[dev-dependencies]
1921
tempfile = "3.8"

core/src/cache.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
use std::io::{Read, Write};
22
use std::path::{Path, PathBuf};
33

4-
use crate::common::{collect_owners, collect_tags, find_owners_for_file, find_tags_for_file};
4+
use crate::common::{
5+
collect_owners, collect_tags, find_owners_for_file, find_tags_for_file, get_repo_hash,
6+
};
7+
use crate::parse::parse_repo;
58
use crate::types::{CacheEncoding, CodeownersCache, CodeownersEntry, FileEntry};
69
use utils::error::{Error, Result};
710

811
/// Create a cache from parsed CODEOWNERS entries and files
9-
pub fn build_cache(entries: Vec<CodeownersEntry>, files: Vec<PathBuf>) -> Result<CodeownersCache> {
12+
pub fn build_cache(
13+
entries: Vec<CodeownersEntry>, files: Vec<PathBuf>, hash: [u8; 32],
14+
) -> Result<CodeownersCache> {
1015
let mut file_entries = Vec::new();
1116
let mut owners_map = std::collections::HashMap::new();
1217
let mut tags_map = std::collections::HashMap::new();
@@ -48,6 +53,7 @@ pub fn build_cache(entries: Vec<CodeownersEntry>, files: Vec<PathBuf>) -> Result
4853
});
4954

5055
Ok(CodeownersCache {
56+
hash,
5157
entries,
5258
files: file_entries,
5359
owners_map,
@@ -143,11 +149,24 @@ pub fn sync_cache(
143149
}
144150

145151
// Load the cache from the specified file
146-
load_cache(&repo.join(cache_file)).map_err(|e| {
152+
let cache = load_cache(&repo.join(cache_file)).map_err(|e| {
147153
utils::error::Error::new(&format!(
148154
"Failed to load cache from {}: {}",
149155
cache_file.display(),
150156
e
151157
))
152-
})
158+
})?;
159+
160+
// verify the hash of the cache matches the current repo hash
161+
let current_hash = get_repo_hash(repo)?;
162+
dbg!(current_hash);
163+
let cache_hash = cache.hash;
164+
dbg!(cache_hash);
165+
166+
if cache_hash != current_hash {
167+
// parse the codeowners files and build the cache
168+
return parse_repo(&repo, &cache_file);
169+
} else {
170+
return Ok(cache);
171+
}
153172
}

core/src/commands.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::io::{self, Write};
22

33
use crate::cache::{build_cache, load_cache, store_cache, sync_cache};
4-
use crate::common::find_files;
4+
use crate::common::{find_files, get_repo_hash};
55
use crate::parse::parse_repo;
66
use crate::types::{CacheEncoding, CodeownersCache, CodeownersEntry, OutputFormat};
77

@@ -22,6 +22,10 @@ pub fn codeowners_parse(
2222
) -> Result<()> {
2323
println!("Parsing CODEOWNERS files at {}", path.display());
2424

25+
let hash = get_repo_hash(path.as_ref())?;
26+
dbg!(hash);
27+
panic!();
28+
2529
let cache_file = match cache_file {
2630
Some(file) => path.join(file),
2731
None => {
@@ -47,7 +51,8 @@ pub fn codeowners_parse(
4751
let files = find_files(path)?;
4852

4953
// Build the cache from the parsed CODEOWNERS entries and the files
50-
let cache = build_cache(parsed_codeowners, files)?;
54+
let hash = get_repo_hash(path)?;
55+
let cache = build_cache(parsed_codeowners, files, hash)?;
5156

5257
// Store the cache in the specified file
5358
store_cache(&cache, &cache_file, encoding)?;

core/src/common.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use git2::{DiffFormat, DiffOptions, Repository};
12
use ignore::{
23
Walk,
34
overrides::{Override, OverrideBuilder},
45
};
6+
use sha2::{Digest, Sha256};
57
use std::path::{Path, PathBuf};
68
use utils::error::{Error, Result};
79

@@ -357,6 +359,52 @@ pub fn collect_tags(entries: &[CodeownersEntry]) -> Vec<Tag> {
357359
tags.into_iter().collect()
358360
}
359361

362+
pub fn get_repo_hash(repo_path: &Path) -> Result<[u8; 32]> {
363+
let repo = Repository::open(repo_path)
364+
.map_err(|e| Error::with_source("Failed to open repo", Box::new(e)))?;
365+
366+
// 1. Get HEAD commit hash (or zeros if unborn)
367+
let head_oid = repo
368+
.head()
369+
.and_then(|r| r.resolve())
370+
.and_then(|r| Ok(r.target()))
371+
.unwrap_or(None);
372+
373+
// 2. Get index/staging area tree hash
374+
let mut index = repo
375+
.index()
376+
.map_err(|e| Error::with_source("Failed to get index", Box::new(e)))?;
377+
378+
let index_tree = index
379+
.write_tree()
380+
.map_err(|e| Error::with_source("Failed to write index tree", Box::new(e)))?;
381+
382+
// 3. Calculate hash of unstaged changes
383+
// TODO: this doesn't work and also we need to exclude .codeowners.cache file
384+
// otherwise the hash will change every time we parse the repo
385+
let unstaged_hash = {
386+
let diff = repo
387+
.diff_index_to_workdir(None, Some(DiffOptions::new().include_untracked(true)))
388+
.map_err(|e| Error::with_source("Failed to get diff", Box::new(e)))?;
389+
390+
let mut hasher = Sha256::new();
391+
diff.print(DiffFormat::Patch, |_, _, line| {
392+
hasher.update(line.content());
393+
true
394+
})
395+
.map_err(|e| Error::with_source("Failed to print diff", Box::new(e)))?;
396+
hasher.finalize()
397+
};
398+
399+
// 4. Combine all components into final hash
400+
let mut hasher = Sha256::new();
401+
hasher.update(head_oid.unwrap_or(git2::Oid::zero()).as_bytes());
402+
hasher.update(index_tree.as_bytes());
403+
hasher.update(&unstaged_hash);
404+
405+
Ok(hasher.finalize().into())
406+
}
407+
360408
#[cfg(test)]
361409
mod tests {
362410
use super::*;

core/src/parse.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use utils::error::Result;
22

33
use crate::{
44
cache::{build_cache, store_cache},
5-
common::{find_codeowners_files, find_files, parse_codeowners},
5+
common::{find_codeowners_files, find_files, get_repo_hash, parse_codeowners},
66
types::{CacheEncoding, CodeownersCache, CodeownersEntry},
77
};
88

@@ -25,8 +25,11 @@ pub fn parse_repo(repo: &std::path::Path, cache_file: &std::path::Path) -> Resul
2525
// Collect all files in the specified path
2626
let files = find_files(repo)?;
2727

28+
// Get the hash of the repository
29+
let hash = get_repo_hash(repo)?;
30+
2831
// Build the cache from the parsed CODEOWNERS entries and the files
29-
let cache = build_cache(parsed_codeowners, files)?;
32+
let cache = build_cache(parsed_codeowners, files, hash)?;
3033

3134
// Store the cache in the specified file
3235
store_cache(&cache, &repo.join(cache_file), CacheEncoding::Bincode)?;

core/src/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub struct FileEntry {
7474
/// Cache for storing parsed CODEOWNERS information
7575
#[derive(Debug)]
7676
pub struct CodeownersCache {
77+
pub hash: [u8; 32],
7778
pub entries: Vec<CodeownersEntry>,
7879
pub files: Vec<FileEntry>,
7980
// Derived data for lookups
@@ -89,6 +90,7 @@ impl Serialize for CodeownersCache {
8990
use serde::ser::SerializeStruct;
9091

9192
let mut state = serializer.serialize_struct("CodeownersCache", 4)?;
93+
state.serialize_field("hash", &self.hash)?;
9294
state.serialize_field("entries", &self.entries)?;
9395
state.serialize_field("files", &self.files)?;
9496

@@ -112,6 +114,7 @@ impl<'de> Deserialize<'de> for CodeownersCache {
112114
{
113115
#[derive(Deserialize)]
114116
struct CodeownersCacheHelper {
117+
hash: [u8; 32],
115118
entries: Vec<CodeownersEntry>,
116119
files: Vec<FileEntry>,
117120
owners_map: Vec<(Owner, Vec<PathBuf>)>,
@@ -132,6 +135,7 @@ impl<'de> Deserialize<'de> for CodeownersCache {
132135
}
133136

134137
Ok(CodeownersCache {
138+
hash: helper.hash,
135139
entries: helper.entries,
136140
files: helper.files,
137141
owners_map,

0 commit comments

Comments
 (0)