// SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021 Alyssa Ross mod diff_thread; mod filter; use diff_thread::{DiffThread, FileDiff, MakeDiff}; use filter::Filter; 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 + '_ { 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 { 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()); } } }