summary refs log tree commit diff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs131
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());
+        }
+    }
 }