-
Notifications
You must be signed in to change notification settings - Fork 0
Migrate commit-atomicity.yml to Rust (atomicity-checker) #720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 🔍 Commit Atomicity - Ensures commits follow the atomic commit principle | ||
| # Part of Git-Core Protocol | ||
|
|
||
| name: 🔍 Commit Atomicity | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| types: [opened, synchronize, reopened] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
|
|
||
| jobs: | ||
| check-atomicity: | ||
| name: Check Atomicity | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: 📋 Checkout | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # Full history needed for commit analysis | ||
|
|
||
| - name: 🦀 Setup Rust | ||
| uses: dtolnay/rust-toolchain@stable | ||
|
|
||
| - name: 🏗️ Build Atomicity Checker | ||
| run: cargo build --release -p atomicity-checker | ||
|
|
||
| - name: 🔍 Run Atomicity Check | ||
| run: | | ||
| ./target/release/atomicity-checker \ | ||
| --range "origin/${{ github.base_ref }}..HEAD" \ | ||
| --config ".github/atomicity-config.yml" | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||||||
| [package] | ||||||||||||||||||||||||||
| name = "atomicity-checker" | ||||||||||||||||||||||||||
| version.workspace = true | ||||||||||||||||||||||||||
| edition.workspace = true | ||||||||||||||||||||||||||
| license.workspace = true | ||||||||||||||||||||||||||
| authors.workspace = true | ||||||||||||||||||||||||||
| repository.workspace = true | ||||||||||||||||||||||||||
| description.workspace = true | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| [dependencies] | ||||||||||||||||||||||||||
| clap = { workspace = true } | ||||||||||||||||||||||||||
| serde = { workspace = true } | ||||||||||||||||||||||||||
| serde_json = { workspace = true } | ||||||||||||||||||||||||||
| serde_yaml = "0.9" | ||||||||||||||||||||||||||
| regex = { workspace = true } | ||||||||||||||||||||||||||
| anyhow = { workspace = true } | ||||||||||||||||||||||||||
| colored = "2" | ||||||||||||||||||||||||||
| git2 = "0.19" | ||||||||||||||||||||||||||
| globset = "0.4" | ||||||||||||||||||||||||||
|
Comment on lines
+14
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For better build reproducibility and to avoid potential issues from transitive dependencies, it's a good practice to pin your dependencies to more specific versions rather than using broad version requirements.
Suggested change
|
||||||||||||||||||||||||||
| chrono = { workspace = true } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,141 @@ | ||||||||||||||||||||||||||||||||||||||||
| use crate::config::Config; | ||||||||||||||||||||||||||||||||||||||||
| use crate::models::{CommitInfo, FileInfo}; | ||||||||||||||||||||||||||||||||||||||||
| use globset::{Glob, GlobSet, GlobSetBuilder}; | ||||||||||||||||||||||||||||||||||||||||
| use anyhow::Result; | ||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashSet; | ||||||||||||||||||||||||||||||||||||||||
| use regex::Regex; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| pub struct Checker { | ||||||||||||||||||||||||||||||||||||||||
| config: Config, | ||||||||||||||||||||||||||||||||||||||||
| concern_globs: Vec<(String, GlobSet)>, | ||||||||||||||||||||||||||||||||||||||||
| exclude_glob: GlobSet, | ||||||||||||||||||||||||||||||||||||||||
| bot_regexes: Vec<Regex>, | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| impl Checker { | ||||||||||||||||||||||||||||||||||||||||
| pub fn new(config: Config) -> Result<Self> { | ||||||||||||||||||||||||||||||||||||||||
| let mut concern_globs = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||
| for (concern, patterns) in &config.concern_patterns { | ||||||||||||||||||||||||||||||||||||||||
| let mut builder = GlobSetBuilder::new(); | ||||||||||||||||||||||||||||||||||||||||
| for pattern in patterns { | ||||||||||||||||||||||||||||||||||||||||
| builder.add(Glob::new(pattern)?); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| concern_globs.push((concern.clone(), builder.build()?)); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-deterministic iteration order may cause inconsistent categorization.
🔧 Proposed fix to ensure deterministic ordering+ let mut sorted_concerns: Vec<_> = config.concern_patterns.iter().collect();
+ sorted_concerns.sort_by_key(|(k, _)| k.clone());
+
let mut concern_globs = Vec::new();
- for (concern, patterns) in &config.concern_patterns {
+ for (concern, patterns) in sorted_concerns {
let mut builder = GlobSetBuilder::new();
for pattern in patterns {
builder.add(Glob::new(pattern)?);
}
concern_globs.push((concern.clone(), builder.build()?));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let mut exclude_builder = GlobSetBuilder::new(); | ||||||||||||||||||||||||||||||||||||||||
| for pattern in &config.exclude_patterns { | ||||||||||||||||||||||||||||||||||||||||
| exclude_builder.add(Glob::new(pattern)?); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| let exclude_glob = exclude_builder.build()?; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let mut bot_regexes = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||
| for pattern in &config.bot_patterns { | ||||||||||||||||||||||||||||||||||||||||
| bot_regexes.push(Regex::new(pattern)?); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Ok(Checker { | ||||||||||||||||||||||||||||||||||||||||
| config, | ||||||||||||||||||||||||||||||||||||||||
| concern_globs, | ||||||||||||||||||||||||||||||||||||||||
| exclude_glob, | ||||||||||||||||||||||||||||||||||||||||
| bot_regexes, | ||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| pub fn is_bot(&self, author: &str) -> bool { | ||||||||||||||||||||||||||||||||||||||||
| if !self.config.ignore_bots { | ||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| for re in &self.bot_regexes { | ||||||||||||||||||||||||||||||||||||||||
| if re.is_match(author) { | ||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| false | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+49
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| pub fn categorize_file(&self, path: &str) -> String { | ||||||||||||||||||||||||||||||||||||||||
| for (concern, globset) in &self.concern_globs { | ||||||||||||||||||||||||||||||||||||||||
| if globset.is_match(path) { | ||||||||||||||||||||||||||||||||||||||||
| return concern.clone(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Fallback categorization logic if not matched by config | ||||||||||||||||||||||||||||||||||||||||
| if path.contains("test") || path.contains("spec") { | ||||||||||||||||||||||||||||||||||||||||
| return "tests".to_string(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| if path.ends_with(".md") { | ||||||||||||||||||||||||||||||||||||||||
| return "docs".to_string(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fallback categorization logic appears to be a regression compared to the original shell script it replaces. The script had more comprehensive checks for test files (e.g., |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| "other".to_string() | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fallback categorization is case-sensitive, potentially missing test files. The substring checks 🔧 Proposed fix for case-insensitive matching // Fallback categorization logic if not matched by config
- if path.contains("test") || path.contains("spec") {
+ let path_lower = path.to_lowercase();
+ if path_lower.contains("test") || path_lower.contains("spec") {
return "tests".to_string();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| pub fn is_excluded(&self, path: &str) -> bool { | ||||||||||||||||||||||||||||||||||||||||
| self.exclude_glob.is_match(path) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| pub fn analyze_commit(&self, sha: String, message: String, author: String, files: Vec<String>) -> CommitInfo { | ||||||||||||||||||||||||||||||||||||||||
| let mut concerns = HashSet::new(); | ||||||||||||||||||||||||||||||||||||||||
| let mut file_infos = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| for file in files { | ||||||||||||||||||||||||||||||||||||||||
| if self.is_excluded(&file) { | ||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let concern = self.categorize_file(&file); | ||||||||||||||||||||||||||||||||||||||||
| concerns.insert(concern.clone()); | ||||||||||||||||||||||||||||||||||||||||
| file_infos.push(FileInfo { | ||||||||||||||||||||||||||||||||||||||||
| path: file, | ||||||||||||||||||||||||||||||||||||||||
| concern, | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let count = concerns.len(); | ||||||||||||||||||||||||||||||||||||||||
| let is_atomic = count <= self.config.max_concerns; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| CommitInfo { | ||||||||||||||||||||||||||||||||||||||||
| sha, | ||||||||||||||||||||||||||||||||||||||||
| message, | ||||||||||||||||||||||||||||||||||||||||
| author, | ||||||||||||||||||||||||||||||||||||||||
| concerns: concerns.into_iter().collect(), | ||||||||||||||||||||||||||||||||||||||||
| count, | ||||||||||||||||||||||||||||||||||||||||
| is_atomic, | ||||||||||||||||||||||||||||||||||||||||
| files: file_infos, | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||||||||||||||||||||||||
| mod tests { | ||||||||||||||||||||||||||||||||||||||||
| use super::*; | ||||||||||||||||||||||||||||||||||||||||
| use crate::config::Config; | ||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||
| fn test_categorization() { | ||||||||||||||||||||||||||||||||||||||||
| let mut concern_patterns = HashMap::new(); | ||||||||||||||||||||||||||||||||||||||||
| concern_patterns.insert("source".to_string(), vec!["src/**/*.rs".to_string()]); | ||||||||||||||||||||||||||||||||||||||||
| concern_patterns.insert("docs".to_string(), vec!["*.md".to_string()]); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let config = Config { | ||||||||||||||||||||||||||||||||||||||||
| enabled: true, | ||||||||||||||||||||||||||||||||||||||||
| mode: "warning".to_string(), | ||||||||||||||||||||||||||||||||||||||||
| ignore_bots: true, | ||||||||||||||||||||||||||||||||||||||||
| bot_patterns: vec![], | ||||||||||||||||||||||||||||||||||||||||
| max_concerns: 1, | ||||||||||||||||||||||||||||||||||||||||
| concern_patterns, | ||||||||||||||||||||||||||||||||||||||||
| exclude_patterns: vec!["Cargo.lock".to_string()], | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let checker = Checker::new(config).unwrap(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assert_eq!(checker.categorize_file("src/main.rs"), "source"); | ||||||||||||||||||||||||||||||||||||||||
| assert_eq!(checker.categorize_file("README.md"), "docs"); | ||||||||||||||||||||||||||||||||||||||||
| assert_eq!(checker.categorize_file("other.txt"), "other"); | ||||||||||||||||||||||||||||||||||||||||
| assert!(checker.is_excluded("Cargo.lock")); | ||||||||||||||||||||||||||||||||||||||||
| assert!(!checker.is_excluded("src/main.rs")); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| use serde::{Deserialize, Serialize}; | ||
| use std::collections::HashMap; | ||
| use std::fs; | ||
| use std::path::Path; | ||
| use anyhow::Result; | ||
|
|
||
| #[derive(Debug, Serialize, Deserialize, Clone)] | ||
| pub struct Config { | ||
| #[serde(default = "default_enabled")] | ||
| pub enabled: bool, | ||
| #[serde(default = "default_mode")] | ||
| pub mode: String, | ||
| #[serde(default = "default_ignore_bots")] | ||
| pub ignore_bots: bool, | ||
| #[serde(default)] | ||
| pub bot_patterns: Vec<String>, | ||
| #[serde(default = "default_max_concerns")] | ||
| pub max_concerns: usize, | ||
| #[serde(default)] | ||
| pub concern_patterns: HashMap<String, Vec<String>>, | ||
| #[serde(default)] | ||
| pub exclude_patterns: Vec<String>, | ||
| } | ||
|
|
||
| fn default_enabled() -> bool { true } | ||
| fn default_mode() -> String { "warning".to_string() } | ||
| fn default_ignore_bots() -> bool { true } | ||
| fn default_max_concerns() -> usize { 1 } | ||
|
|
||
| impl Config { | ||
| pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> { | ||
| let content = fs::read_to_string(path)?; | ||
| let config: Config = serde_yaml::from_str(&content)?; | ||
| Ok(config) | ||
| } | ||
|
|
||
| pub fn default() -> Self { | ||
| Config { | ||
| enabled: true, | ||
| mode: "warning".to_string(), | ||
| ignore_bots: true, | ||
| bot_patterns: vec![], | ||
| max_concerns: 1, | ||
| concern_patterns: HashMap::new(), | ||
| exclude_patterns: vec![], | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+30
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of a standalone impl Config {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = fs::read_to_string(path)?;
let config: Config = serde_yaml::from_str(&content)?;
Ok(config)
}
}
impl Default for Config {
fn default() -> Self {
Self {
enabled: true,
mode: "warning".to_string(),
ignore_bots: true,
bot_patterns: vec![],
max_concerns: 1,
concern_patterns: HashMap::new(),
exclude_patterns: vec![],
}
}
} |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||
| use git2::{Repository, DiffOptions, Oid}; | ||||||||||||||||||||||||||||||||||
| use anyhow::Result; | ||||||||||||||||||||||||||||||||||
| use std::path::Path; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| pub struct GitContext { | ||||||||||||||||||||||||||||||||||
| repo: Repository, | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| impl GitContext { | ||||||||||||||||||||||||||||||||||
| pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> { | ||||||||||||||||||||||||||||||||||
| let repo = Repository::discover(path)?; | ||||||||||||||||||||||||||||||||||
| Ok(GitContext { repo }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| pub fn get_commits_in_range(&self, range: &str) -> Result<Vec<Oid>> { | ||||||||||||||||||||||||||||||||||
| let mut revwalk = self.repo.revwalk()?; | ||||||||||||||||||||||||||||||||||
| revwalk.push_range(range)?; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let mut oids = Vec::new(); | ||||||||||||||||||||||||||||||||||
| for oid in revwalk { | ||||||||||||||||||||||||||||||||||
| oids.push(oid?); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Ok(oids) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop for collecting OIDs can be written more idiomatically and concisely using
Suggested change
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| pub fn get_changed_files(&self, commit_oid: Oid) -> Result<Vec<String>> { | ||||||||||||||||||||||||||||||||||
| let commit = self.repo.find_commit(commit_oid)?; | ||||||||||||||||||||||||||||||||||
| let tree = commit.tree()?; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let parent_tree = if commit.parent_count() > 0 { | ||||||||||||||||||||||||||||||||||
| Some(commit.parent(0)?.tree()?) | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let mut opts = DiffOptions::new(); | ||||||||||||||||||||||||||||||||||
| let diff = self.repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), Some(&mut opts))?; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let mut files = Vec::new(); | ||||||||||||||||||||||||||||||||||
| diff.foreach( | ||||||||||||||||||||||||||||||||||
| &mut |delta, _| { | ||||||||||||||||||||||||||||||||||
| if let Some(path) = delta.new_file().path() { | ||||||||||||||||||||||||||||||||||
| files.push(path.to_string_lossy().into_owned()); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# First, let's see the file structure and find the exact lines
fd git.rs tools/atomicity-checker/src/Repository: iberi22/synapse-protocol Length of output: 102 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the relevant section with context
sed -n '35,50p' tools/atomicity-checker/src/git.rsRepository: iberi22/synapse-protocol Length of output: 552 🌐 Web query:
💡 Result: In git2-rs,
Because of that, Practical behavior you’ll see
So for “the path of the file in this delta”, common practice is: let path = delta.old_file().path().or(delta.new_file().path());This pattern is used in real code specifically to handle added/deleted cases. (origen-sdk.org) Note: if you generate a diff with libgit2’s “reverse” option, the meaning of old/new is flipped. (libgit2.org) Citations:
Handle deleted files in diff classification to prevent incomplete atomicity results. The current code only checks For deleted files, Suggested fix diff.foreach(
&mut |delta, _| {
- if let Some(path) = delta.new_file().path() {
+ let path = delta
+ .new_file()
+ .path()
+ .or_else(|| delta.old_file().path());
+ if let Some(path) = path {
files.push(path.to_string_lossy().into_owned());
}
true
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| true | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||
| )?; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Ok(files) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| pub fn get_commit_details(&self, commit_oid: Oid) -> Result<(String, String)> { | ||||||||||||||||||||||||||||||||||
| let commit = self.repo.find_commit(commit_oid)?; | ||||||||||||||||||||||||||||||||||
| let message = commit.message().unwrap_or("").trim().to_string(); | ||||||||||||||||||||||||||||||||||
| let author = commit.author().name().unwrap_or("").to_string(); | ||||||||||||||||||||||||||||||||||
| Ok((message, author)) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetch the base ref before computing
origin/<base>..HEAD.Line 35 assumes
origin/${{ github.base_ref }}is present locally. In PR checkouts this can be missing, causing range resolution failures and a false-red workflow.Suggested workflow patch
- name: 🦀 Setup Rust uses: dtolnay/rust-toolchain@stable + - name: 📥 Fetch base branch ref + run: | + git fetch --no-tags --prune --depth=1 origin \ + +refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} + - name: 🏗️ Build Atomicity Checker run: cargo build --release -p atomicity-checker🤖 Prompt for AI Agents