summary refs log tree commit diff
path: root/src/diff_thread
diff options
context:
space:
mode:
Diffstat (limited to 'src/diff_thread')
-rw-r--r--src/diff_thread/client.rs31
-rw-r--r--src/diff_thread/mod.rs28
-rw-r--r--src/diff_thread/server.rs39
3 files changed, 98 insertions, 0 deletions
diff --git a/src/diff_thread/client.rs b/src/diff_thread/client.rs
new file mode 100644
index 0000000..dcc9cfe
--- /dev/null
+++ b/src/diff_thread/client.rs
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: EUPL-1.2
+// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
+
+use super::{server, FileDiff, MakeDiff};
+
+use std::sync::mpsc::{sync_channel, SyncSender};
+use std::thread::spawn;
+
+use git2::Repository;
+
+pub struct DiffThread<D: MakeDiff> {
+    sender: SyncSender<(D, SyncSender<Result<FileDiff, git2::Error>>)>,
+}
+
+impl<D: MakeDiff> DiffThread<D> {
+    pub fn new(repo: Repository) -> Self {
+        let (sender, receiver) = sync_channel(0);
+
+        spawn(move || server::main(&repo, receiver));
+
+        Self { sender }
+    }
+
+    pub fn diff_files(&self, diff_maker: D) -> impl Iterator<Item = Result<FileDiff, git2::Error>> {
+        let (sender, receiver) = sync_channel(2);
+        self.sender
+            .send((diff_maker, sender))
+            .expect("sending to diff thread");
+        receiver.into_iter()
+    }
+}
diff --git a/src/diff_thread/mod.rs b/src/diff_thread/mod.rs
new file mode 100644
index 0000000..9416509
--- /dev/null
+++ b/src/diff_thread/mod.rs
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: EUPL-1.2
+// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
+
+//! libgit2's diffing API gives us a callback whenever a file diff is ready, but
+//! that's not very convenient for Rust code, because it can't be used as an
+//! iterator.  So we run the diff in a thread, and send the diff information we need
+//! to the main thread over a channel.  When the main thread is processing diff
+//! information too slowly, we block the diff thread until the main thread catches
+//! up.
+
+mod server;
+mod client;
+
+pub use client::DiffThread;
+
+use std::path::PathBuf;
+
+use git2::{Repository, Diff, Delta};
+
+pub trait MakeDiff: Send + 'static {
+    fn make_diff(self, _: &Repository) -> Result<Diff, git2::Error>;
+}
+
+/// `new_path` isn't included because it's not currently used in git-girf.
+pub struct FileDiff {
+    pub status: Delta,
+    pub old_path: Option<PathBuf>,
+}
diff --git a/src/diff_thread/server.rs b/src/diff_thread/server.rs
new file mode 100644
index 0000000..d6d43ab
--- /dev/null
+++ b/src/diff_thread/server.rs
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: EUPL-1.2
+// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
+
+use super::{FileDiff, MakeDiff};
+
+use std::sync::mpsc::{Receiver, SyncSender};
+
+use git2::Repository;
+
+pub fn main(
+    repository: &Repository,
+    receiver: Receiver<(impl MakeDiff, SyncSender<Result<FileDiff, git2::Error>>)>,
+) {
+    for (diff_maker, sender) in receiver {
+        let diff = match diff_maker.make_diff(&repository) {
+            Ok(diff) => diff,
+            Err(e) => {
+                let _ = sender.send(Err(e));
+                return;
+            }
+        };
+
+        if let Err(e) = diff.foreach(
+            &mut |delta, _| {
+                let _ = sender.send(Ok(FileDiff {
+                    status: delta.status(),
+                    old_path: delta.old_file().path().map(Into::into),
+                }));
+
+                true
+            },
+            None,
+            None,
+            None,
+        ) {
+            let _ = sender.send(Err(e));
+        }
+    }
+}