diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 131 |
1 files changed, 126 insertions, 5 deletions
diff --git a/src/main.rs b/src/main.rs index 062a448..d5d763d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,137 @@ // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> -use std::process::exit; +mod diff_thread; +mod filter; -use git2::Repository; +use diff_thread::{DiffThread, FileDiff, MakeDiff}; +use filter::Filter; -fn main() { - let repo = match Repository::open_from_env() { +use std::env::args_os; +use std::ffi::OsString; +use std::io::{stderr, Read, Write}; +use std::os::unix::prelude::*; +use std::process::{exit, ChildStdout}; +use std::thread::spawn; + +use git2::{Delta, Diff, Oid, Repository, Sort, Tree}; + +fn child_out_bytes(child: &mut ChildStdout) -> impl Iterator<Item = u8> + '_ { + child.by_ref().bytes().map(Result::unwrap) +} + +fn output_eq(lhs: &mut ChildStdout, rhs: &mut ChildStdout) -> bool { + child_out_bytes(lhs).eq(child_out_bytes(rhs)) +} + +fn filtered_file_same( + repo: &Repository, + old: &Tree, + new: &Tree, + diff: FileDiff, + argv: &[OsString], +) -> bool { + if diff.status != Delta::Modified { + return false; + } + + let path = diff.old_path.unwrap(); + + let (filter_old, mut stdout_old) = Filter::new(&repo, &old, &path, argv); + let (filter_new, mut stdout_new) = Filter::new(&repo, &new, &path, argv); + + let thread = spawn(move || output_eq(&mut stdout_old, &mut stdout_new)); + + filter_old.wait().expect("TODO: HANDLE FILTER ERROR"); + filter_new.wait().expect("TODO: HANDLE FILTER ERROR"); + + thread.join().unwrap() +} + +fn open_repo() -> Repository { + match Repository::open_from_env() { Ok(repo) => repo, Err(e) => { eprintln!("fatal: {}", e.message()); exit(128); } - }; + } +} + +struct DiffMaker { + old_tree_oid: Oid, + new_tree_oid: Oid, +} + +impl MakeDiff for DiffMaker { + fn make_diff(self, repo: &Repository) -> Result<Diff, git2::Error> { + let old_tree = repo.find_tree(self.old_tree_oid)?; + let new_tree = repo.find_tree(self.new_tree_oid)?; + repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), Default::default()) + } +} + +fn main() { + let args: Vec<_> = args_os().collect(); + if args.len() < 2 { + eprintln!("fatal: no command given"); + exit(128); + } + + if let Some(arg) = args + .iter() + .skip(1) + .take_while(|a| a.as_bytes().starts_with(b"-")) + .next() + { + eprint!("fatal: unrecognized argument: "); + let _ = stderr().write_all(arg.as_bytes()); + eprintln!(); + exit(128); + } + + let repo = open_repo(); + + let diff_thread = + DiffThread::new(Repository::open(repo.path()).expect("opening diff thread repo")); + + let mut walk = repo.revwalk().expect("revwalk"); + walk.set_sorting(Sort::TOPOLOGICAL).expect("set_sorting"); + walk.push_head().expect("push_head"); + + // It might be good to parallelize this loop at some point, but + // we'd want to preserve order. + for commit_oid in walk { + let commit_oid = commit_oid.expect("revwalk"); + let commit = repo.find_commit(commit_oid).expect("find_commit"); + + // eprint!("checking {:.7} ", commit_oid); + // let _ = stderr().write_all(commit.message_bytes().split(|b| *b == b'\n').next().unwrap()); + // eprintln!(); + + if commit.parent_count() != 1 { + continue; + } + + // TODO: typechange + // TODO: filemode + // TODO: submodules + // + // TODO: fix all these unwraps + + let parent_tree = commit.parents().next().unwrap().tree().unwrap(); + let commit_tree = commit.tree().unwrap(); + + if diff_thread + .diff_files(DiffMaker { + old_tree_oid: parent_tree.id(), + new_tree_oid: commit_tree.id(), + }) + .all(|diff| { + filtered_file_same(&repo, &parent_tree, &commit_tree, diff.unwrap(), &args[1..]) + }) + { + println!("{}", commit.id()); + } + } } |