The watcher-crate compiles and tests run

This commit is contained in:
Sebastian Thiel 2024-04-17 20:56:14 +02:00
parent 143fc05547
commit 81dd1fc13e
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
13 changed files with 159 additions and 227 deletions

5
Cargo.lock generated
View File

@ -2164,16 +2164,17 @@ name = "gitbutler-watcher"
version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"backoff",
"crossbeam-channel",
"futures",
"git2",
"gitbutler-analytics",
"gitbutler-core",
"gitbutler-testsupport",
"itertools 0.12.1",
"notify",
"notify-debouncer-full",
"tauri",
"once_cell",
"tempfile",
"thiserror",
"tokio",

View File

@ -20,6 +20,7 @@ tokio = { version = "1.37.0" }
gitbutler-git = { path = "crates/gitbutler-git" }
gitbutler-core = { path = "crates/gitbutler-core" }
gitbutler-analytics = { path = "crates/gitbutler-analytics" }
gitbutler-testsupport = { path = "crates/gitbutler-testsupport" }
[profile.release]

View File

@ -8,6 +8,7 @@ publish = false
doctest = false
[dependencies]
gitbutler-analytics.workspace = true
gitbutler-core.workspace = true
thiserror.workspace = true
anyhow = "1.0.81"
@ -15,7 +16,6 @@ futures = "0.3.30"
tokio = { workspace = true, features = [ "full", "sync" ] }
tokio-util = "0.7.10"
tracing = "0.1.40"
async-trait = "0.1.79"
backoff = "0.4.0"
notify = { version = "6.0.1" }
@ -23,19 +23,11 @@ notify-debouncer-full = "0.3.1"
crossbeam-channel = "0.5.12"
itertools = "0.12"
# TODO(ST): remove this dependency, abstract it
[dependencies.tauri]
version = "1.6.1"
features = [
"http-all", "os-all", "dialog-open", "fs-read-file",
"path-all", "process-relaunch", "protocol-asset",
"shell-open", "window-maximize", "window-start-dragging",
"window-unmaximize"
]
[dev-dependencies]
tempfile = "3.10"
gitbutler-testsupport.workspace = true
git2.workspace = true
tempfile = "3.10"
once_cell = "1.19"
[lints.clippy]
all = "deny"

View File

