Skip to content

Commit a8cd8b1

Browse files
extract shared resolve_file_changes helper reducing duplication across diff.rs, review.rds, changelog.rs consolidated normalize_extts. Config changesi n risk assessment, now considers modified config/data as medium risk instead of silently falling through to low. Richer CommitInfo.
1 parent b305d2a commit a8cd8b1

12 files changed

Lines changed: 160 additions & 204 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules/
22
dist/
33
.sem/
44
*.db
5+
docs-vitepress/

crates/sem-cli/src/commands/changelog.rs

Lines changed: 12 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
use std::path::Path;
2-
use std::process;
32

43
use colored::Colorize;
5-
use sem_core::git::bridge::GitBridge;
6-
use sem_core::git::types::DiffScope;
74
use sem_core::parser::changelog::{
85
build_changelog, render_markdown, ChangelogResult,
96
};
107
use sem_core::parser::differ::compute_semantic_diff;
118
use sem_core::parser::graph::EntityGraph;
129
use sem_core::parser::plugins::create_default_registry;
1310

11+
use super::common::{self, open_git_or_exit};
12+
1413
pub struct ChangelogOptions {
1514
pub cwd: String,
1615
pub from: Option<String>,
@@ -34,67 +33,16 @@ pub enum ChangelogFormat {
3433
pub fn changelog_command(opts: ChangelogOptions) {
3534
let root = Path::new(&opts.cwd);
3635
let registry = create_default_registry();
36+
let git = open_git_or_exit(root);
3737

38-
let git = match GitBridge::open(root) {
39-
Ok(g) => g,
40-
Err(_) => {
41-
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
42-
process::exit(1);
43-
}
44-
};
45-
46-
// Get file changes
47-
let file_changes = if let Some(ref sha) = opts.commit {
48-
let scope = DiffScope::Commit { sha: sha.clone() };
49-
match git.get_changed_files(&scope) {
50-
Ok(files) => files,
51-
Err(e) => {
52-
eprintln!("\x1b[31mError: {e}\x1b[0m");
53-
process::exit(1);
54-
}
55-
}
56-
} else if let (Some(ref from), Some(ref to)) = (&opts.from, &opts.to) {
57-
let scope = DiffScope::Range {
58-
from: from.clone(),
59-
to: to.clone(),
60-
};
61-
match git.get_changed_files(&scope) {
62-
Ok(files) => files,
63-
Err(e) => {
64-
eprintln!("\x1b[31mError: {e}\x1b[0m");
65-
process::exit(1);
66-
}
67-
}
68-
} else if opts.staged {
69-
let scope = DiffScope::Staged;
70-
match git.get_changed_files(&scope) {
71-
Ok(files) => files,
72-
Err(e) => {
73-
eprintln!("\x1b[31mError: {e}\x1b[0m");
74-
process::exit(1);
75-
}
76-
}
77-
} else {
78-
match git.detect_and_get_files() {
79-
Ok((_scope, files)) => files,
80-
Err(_) => {
81-
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
82-
process::exit(1);
83-
}
84-
}
85-
};
86-
87-
// Filter by extensions
88-
let file_changes = if opts.file_exts.is_empty() {
89-
file_changes
90-
} else {
91-
let exts: Vec<String> = opts.file_exts.iter().map(|e| {
92-
if e.starts_with('.') { e.clone() } else { format!(".{}", e) }
93-
}).collect();
94-
file_changes.into_iter().filter(|fc| {
95-
exts.iter().any(|ext| fc.file_path.ends_with(ext.as_str()))
96-
}).collect()
97-
};
38+
let file_changes = common::resolve_file_changes(
39+
&git,
40+
opts.commit.as_deref(),
41+
opts.from.as_deref(),
42+
opts.to.as_deref(),
43+
opts.staged,
44+
);
45+
let file_changes = common::filter_by_exts(file_changes, &opts.file_exts);
9846

9947
if file_changes.is_empty() {
10048
println!("{}", "No changes detected.".dimmed());
@@ -110,7 +58,7 @@ pub fn changelog_command(opts: ChangelogOptions) {
11058
}
11159

11260
// Build entity graph
113-
let ext_filter = super::graph::normalize_exts(&opts.file_exts);
61+
let ext_filter = common::normalize_exts(&opts.file_exts);
11462
let all_files = sem_core::utils::files::find_supported_files(root, &registry, &ext_filter);
11563
let graph = EntityGraph::build(root, &all_files, &registry);
11664

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::path::Path;
2+
use std::process;
3+
4+
use sem_core::git::bridge::GitBridge;
5+
use sem_core::git::types::{DiffScope, FileChange};
6+
7+
/// Resolve file changes from git based on CLI options.
8+
pub fn resolve_file_changes(
9+
git: &GitBridge,
10+
commit: Option<&str>,
11+
from: Option<&str>,
12+
to: Option<&str>,
13+
staged: bool,
14+
) -> Vec<FileChange> {
15+
if let Some(sha) = commit {
16+
let scope = DiffScope::Commit { sha: sha.to_string() };
17+
match git.get_changed_files(&scope) {
18+
Ok(files) => files,
19+
Err(e) => {
20+
eprintln!("\x1b[31mError: {e}\x1b[0m");
21+
process::exit(1);
22+
}
23+
}
24+
} else if let (Some(f), Some(t)) = (from, to) {
25+
let scope = DiffScope::Range {
26+
from: f.to_string(),
27+
to: t.to_string(),
28+
};
29+
match git.get_changed_files(&scope) {
30+
Ok(files) => files,
31+
Err(e) => {
32+
eprintln!("\x1b[31mError: {e}\x1b[0m");
33+
process::exit(1);
34+
}
35+
}
36+
} else if staged {
37+
let scope = DiffScope::Staged;
38+
match git.get_changed_files(&scope) {
39+
Ok(files) => files,
40+
Err(e) => {
41+
eprintln!("\x1b[31mError: {e}\x1b[0m");
42+
process::exit(1);
43+
}
44+
}
45+
} else {
46+
match git.detect_and_get_files() {
47+
Ok((_scope, files)) => files,
48+
Err(_) => {
49+
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
50+
process::exit(1);
51+
}
52+
}
53+
}
54+
}
55+
56+
/// Open a GitBridge or exit with an error.
57+
pub fn open_git_or_exit(root: &Path) -> GitBridge {
58+
match GitBridge::open(root) {
59+
Ok(g) => g,
60+
Err(_) => {
61+
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
62+
process::exit(1);
63+
}
64+
}
65+
}
66+
67+
/// Normalize extension strings to have a leading dot.
68+
pub fn normalize_exts(exts: &[String]) -> Vec<String> {
69+
exts.iter()
70+
.map(|e| {
71+
if e.starts_with('.') {
72+
e.clone()
73+
} else {
74+
format!(".{}", e)
75+
}
76+
})
77+
.collect()
78+
}
79+
80+
/// Filter file changes by extension.
81+
pub fn filter_by_exts(file_changes: Vec<FileChange>, exts: &[String]) -> Vec<FileChange> {
82+
if exts.is_empty() {
83+
return file_changes;
84+
}
85+
let normalized = normalize_exts(exts);
86+
file_changes
87+
.into_iter()
88+
.filter(|fc| normalized.iter().any(|ext| fc.file_path.ends_with(ext.as_str())))
89+
.collect()
90+
}

crates/sem-cli/src/commands/diff.rs

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use std::path::Path;
33
use std::process;
44
use std::time::Instant;
55

6-
use sem_core::git::bridge::GitBridge;
7-
use sem_core::git::types::{DiffScope, FileChange};
6+
use sem_core::git::types::FileChange;
87
use sem_core::parser::differ::compute_semantic_diff;
98
use sem_core::parser::plugins::create_default_registry;
109

10+
use super::common::{self, open_git_or_exit};
1111
use crate::formatters::{json::format_json, terminal::format_terminal};
1212

1313
pub struct DiffOptions {
@@ -71,68 +71,19 @@ pub fn diff_command(opts: DiffOptions) {
7171
});
7272
(changes, true)
7373
} else {
74-
let git = match GitBridge::open(Path::new(&opts.cwd)) {
75-
Ok(g) => g,
76-
Err(_) => {
77-
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
78-
process::exit(1);
79-
}
80-
};
81-
82-
let (_scope, file_changes) = if let Some(ref sha) = opts.commit {
83-
let scope = DiffScope::Commit { sha: sha.clone() };
84-
match git.get_changed_files(&scope) {
85-
Ok(files) => (scope, files),
86-
Err(e) => {
87-
eprintln!("\x1b[31mError: {e}\x1b[0m");
88-
process::exit(1);
89-
}
90-
}
91-
} else if let (Some(ref from), Some(ref to)) = (&opts.from, &opts.to) {
92-
let scope = DiffScope::Range {
93-
from: from.clone(),
94-
to: to.clone(),
95-
};
96-
match git.get_changed_files(&scope) {
97-
Ok(files) => (scope, files),
98-
Err(e) => {
99-
eprintln!("\x1b[31mError: {e}\x1b[0m");
100-
process::exit(1);
101-
}
102-
}
103-
} else if opts.staged {
104-
let scope = DiffScope::Staged;
105-
match git.get_changed_files(&scope) {
106-
Ok(files) => (scope, files),
107-
Err(e) => {
108-
eprintln!("\x1b[31mError: {e}\x1b[0m");
109-
process::exit(1);
110-
}
111-
}
112-
} else {
113-
match git.detect_and_get_files() {
114-
Ok((scope, files)) => (scope, files),
115-
Err(_) => {
116-
eprintln!("\x1b[31mError: Not inside a Git repository.\x1b[0m");
117-
process::exit(1);
118-
}
119-
}
120-
};
74+
let git = open_git_or_exit(Path::new(&opts.cwd));
75+
let file_changes = common::resolve_file_changes(
76+
&git,
77+
opts.commit.as_deref(),
78+
opts.from.as_deref(),
79+
opts.to.as_deref(),
80+
opts.staged,
81+
);
12182
(file_changes, false)
12283
};
12384
let git_diff_ms = t0.elapsed().as_secs_f64() * 1000.0;
12485

125-
// Filter by file extensions if specified
126-
let file_changes = if opts.file_exts.is_empty() {
127-
file_changes
128-
} else {
129-
let exts: Vec<String> = opts.file_exts.iter().map(|e| {
130-
if e.starts_with('.') { e.clone() } else { format!(".{}", e) }
131-
}).collect();
132-
file_changes.into_iter().filter(|fc| {
133-
exts.iter().any(|ext| fc.file_path.ends_with(ext.as_str()))
134-
}).collect()
135-
};
86+
let file_changes = common::filter_by_exts(file_changes, &opts.file_exts);
13687

13788
if file_changes.is_empty() {
13889
println!("\x1b[2mNo changes detected.\x1b[0m");

crates/sem-cli/src/commands/graph.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn graph_command(opts: GraphOptions) {
2121
let root = Path::new(&opts.cwd);
2222
let registry = create_default_registry();
2323

24-
let ext_filter = normalize_exts(&opts.file_exts);
24+
let ext_filter = super::common::normalize_exts(&opts.file_exts);
2525

2626
// If no files specified, find all supported files in the repo
2727
let file_paths = if opts.file_paths.is_empty() {
@@ -231,10 +231,3 @@ fn ref_symbol(ref_type: &RefType) -> colored::ColoredString {
231231
RefType::Imports => "↓".green(),
232232
}
233233
}
234-
235-
/// Normalize extension strings: ensure each starts with '.'
236-
pub fn normalize_exts(exts: &[String]) -> Vec<String> {
237-
exts.iter().map(|e| {
238-
if e.starts_with('.') { e.clone() } else { format!(".{}", e) }
239-
}).collect()
240-
}

crates/sem-cli/src/commands/impact.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn impact_command(opts: ImpactOptions) {
1616
let root = Path::new(&opts.cwd);
1717
let registry = create_default_registry();
1818

19-
let ext_filter = super::graph::normalize_exts(&opts.file_exts);
19+
let ext_filter = super::common::normalize_exts(&opts.file_exts);
2020

2121
// If no files specified, find all supported files in the repo
2222
let file_paths = if opts.file_paths.is_empty() {

crates/sem-cli/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod blame;
22
pub mod changelog;
3+
pub mod common;
34
pub mod diff;
45
pub mod graph;
56
pub mod impact;

0 commit comments

Comments
 (0)