Skip to content

Commit 61f688c

Browse files
committed
wip!: blame + deletion based no-history derivation
1 parent 509bb33 commit 61f688c

3 files changed

Lines changed: 105 additions & 4 deletions

File tree

src/commands.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub mod diff;
33
pub mod init;
44
pub mod list;
55

6-
pub use derive::derive;
6+
pub use derive::{derive, derive_no_history};
77
pub use diff::diff;
88
pub use init::init;
99
pub use list::list;

src/commands/derive.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
use anyhow::{Context, Result};
2-
use git2::{Diff, Index, IndexEntry, IndexTime, Oid, Repository, Signature, Sort, Tree};
2+
use git2::{
3+
BlameOptions, Diff, Index, IndexEntry, IndexTime, Oid, Repository, Signature, Sort, Status,
4+
Tree, TreeWalkResult,
5+
};
36
use regex::Regex;
7+
use std::fs::File;
8+
use std::io::{BufRead, BufReader};
49
use std::path::{Path, PathBuf};
510

611
use std::collections::{HashMap, HashSet};
@@ -263,6 +268,90 @@ pub fn derive(repo: &Repository, name: &str, features: &[String]) -> Result<()>
263268
Ok(())
264269
}
265270

271+
pub fn derive_no_history(repo: &Repository, name: &str, features: &[String]) -> Result<()> {
272+
let statuses = repo.statuses(None)?;
273+
for entry in statuses.iter() {
274+
let s = entry.status();
275+
if s != Status::CURRENT {
276+
println!("Repository is not clean!");
277+
return Ok(());
278+
}
279+
}
280+
281+
let ref_name = format!("refs/heads/variant/{name}");
282+
let sig = repo.signature()?;
283+
let variant_head_oid = create_variant_initial_commit(repo, &ref_name, &sig)?;
284+
285+
let target_features: HashSet<String> = features.iter().cloned().collect();
286+
let head = repo.head()?;
287+
let tree = head.peel_to_tree()?;
288+
289+
let mut tree_builder = repo.treebuilder(None)?;
290+
291+
tree.walk(git2::TreeWalkMode::PreOrder, |root, entry| {
292+
if let Some(name) = entry.name() {
293+
let path = Path::new(root).join(name);
294+
let content = process_file(repo, &path, &target_features).unwrap();
295+
296+
let oid = repo.blob(content.as_bytes()).unwrap();
297+
tree_builder.insert(path, oid, 0o100644).unwrap();
298+
}
299+
TreeWalkResult::Ok
300+
})?;
301+
302+
let variant_tree_oid = tree_builder.write()?;
303+
let variant_tree = repo.find_tree(variant_tree_oid)?;
304+
let variant_parent = repo.find_commit(variant_head_oid)?;
305+
306+
repo.commit(
307+
Some(&ref_name),
308+
&sig,
309+
&sig,
310+
"derive no history",
311+
&variant_tree,
312+
&[&variant_parent],
313+
)?;
314+
Ok(())
315+
}
316+
317+
fn process_file(
318+
repo: &Repository,
319+
name: &PathBuf,
320+
target_features: &HashSet<String>,
321+
) -> Result<String> {
322+
let file = File::open(name)?;
323+
let reader = BufReader::new(file);
324+
let mut lines: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
325+
326+
let mut commit_cache: HashMap<Oid, bool> = HashMap::new();
327+
328+
let mut blame_opts = BlameOptions::new();
329+
let blame = repo.blame_file(Path::new(name), Some(&mut blame_opts))?;
330+
331+
for (i, line) in lines.iter_mut().enumerate() {
332+
let line_no = i + 1;
333+
334+
let hunk = blame
335+
.get_line(line_no)
336+
.context("Cant get line info in hunk")?;
337+
338+
let oid = hunk.final_commit_id();
339+
340+
let contains_feature = commit_cache.entry(oid).or_insert_with(|| {
341+
let commit = repo.find_commit(oid).unwrap();
342+
let summary = commit.summary().unwrap();
343+
extract_scope(summary).is_some_and(|feature| target_features.contains(feature))
344+
});
345+
346+
if !*contains_feature {
347+
*line = String::new();
348+
}
349+
}
350+
351+
// lines.retain(|line| !line.is_empty());
352+
Ok(lines.join("\n"))
353+
}
354+
266355
// TODO: building each time can be expensive. Look for an alternative
267356
fn extract_scope(commit: &str) -> Option<&str> {
268357
let re = Regex::new(r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:").unwrap();

src/main.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ enum Commands {
2222
/// The set of features to use for the derivation
2323
#[arg(short, long = "feature", required = true)]
2424
features: Vec<String>,
25+
/// use the no-history derivation
26+
#[arg(long = "history")]
27+
history: bool,
2528
},
2629
/// List the commits exist in the target branch but not in the current branch
2730
Diff {
@@ -47,8 +50,17 @@ fn main() -> Result<()> {
4750
Commands::Init => {
4851
commands::init(&repo).context("Failed to initialize VMS support")?;
4952
}
50-
Commands::Derive { name, features } => {
51-
commands::derive(&repo, &name, &features).context("Failed to derive variant")?;
53+
Commands::Derive {
54+
name,
55+
features,
56+
history,
57+
} => {
58+
if history {
59+
commands::derive(&repo, &name, &features).context("Failed to derive variant")?;
60+
} else {
61+
commands::derive_no_history(&repo, &name, &features)
62+
.context("Failed to derive variant")?;
63+
}
5264
}
5365
Commands::Diff { target, reverse } => {
5466
commands::diff(&repo, &target, reverse).context(format!(

0 commit comments

Comments
 (0)