@ -1,12 +1,13 @@
use std::fmt::Display;
use std::path::PathBuf;
use gitbutler_core::{deltas, reader, sessions::SessionId, virtual_branches};
use gitbutler_core::{projects::ProjectId, sessions};
/// An event for internal use, as merge between [super::file_monitor::Event] and [Event].
/// An event for internal use, as merge between [super::file_monitor::Event] and [Action].
#[derive(Debug)]
pub(super) enum InternalEvent {
// From public API
// From public action API
Flush(ProjectId, sessions::Session),
CalculateVirtualBranches(ProjectId),
FetchGitbutlerData(ProjectId),
@ -21,31 +22,33 @@ pub(super) enum InternalEvent {
// TODO(ST): This should not have to be implemented in the Watcher, figure out how this can be moved
// to application logic at least. However, it's called through a trait in `core`.
#[derive(Debug, PartialEq, Clone)]
pub enum Event {
#[allow(missing_docs)]
pub enum Action {
Flush(ProjectId, sessions::Session),
CalculateVirtualBranches(ProjectId),
FetchGitbutlerData(ProjectId),
PushGitbutlerData(ProjectId),
}
impl Event {
impl Action {
/// Return the action's associated project id.
pub fn project_id(&self) -> ProjectId {
match self {
Event::FetchGitbutlerData(project_id)
| Event::Flush(project_id, _)
| Event::CalculateVirtualBranches(project_id)
| Event::PushGitbutlerData(project_id) => *project_id,
Action::FetchGitbutlerData(project_id)
| Action::Flush(project_id, _)
| Action::CalculateVirtualBranches(project_id)
| Action::PushGitbutlerData(project_id) => *project_id,
}
}
}
impl From<Event> for InternalEvent {
fn from(value: Event) -> Self {
impl From<Action> for InternalEvent {
fn from(value: Action) -> Self {
match value {
Event::Flush(a, b) => InternalEvent::Flush(a, b),
Event::CalculateVirtualBranches(v) => InternalEvent::CalculateVirtualBranches(v),
Event::FetchGitbutlerData(v) => InternalEvent::FetchGitbutlerData(v),
Event::PushGitbutlerData(v) => InternalEvent::PushGitbutlerData(v),
Action::Flush(a, b) => InternalEvent::Flush(a, b),
Action::CalculateVirtualBranches(v) => InternalEvent::CalculateVirtualBranches(v),
Action::FetchGitbutlerData(v) => InternalEvent::FetchGitbutlerData(v),
Action::PushGitbutlerData(v) => InternalEvent::PushGitbutlerData(v),
}
}
}
@ -96,3 +99,38 @@ fn comma_separated_paths(paths: &[PathBuf]) -> String {
listing
}
}
/// An event telling the receiver something about the state of the application which just changed.
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub enum Change {
GitIndex(ProjectId),
GitFetch(ProjectId),
GitHead {
project_id: ProjectId,
head: String,
},
GitActivity(ProjectId),
File {
project_id: ProjectId,
session_id: SessionId,
// TODO(ST): Should probably not be a string, but rather, relative.
file_path: String,
contents: Option<reader::Content>,
},
Session {
project_id: ProjectId,
session: sessions::Session,
},
Deltas {
project_id: ProjectId,
session_id: SessionId,
// TODO(ST): check if this is ever more than one
deltas: Vec<deltas::Delta>,
relative_file_path: PathBuf,
},
VirtualBranches {
project_id: ProjectId,
virtual_branches: virtual_branches::VirtualBranches,
},
}

View File

@ -14,12 +14,12 @@ use gitbutler_core::{
assets, deltas, gb_repository, git, project_repository, projects, reader, sessions, users,
virtual_branches,
};
use tauri::{AppHandle, Manager};
use tracing::instrument;
use super::events;
use crate::{analytics, events as app_events};
use super::{events, Change};
/// A type that contains enough state to make decisions based on changes in the filesystem, which themselves
/// may trigger [Changes](Change)
// NOTE: This is `Clone` as each incoming event is spawned onto a thread for processing.
#[derive(Clone)]
pub struct Handler {
@ -29,7 +29,7 @@ pub struct Handler {
// the tauri app, assuming that such application would not be `Send + Sync` everywhere and thus would
// need extra protection.
users: users::Controller,
analytics: analytics::Client,
analytics: gitbutler_analytics::Client,
local_data_dir: path::PathBuf,
projects: projects::Controller,
vbranch_controller: virtual_branches::Controller,
@ -39,42 +39,7 @@ pub struct Handler {
/// A function to send events - decoupled from app-handle for testing purposes.
#[allow(clippy::type_complexity)]
send_event: Arc<dyn Fn(&crate::events::Event) -> Result<()> + Send + Sync + 'static>,
}
impl Handler {
pub fn from_app(app: &AppHandle) -> Result<Self, anyhow::Error> {
let app_data_dir = app
.path_resolver()
.app_data_dir()
.context("failed to get app data dir")?;
let analytics = app
.try_state::<analytics::Client>()
.map_or(analytics::Client::default(), |client| {
client.inner().clone()
});
let users = app.state::<users::Controller>().inner().clone();
let projects = app.state::<projects::Controller>().inner().clone();
let vbranches = app.state::<virtual_branches::Controller>().inner().clone();
let assets_proxy = app.state::<assets::Proxy>().inner().clone();
let sessions_db = app.state::<sessions::Database>().inner().clone();
let deltas_db = app.state::<deltas::Database>().inner().clone();
Ok(Handler::new(
app_data_dir.clone(),
analytics,
users,
projects,
vbranches,
assets_proxy,
sessions_db,
deltas_db,
{
let app = app.clone();
move |event: &crate::events::Event| event.send(&app)
},
))
}
send_event: Arc<dyn Fn(Change) -> Result<()> + Send + Sync + 'static>,
}
impl Handler {
@ -82,14 +47,14 @@ impl Handler {
#[allow(clippy::too_many_arguments)]
pub fn new(
local_data_dir: PathBuf,
analytics: analytics::Client,
analytics: gitbutler_analytics::Client,
users: users::Controller,
projects: projects::Controller,
vbranch_controller: virtual_branches::Controller,
assets_proxy: assets::Proxy,
sessions_db: sessions::Database,
deltas_db: deltas::Database,
send_event: impl Fn(&app_events::Event) -> Result<()> + Send + Sync + 'static,
send_event: impl Fn(Change) -> Result<()> + Send + Sync + 'static,
) -> Self {
Handler {
local_data_dir,
@ -144,7 +109,7 @@ impl Handler {
}
impl Handler {
fn emit_app_event(&self, event: &crate::events::Event) -> Result<()> {
fn emit_app_event(&self, event: Change) -> Result<()> {
(self.send_event)(event).context("failed to send event")
}
@ -153,16 +118,16 @@ impl Handler {
project_id: ProjectId,
session_id: SessionId,
file_path: &Path,
contents: Option<&reader::Content>,
contents: Option<reader::Content>,
) -> Result<()> {
self.emit_app_event(&app_events::Event::file(
self.emit_app_event(Change::File {
project_id,
session_id,
&file_path.display().to_string(),
file_path: file_path.display().to_string(),
contents,
))
})
}
fn send_analytics_event_none_blocking(&self, event: &analytics::Event) -> Result<()> {
fn send_analytics_event_none_blocking(&self, event: &gitbutler_analytics::Event) -> Result<()> {
if let Some(user) = self.users.get_user().context("failed to get user")? {
self.analytics
.send_non_anonymous_event_nonblocking(&user, event);
@ -193,7 +158,7 @@ impl Handler {
.flush_session(&project_repository, session, user.as_ref())
.context(format!("failed to flush session {}", session.id))?;
self.index_session(project_id, &session)?;
self.index_session(project_id, session)?;
let push_gb_data = tokio::task::spawn_blocking({
let this = self.clone();
@ -213,13 +178,13 @@ impl Handler {
{
Ok((branches, skipped_files)) => {
let branches = self.assets_proxy.proxy_virtual_branches(branches).await;
self.emit_app_event(&app_events::Event::virtual_branches(
self.emit_app_event(Change::VirtualBranches {
project_id,
&VirtualBranches {
virtual_branches: VirtualBranches {
branches,
skipped_files,
},
))
})
}
Err(err) if err.is::<virtual_branches::errors::VerifyError>() => Ok(()),
Err(err) => Err(err.context("failed to list virtual branches").into()),
@ -314,7 +279,7 @@ impl Handler {
let sessions_after_fetch = gb_repo.get_sessions_iterator()?.filter_map(Result::ok);
let new_sessions = sessions_after_fetch.filter(|s| !sessions_before_fetch.contains(s));
for session in new_sessions {
self.index_session(project_id, &session)?;
self.index_session(project_id, session)?;
}
Ok(())
}
@ -358,11 +323,11 @@ impl Handler {
};
match file_name {
"FETCH_HEAD" => {
self.emit_app_event(&app_events::Event::git_fetch(project_id))?;
self.emit_app_event(Change::GitFetch(project_id))?;
self.calculate_virtual_branches(project_id).await?;
}
"logs/HEAD" => {
self.emit_app_event(&app_events::Event::git_activity(project.id))?;
self.emit_app_event(Change::GitActivity(project.id))?;
}
"GB_FLUSH" => {
let user = self.users.get_user()?;
@ -404,18 +369,20 @@ impl Handler {
integration_reference.delete()?;
}
if let Some(head) = head_ref.name() {
self.send_analytics_event_none_blocking(&analytics::Event::HeadChange {
self.send_analytics_event_none_blocking(
&gitbutler_analytics::Event::HeadChange {
project_id,
reference_name: head_ref_name.to_string(),
},
)?;
self.emit_app_event(Change::GitHead {
project_id,
reference_name: head_ref_name.to_string(),
head: head.to_string(),
})?;
self.emit_app_event(&app_events::Event::git_head(
project_id,
&head.to_string(),
))?;
}
}
"index" => {
self.emit_app_event(&app_events::Event::git_index(project.id))?;
self.emit_app_event(Change::GitIndex(project.id))?;
}
_ => {}
}

View File

@ -1,3 +1,4 @@
use crate::Change;
use anyhow::{Context, Result};
use gitbutler_core::{
deltas, gb_repository, project_repository, projects::ProjectId, reader, sessions,
@ -43,7 +44,7 @@ impl super::Handler {
.context("failed to get or create current session")?;
let session = current_session.clone();
let process = move |path: &Path| -> Result<bool> {
let process = move |path: PathBuf| -> Result<bool> {
let _span = tracing::span!(tracing::Level::TRACE, "processing", ?path).entered();
let current_session_reader =
sessions::Reader::open(&gb_repository, &current_session)
@ -51,18 +52,18 @@ impl super::Handler {
let deltas_reader = deltas::Reader::new(&current_session_reader);
let writer =
deltas::Writer::new(&gb_repository).context("failed to open deltas writer")?;
let current_wd_file_content = match Self::file_content(&project_repository, path) {
let current_wd_file_content = match Self::file_content(&project_repository, &path) {
Ok(content) => Some(content),
Err(reader::Error::NotFound) => None,
Err(err) => Err(err).context("failed to get file content")?,
};
let latest_file_content = match current_session_reader.file(path) {
let latest_file_content = match current_session_reader.file(&path) {
Ok(content) => Some(content),
Err(reader::Error::NotFound) => None,
Err(err) => Err(err).context("failed to get file content")?,
};
let current_deltas = deltas_reader
.read_file(path)
.read_file(&path)
.context("failed to get file deltas")?;
let mut text_doc = deltas::Document::new(
latest_file_content.as_ref(),
@ -78,30 +79,30 @@ impl super::Handler {
let deltas = text_doc.get_deltas();
writer
.write(path, &deltas)
.write(&path, &deltas)
.context("failed to write deltas")?;
match &current_wd_file_content {
Some(reader::Content::UTF8(text)) => writer.write_wd_file(path, text),
Some(_) => writer.write_wd_file(path, ""),
None => writer.remove_wd_file(path),
Some(reader::Content::UTF8(text)) => writer.write_wd_file(&path, text),
Some(_) => writer.write_wd_file(&path, ""),
None => writer.remove_wd_file(&path),
}?;
let session_id = current_session.id;
self.emit_session_file(project_id, session_id, path, latest_file_content.as_ref())?;
self.emit_session_file(project_id, session_id, &path, latest_file_content)?;
self.index_deltas(
project_id,
session_id,
path,
&path,
std::slice::from_ref(&new_delta),
)
.context("failed to index deltas")?;
self.emit_app_event(&app_events::Event::deltas(
self.emit_app_event(Change::Deltas {
project_id,
session_id,
std::slice::from_ref(&new_delta),
path,
))?;
deltas: vec![new_delta],
relative_file_path: path,
})?;
Ok(true)
};
Ok((process, session))
@ -116,7 +117,7 @@ impl super::Handler {
let current_session = if num_threads < 2 {
let (process, session) = make_processor()?;
for path in paths {
if !process(path.as_path())? {
if !process(path)? {
num_no_delta += 1;
}
}
@ -135,7 +136,7 @@ impl super::Handler {
let mut num_no_delta = 0;
let (process, _) = make_processor()?;
for path in rx {
if !process(path.as_path())? {
if !process(path)? {
num_no_delta += 1;
}
}
@ -158,7 +159,7 @@ impl super::Handler {
let (_, session) = make_processor()?;
session
};
self.index_session(project_id, &current_session)?;
self.index_session(project_id, current_session)?;
Ok(num_no_delta)
})?;
tracing::debug!(%project_id, paths_without_deltas = num_no_delta, paths_with_delta = num_paths - num_no_delta);

View File

@ -7,7 +7,7 @@ use gitbutler_core::{
sessions::{self, SessionId},
};
use crate::events as app_events;
use crate::Change;
impl super::Handler {
pub(super) fn index_deltas(
@ -36,7 +36,7 @@ impl super::Handler {
let sessions_iter = gb_repository.get_sessions_iterator()?;
for session in sessions_iter {
self.process_session(&gb_repository, &session?)?;
self.process_session(&gb_repository, session?)?;
}
Ok(())
}
@ -44,7 +44,7 @@ impl super::Handler {
pub(super) fn index_session(
&self,
project_id: ProjectId,
session: &sessions::Session,
session: sessions::Session,
) -> Result<()> {
let project = self.projects.get(&project_id)?;
let project_repository =
@ -63,21 +63,21 @@ impl super::Handler {
fn process_session(
&self,
gb_repository: &gb_repository::Repository,
session: &sessions::Session,
session: sessions::Session,
) -> Result<()> {
let project_id = gb_repository.get_project_id();
// now, index session if it has changed to the database.
let from_db = self.sessions_db.get_by_id(&session.id)?;
if from_db.map_or(false, |from_db| from_db == *session) {
if from_db.map_or(false, |from_db| from_db == session) {
return Ok(());
}
self.sessions_db
.insert(project_id, &[session])
.insert(project_id, &[&session])
.context("failed to insert session into database")?;
let session_reader = sessions::Reader::open(gb_repository, session)?;
let session_reader = sessions::Reader::open(gb_repository, &session)?;
let deltas_reader = deltas::Reader::new(&session_reader);
for (file_path, deltas) in deltas_reader
.read(None)
@ -86,7 +86,10 @@ impl super::Handler {
self.index_deltas(*project_id, session.id, &file_path, &deltas)?;
}
(self.send_event)(&app_events::Event::session(*project_id, session))?;
(self.send_event)(Change::Session {
project_id: *project_id,
session,
})?;
Ok(())
}
}

View File

@ -1,104 +1,29 @@
//! Implement the file-monitoring agent that informs about changes in interesting locations.
#![deny(missing_docs)]
#![deny(unsafe_code, rust_2018_idioms)]
#![allow(clippy::doc_markdown, clippy::missing_errors_doc)]
#![feature(slice_as_chunks)]
mod events;
pub use events::Event;
use events::InternalEvent;
pub use events::{Action, Change};
mod file_monitor;
mod handler;
pub use handler::Handler;
use std::path::Path;
use std::{sync::Arc, time};
use std::time;
use anyhow::{Context, Result};
use futures::executor::block_on;
use gitbutler_core::projects::{self, Project, ProjectId};
use tauri::AppHandle;
use gitbutler_core::projects::ProjectId;
use tokio::{
sync::mpsc::{unbounded_channel, UnboundedSender},
task,
};
use tokio_util::sync::CancellationToken;
use tracing::instrument;
/// Note that this type is managed in Tauri and thus needs to be send and sync.
#[derive(Clone)]
pub struct Watchers {
/// NOTE: This handle is required for this type to be self-contained as it's used by `core` through a trait.
app_handle: AppHandle,
/// The watcher of the currently active project.
/// NOTE: This is a `tokio` mutex as this needs to lock the inner option from within async.
watcher: Arc<tokio::sync::Mutex<Option<WatcherHandle>>>,
}
impl Watchers {
pub fn new(app_handle: AppHandle) -> Self {
Self {
app_handle,
watcher: Default::default(),
}
}
#[instrument(skip(self, project), err(Debug))]
pub fn watch(&self, project: &projects::Project) -> Result<()> {
let handler = handler::Handler::from_app(&self.app_handle)?;
let project_id = project.id;
let project_path = project.path.clone();
let handle = watch_in_background(handler, project_path, project_id)?;
block_on(self.watcher.lock()).replace(handle);
Ok(())
}
pub async fn post(&self, event: Event) -> Result<()> {
let watcher = self.watcher.lock().await;
if let Some(handle) = watcher
.as_ref()
.filter(|watcher| watcher.project_id == event.project_id())
{
handle.post(event).await.context("failed to post event")
} else {
Err(anyhow::anyhow!("watcher not found",))
}
}
pub async fn stop(&self, project_id: ProjectId) {
let mut handle = self.watcher.lock().await;
if handle
.as_ref()
.map_or(false, |handle| handle.project_id == project_id)
{
handle.take();
}
}
}
#[async_trait::async_trait]
impl gitbutler_core::projects::Watchers for Watchers {
fn watch(&self, project: &Project) -> Result<()> {
Watchers::watch(self, project)
}
async fn stop(&self, id: ProjectId) {
Watchers::stop(self, id).await
}
async fn fetch_gb_data(&self, id: ProjectId) -> Result<()> {
self.post(Event::FetchGitbutlerData(id)).await
}
async fn push_gb_data(&self, id: ProjectId) -> Result<()> {
self.post(Event::PushGitbutlerData(id)).await
}
}
/// An abstraction over a link to the spawned watcher, which runs in the background.
struct WatcherHandle {
pub struct WatcherHandle {
/// A way to post events and interact with the actual handler in the background.
tx: UnboundedSender<InternalEvent>,
/// The id of the project we are watching.
@ -114,10 +39,18 @@ impl Drop for WatcherHandle {
}
impl WatcherHandle {
pub async fn post(&self, event: Event) -> Result<()> {
self.tx.send(event.into()).context("failed to send event")?;
/// Post an `action` for the watcher to perform.
pub async fn post(&self, action: Action) -> Result<()> {
self.tx
.send(action.into())
.context("failed to send event")?;
Ok(())
}
/// Return the id of the project we are watching.
pub fn project_id(&self) -> ProjectId {
self.project_id
}
}
/// Run our file watcher processing loop in the background and let `handler` deal with them.
@ -128,7 +61,7 @@ impl WatcherHandle {
///
/// It runs in such a way that each filesystem event is processed concurrently with others, which is why
/// spamming massive amounts of events should be avoided!
fn watch_in_background(
pub fn watch_in_background(
handler: handler::Handler,
path: impl AsRef<Path>,
project_id: ProjectId,

View File

@ -11,18 +11,17 @@ use gitbutler_core::{
reader, sessions,
virtual_branches::{self, branch, VirtualBranchesHandle},
};
use gitbutler_tauri::watcher;
use once_cell::sync::Lazy;
use self::branch::BranchId;
use crate::watcher::handler::support::Fixture;
use crate::handler::support::Fixture;
use gitbutler_testsupport::{commit_all, Case};
static TEST_TARGET_INDEX: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
#[derive(Clone)]
pub struct State {
inner: watcher::Handler,
inner: gitbutler_watcher::Handler,
}
impl State {

View File

@ -1,10 +1,9 @@
use std::time::SystemTime;
use gitbutler_core::projects;
use pretty_assertions::assert_eq;
use crate::watcher::handler::support::Fixture;
use crate::watcher::handler::test_remote_repository;
use crate::handler::support::Fixture;
use crate::handler::test_remote_repository;
use gitbutler_testsupport::Case;
#[tokio::test]

View File

@ -2,11 +2,10 @@ use std::fs;
use anyhow::Result;
use gitbutler_core::projects;
use gitbutler_tauri::watcher;
use pretty_assertions::assert_eq;
use crate::watcher::handler::support::Fixture;
use crate::handler::support::Fixture;
use gitbutler_testsupport::Case;
use gitbutler_watcher::Change;
#[tokio::test]
async fn flush_session() -> Result<()> {
@ -32,10 +31,10 @@ async fn flush_session() -> Result<()> {
let events = fixture.events();
assert_eq!(events.len(), 4);
assert!(events[0].name().ends_with("/files"));
assert!(events[1].name().ends_with("/deltas"));
assert!(events[2].name().ends_with("/sessions"));
assert!(events[3].name().ends_with("/sessions"));
assert!(matches!(events[0], Change::File { .. }));
assert!(matches!(events[1], Change::Deltas { .. }));
assert!(matches!(events[2], Change::Session { .. }));
assert!(matches!(events[3], Change::Session { .. }));
Ok(())
}
@ -57,9 +56,9 @@ async fn do_not_flush_session_if_file_is_missing() -> Result<()> {
}
let events = fixture.events();
assert_eq!(events.len(), 3);
assert!(events[0].name().ends_with("/files"));
assert!(events[1].name().ends_with("/deltas"));
assert!(events[2].name().ends_with("/sessions"));
assert!(matches!(events[0], Change::File { .. }));
assert!(matches!(events[1], Change::Deltas { .. }));
assert!(matches!(events[2], Change::Session { .. }));
Ok(())
}
@ -83,7 +82,7 @@ async fn flush_deletes_flush_file_without_session_to_flush() -> Result<()> {
fn create_new_session_via_new_file(
project: &projects::Project,
fixture: &mut Fixture,
) -> watcher::Handler {
) -> gitbutler_watcher::Handler {
fs::write(project.path.join("test.txt"), "test").unwrap();
let handler = fixture.new_handler();

View File

@ -2,7 +2,6 @@ use tempfile::TempDir;
mod support {
use gitbutler_core::{assets, deltas, git, sessions, virtual_branches};
use gitbutler_tauri::{analytics, watcher};
use tempfile::TempDir;
/// Like [`gitbutler_testsupport::Suite`], but with all the instances needed to build a handler
@ -13,8 +12,8 @@ mod support {
pub vbranch_controller: virtual_branches::Controller,
pub assets_proxy: assets::Proxy,
/// Keeps events emitted from the last created handler.
events: Option<std::sync::mpsc::Receiver<gitbutler_tauri::Event>>,
/// Keeps changes emitted from the last created handler.
changes: Option<std::sync::mpsc::Receiver<gitbutler_watcher::Change>>,
/// Storage for the databases, to be dropped last.
_tmp: TempDir,
}
@ -49,7 +48,7 @@ mod support {
deltas_db,
vbranch_controller,
assets_proxy,
events: None,
changes: None,
_tmp: tmp,
}
}
@ -59,12 +58,12 @@ mod support {
/// Must be mut as handler events are collected into the fixture automatically.
///
/// Note that this only works for the most recent created handler.
pub fn new_handler(&mut self) -> watcher::Handler {
pub fn new_handler(&mut self) -> gitbutler_watcher::Handler {
let (tx, rx) = std::sync::mpsc::channel();
self.events = Some(rx);
watcher::Handler::new(
self.changes = Some(rx);
gitbutler_watcher::Handler::new(
self.local_app_data().to_owned(),
analytics::Client::default(),
gitbutler_analytics::Client::default(),
self.users.clone(),
self.projects.clone(),
self.vbranch_controller.clone(),
@ -76,8 +75,8 @@ mod support {
}
/// Returns the events that were emitted to the tauri app.
pub fn events(&mut self) -> Vec<gitbutler_tauri::Event> {
let Some(rx) = self.events.as_ref() else {
pub fn events(&mut self) -> Vec<gitbutler_watcher::Change> {
let Some(rx) = self.changes.as_ref() else {
return Vec::new();
};
let mut out = Vec::new();

View File

@ -3,8 +3,8 @@ use std::{collections::HashMap, path::PathBuf};
use anyhow::Result;
use gitbutler_core::{git, project_repository::LogUntil, projects};
use crate::watcher::handler::support::Fixture;
use crate::watcher::handler::test_remote_repository;
use crate::handler::support::Fixture;
use crate::handler::test_remote_repository;
use gitbutler_testsupport::{virtual_branches::set_test_target, Case};
fn log_walk(repo: &git2::Repository, head: git::Oid) -> Vec<git::Oid> {