|
1 | 1 | 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 | +}; |
3 | 6 | use regex::Regex; |
| 7 | +use std::fs::File; |
| 8 | +use std::io::{BufRead, BufReader}; |
4 | 9 | use std::path::{Path, PathBuf}; |
5 | 10 |
|
6 | 11 | use std::collections::{HashMap, HashSet}; |
@@ -263,6 +268,67 @@ pub fn derive(repo: &Repository, name: &str, features: &[String]) -> Result<()> |
263 | 268 | Ok(()) |
264 | 269 | } |
265 | 270 |
|
| 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 target_features: HashSet<String> = features.iter().cloned().collect(); |
| 282 | + let head = repo.head()?; |
| 283 | + let tree = head.peel_to_tree()?; |
| 284 | + |
| 285 | + tree.walk(git2::TreeWalkMode::PreOrder, |root, entry| { |
| 286 | + if let Some(name) = entry.name() { |
| 287 | + let path = Path::new(root).join(name); |
| 288 | + } |
| 289 | + TreeWalkResult::Ok |
| 290 | + })?; |
| 291 | + Ok(()) |
| 292 | +} |
| 293 | + |
| 294 | +fn process_file( |
| 295 | + repo: &Repository, |
| 296 | + name: &PathBuf, |
| 297 | + target_features: &HashSet<String>, |
| 298 | +) -> Result<()> { |
| 299 | + let file = File::open(name)?; |
| 300 | + let reader = BufReader::new(file); |
| 301 | + let mut lines: Vec<String> = reader.lines().collect::<Result<_, _>>()?; |
| 302 | + |
| 303 | + let mut commit_cache: HashMap<Oid, bool> = HashMap::new(); |
| 304 | + |
| 305 | + let mut blame_opts = BlameOptions::new(); |
| 306 | + let blame = repo.blame_file(Path::new(name), Some(&mut blame_opts))?; |
| 307 | + |
| 308 | + for (i, line) in lines.iter().enumerate() { |
| 309 | + let line_no = i + 1; |
| 310 | + |
| 311 | + let hunk = blame |
| 312 | + .get_line(line_no) |
| 313 | + .context("Cant get line info in hunk")?; |
| 314 | + |
| 315 | + let oid = hunk.final_commit_id(); |
| 316 | + |
| 317 | + commit_cache.entry(oid).or_insert_with(|| { |
| 318 | + let commit = repo.find_commit(oid).unwrap(); |
| 319 | + let summary = commit.summary().unwrap(); |
| 320 | + if let Some(feature) = extract_scope(summary) |
| 321 | + && target_features.contains(feature) |
| 322 | + { |
| 323 | + return true; |
| 324 | + } |
| 325 | + false |
| 326 | + }); |
| 327 | + } |
| 328 | + |
| 329 | + Ok(()) |
| 330 | +} |
| 331 | + |
266 | 332 | // TODO: building each time can be expensive. Look for an alternative |
267 | 333 | fn extract_scope(commit: &str) -> Option<&str> { |
268 | 334 | let re = Regex::new(r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:").unwrap(); |
|
0 commit comments