diff --git a/crates/gitbutler-core/src/projects/controller.rs b/crates/gitbutler-core/src/projects/controller.rs index dabab4b12..af3e0fcb2 100644 --- a/crates/gitbutler-core/src/projects/controller.rs +++ b/crates/gitbutler-core/src/projects/controller.rs @@ -20,7 +20,6 @@ pub trait Watchers { /// Stop watching filesystem changes. async fn stop(&self, id: ProjectId); async fn fetch_gb_data(&self, id: ProjectId) -> anyhow::Result<()>; - async fn push_gb_data(&self, id: ProjectId) -> anyhow::Result<()>; } #[derive(Clone)] @@ -180,14 +179,6 @@ impl Controller { ); } } - - if let Err(error) = watchers.push_gb_data(project.id).await { - tracing::error!( - project_id = %project.id, - ?error, - "failed to post push project event" - ); - } } } diff --git a/crates/gitbutler-tauri/src/watcher.rs b/crates/gitbutler-tauri/src/watcher.rs index 0c050251c..3853eee1c 100644 --- a/crates/gitbutler-tauri/src/watcher.rs +++ b/crates/gitbutler-tauri/src/watcher.rs @@ -196,9 +196,4 @@ impl gitbutler_core::projects::Watchers for Watchers { self.post(gitbutler_watcher::Action::FetchGitbutlerData(id)) .await } - - async fn push_gb_data(&self, id: ProjectId) -> Result<()> { - self.post(gitbutler_watcher::Action::PushGitbutlerData(id)) - .await - } } diff --git a/crates/gitbutler-watcher/src/events.rs b/crates/gitbutler-watcher/src/events.rs index c350417f5..227f360be 100644 --- a/crates/gitbutler-watcher/src/events.rs +++ b/crates/gitbutler-watcher/src/events.rs @@ -11,7 +11,6 @@ pub(super) enum InternalEvent { Flush(ProjectId, sessions::Session), CalculateVirtualBranches(ProjectId), FetchGitbutlerData(ProjectId), - PushGitbutlerData(ProjectId), // From file monitor GitFilesChange(ProjectId, Vec), @@ -27,7 +26,6 @@ pub enum Action { Flush(ProjectId, sessions::Session), CalculateVirtualBranches(ProjectId), FetchGitbutlerData(ProjectId), - PushGitbutlerData(ProjectId), } impl Action { @@ -36,8 +34,7 @@ impl Action { match self { Action::FetchGitbutlerData(project_id) | Action::Flush(project_id, _) - | Action::CalculateVirtualBranches(project_id) - | Action::PushGitbutlerData(project_id) => *project_id, + | Action::CalculateVirtualBranches(project_id) => *project_id, } } } @@ -48,7 +45,6 @@ impl From for InternalEvent { 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), } } } @@ -79,7 +75,6 @@ impl Display for InternalEvent { ) } InternalEvent::CalculateVirtualBranches(pid) => write!(f, "VirtualBranch({})", pid), - InternalEvent::PushGitbutlerData(pid) => write!(f, "PushGitbutlerData({})", pid), } } } diff --git a/crates/gitbutler-watcher/src/handler/mod.rs b/crates/gitbutler-watcher/src/handler/mod.rs index 18c2a6853..8b4d0f680 100644 --- a/crates/gitbutler-watcher/src/handler/mod.rs +++ b/crates/gitbutler-watcher/src/handler/mod.rs @@ -1,6 +1,5 @@ mod calculate_deltas; mod index; -mod push_project_to_gitbutler; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -85,10 +84,6 @@ impl Handler { .await .context("failed to handle git file change event"), - events::InternalEvent::PushGitbutlerData(project_id) => self - .push_gb_data(project_id) - .context("failed to push gitbutler data"), - events::InternalEvent::FetchGitbutlerData(project_id) => self .fetch_gb_data(project_id, now) .await @@ -152,12 +147,6 @@ impl Handler { self.index_session(project_id, session)?; - let push_gb_data = tokio::task::spawn_blocking({ - let this = self.clone(); - move || this.push_gb_data(project_id) - }); - self.push_project_to_gitbutler(project_id, 1000).await?; - push_gb_data.await??; Ok(()) } @@ -183,25 +172,6 @@ impl Handler { } } - /// NOTE: this is an honest non-async function, and it should stay that way to avoid - /// dealing with git2 repositories across await points, which aren't `Send`. - fn push_gb_data(&self, project_id: ProjectId) -> Result<()> { - let user = self.users.get_user()?; - let project = self.projects.get(&project_id)?; - let project_repository = - project_repository::Repository::open(&project).context("failed to open repository")?; - let gb_repo = gb_repository::Repository::open( - &self.local_data_dir, - &project_repository, - user.as_ref(), - ) - .context("failed to open repository")?; - - gb_repo - .push(user.as_ref()) - .context("failed to push gb repo") - } - pub async fn fetch_gb_data(&self, project_id: ProjectId, now: time::SystemTime) -> Result<()> { let user = self.users.get_user()?; let project = self diff --git a/crates/gitbutler-watcher/src/handler/push_project_to_gitbutler.rs b/crates/gitbutler-watcher/src/handler/push_project_to_gitbutler.rs deleted file mode 100644 index a406724bd..000000000 --- a/crates/gitbutler-watcher/src/handler/push_project_to_gitbutler.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::time; - -use anyhow::{Context, Result}; -use gitbutler_core::id::Id; -use gitbutler_core::{ - gb_repository, - git::{self, Oid, Repository}, - project_repository, - projects::{self, CodePushState, ProjectId}, - users, -}; -use itertools::Itertools; - -impl super::Handler { - pub async fn push_project_to_gitbutler( - &self, - project_id: ProjectId, - batch_size: usize, - ) -> Result<()> { - let project = self - .projects - .get(&project_id) - .context("failed to get project")?; - - if !project.is_sync_enabled() || !project.has_code_url() { - return Ok(()); - } - - let user = self.users.get_user()?; - let project_repository = - project_repository::Repository::open(&project).context("failed to open repository")?; - let gb_code_last_commit = project - .gitbutler_code_push_state - .as_ref() - .map(|state| &state.id) - .copied(); - let gb_repository = gb_repository::Repository::open( - &self.local_data_dir, - &project_repository, - user.as_ref(), - )?; - let default_target = gb_repository - .default_target() - .context("failed to open gb repo")? - .context("failed to get default target")?; - - let target_changed = gb_code_last_commit.map_or(true, |id| id != default_target.sha); - if target_changed { - match self - .push_target( - &project_repository, - &default_target, - gb_code_last_commit, - project_id, - user.as_ref(), - batch_size, - ) - .await - { - Ok(()) => {} - Err(project_repository::RemoteError::Network) => return Ok(()), - Err(err) => return Err(err).context("failed to push"), - }; - } - - tokio::task::spawn_blocking(move || -> Result<()> { - match push_all_refs(&project_repository, user.as_ref(), project_id) { - Ok(()) => Ok(()), - Err(project_repository::RemoteError::Network) => Ok(()), - Err(err) => Err(err).context("failed to push"), - } - }) - .await??; - - // make sure last push time is updated - self.update_project(project_id, default_target.sha).await?; - Ok(()) - } -} - -/// Currently required to make functionality testable without requiring a `Handler` with all of its state. -impl super::Handler { - async fn update_project( - &self, - project_id: Id, - id: Oid, - ) -> Result<(), project_repository::RemoteError> { - self.projects - .update(&projects::UpdateRequest { - id: project_id, - gitbutler_code_push_state: Some(CodePushState { - id, - timestamp: time::SystemTime::now(), - }), - ..Default::default() - }) - .await - .context("failed to update last push")?; - Ok(()) - } - - async fn push_target( - &self, - project_repository: &project_repository::Repository, - default_target: &gitbutler_core::virtual_branches::target::Target, - gb_code_last_commit: Option, - project_id: Id, - user: Option<&users::User>, - batch_size: usize, - ) -> Result<(), project_repository::RemoteError> { - let ids = batch_rev_walk( - &project_repository.git_repository, - batch_size, - default_target.sha, - gb_code_last_commit, - )?; - - tracing::info!( - %project_id, - batches=%ids.len(), - "batches left to push", - ); - - let id_count = ids.len(); - for (idx, id) in ids.iter().enumerate().rev() { - let refspec = format!("+{}:refs/push-tmp/{}", id, project_id); - - project_repository.push_to_gitbutler_server(user, &[&refspec])?; - self.update_project(project_id, *id).await?; - - tracing::info!( - %project_id, - i = id_count.saturating_sub(idx), - total = id_count, - "project batch pushed", - ); - } - - project_repository.push_to_gitbutler_server( - user, - &[&format!("+{}:refs/{}", default_target.sha, project_id)], - )?; - - //TODO: remove push-tmp ref - tracing::info!( - %project_id, - "project target ref fully pushed", - ); - Ok(()) - } -} - -fn push_all_refs( - project_repository: &project_repository::Repository, - user: Option<&users::User>, - project_id: Id, -) -> Result<(), project_repository::RemoteError> { - let gb_references = collect_refs(project_repository)?; - let all_refs: Vec<_> = gb_references - .iter() - .filter(|r| { - matches!( - r, - git::Refname::Remote(_) | git::Refname::Virtual(_) | git::Refname::Local(_) - ) - }) - .map(|r| format!("+{}:{}", r, r)) - .collect(); - - let all_refs: Vec<_> = all_refs.iter().map(String::as_str).collect(); - let anything_pushed = project_repository.push_to_gitbutler_server(user, &all_refs)?; - if anything_pushed { - tracing::info!( - %project_id, - "refs pushed", - ); - } - Ok(()) -} - -fn collect_refs( - project_repository: &project_repository::Repository, -) -> anyhow::Result> { - Ok(project_repository - .git_repository - .references_glob("refs/*")? - .flatten() - .filter_map(|r| r.name()) - .collect::>()) -} - -fn batch_rev_walk( - repo: &Repository, - batch_size: usize, - from: Oid, - until: Option, -) -> Result> { - let mut revwalk = repo.revwalk().context("failed to create revwalk")?; - revwalk - .push(from.into()) - .context(format!("failed to push {}", from))?; - if let Some(oid) = until { - revwalk - .hide(oid.into()) - .context(format!("failed to hide {}", oid))?; - } - let mut oids = Vec::new(); - oids.push(from); - - let from = from.into(); - for batch in &revwalk.chunks(batch_size) { - let Some(oid) = batch.last() else { continue }; - let oid = oid.context("failed to get oid")?; - if oid != from { - oids.push(oid.into()); - } - } - Ok(oids) -} diff --git a/crates/gitbutler-watcher/tests/handler/mod.rs b/crates/gitbutler-watcher/tests/handler/mod.rs index 0dd6b9c12..774deaee5 100644 --- a/crates/gitbutler-watcher/tests/handler/mod.rs +++ b/crates/gitbutler-watcher/tests/handler/mod.rs @@ -98,4 +98,3 @@ fn test_remote_repository() -> anyhow::Result<(git2::Repository, TempDir)> { mod calculate_delta; mod fetch_gitbutler_data; mod git_file_change; -mod push_project_to_gitbutler; diff --git a/crates/gitbutler-watcher/tests/handler/push_project_to_gitbutler.rs b/crates/gitbutler-watcher/tests/handler/push_project_to_gitbutler.rs deleted file mode 100644 index 1f4386b8f..000000000 --- a/crates/gitbutler-watcher/tests/handler/push_project_to_gitbutler.rs +++ /dev/null @@ -1,383 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use anyhow::Result; -use gitbutler_core::{git, project_repository::LogUntil, projects}; - -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 { - let mut walker = repo.revwalk().unwrap(); - walker.push(head.into()).unwrap(); - walker.map(|oid| oid.unwrap().into()).collect::>() -} - -#[tokio::test] -async fn push_error() -> Result<()> { - let mut fixture = Fixture::default(); - let handler = fixture.new_handler(); - let Case { project, .. } = &fixture.new_case(); - - let api_project = projects::ApiProject { - name: "test-sync".to_string(), - description: None, - repository_id: "123".to_string(), - git_url: String::new(), - code_git_url: Some(String::new()), - created_at: 0_i32.to_string(), - updated_at: 0_i32.to_string(), - sync: true, - }; - - fixture - .projects - .update(&projects::UpdateRequest { - id: project.id, - api: Some(api_project.clone()), - ..Default::default() - }) - .await?; - - let res = handler.push_project_to_gitbutler(project.id, 100).await; - let err = res.unwrap_err(); - assert_eq!(err.to_string(), "failed to get default target"); - - Ok(()) -} - -#[tokio::test] -async fn push_simple() -> Result<()> { - let mut fixture = Fixture::default(); - let handler = fixture.new_handler(); - let Case { - project, - gb_repository, - project_repository, - .. - } = &fixture.new_case_with_files(HashMap::from([(PathBuf::from("test.txt"), "test")])); - - fixture.sign_in(); - set_test_target(project_repository).unwrap(); - - let target_id = gb_repository.default_target().unwrap().unwrap().sha; - let reference = project_repository.l(target_id, LogUntil::End).unwrap(); - let (cloud_code, _tmp) = test_remote_repository()?; - let api_project = projects::ApiProject { - name: "test-sync".to_string(), - description: None, - repository_id: "123".to_string(), - git_url: String::new(), - code_git_url: Some(cloud_code.path().to_str().unwrap().to_string()), - created_at: 0_i32.to_string(), - updated_at: 0_i32.to_string(), - sync: true, - }; - - fixture - .projects - .update(&projects::UpdateRequest { - id: project.id, - api: Some(api_project.clone()), - ..Default::default() - }) - .await?; - - cloud_code.find_commit(target_id.into()).unwrap_err(); - - { - handler - .push_project_to_gitbutler(project.id, 10) - .await - .unwrap(); - } - - cloud_code.find_commit(target_id.into()).unwrap(); - - let pushed = log_walk(&cloud_code, target_id); - assert_eq!(reference.len(), pushed.len()); - assert_eq!(reference, pushed); - - assert_eq!( - fixture - .projects - .get(&project.id) - .unwrap() - .gitbutler_code_push_state - .unwrap() - .id, - target_id - ); - - Ok(()) -} - -#[tokio::test] -async fn push_remote_ref() -> Result<()> { - let mut fixture = Fixture::default(); - let handler = fixture.new_handler(); - let Case { - project, - project_repository, - .. - } = &fixture.new_case(); - - fixture.sign_in(); - set_test_target(project_repository).unwrap(); - - let (cloud_code, _tmp) = test_remote_repository()?; - let cloud_code: git::Repository = cloud_code.into(); - - let (remote_repo, _tmp) = test_remote_repository()?; - let remote_repo: git::Repository = remote_repo.into(); - - let last_commit = create_initial_commit(&remote_repo); - - remote_repo - .reference( - &git::Refname::Local(git::LocalRefname::new("refs/heads/testbranch", None)), - last_commit, - false, - "", - ) - .unwrap(); - - let mut remote = project_repository - .git_repository - .remote("tr", &remote_repo.path().to_str().unwrap().parse().unwrap()) - .unwrap(); - - remote - .fetch(&["+refs/heads/*:refs/remotes/tr/*"], None) - .unwrap(); - - project_repository - .git_repository - .find_commit(last_commit) - .unwrap(); - - let api_project = projects::ApiProject { - name: "test-sync".to_string(), - description: None, - repository_id: "123".to_string(), - git_url: String::new(), - code_git_url: Some(cloud_code.path().to_str().unwrap().to_string()), - created_at: 0_i32.to_string(), - updated_at: 0_i32.to_string(), - sync: true, - }; - - fixture - .projects - .update(&projects::UpdateRequest { - id: project.id, - api: Some(api_project.clone()), - ..Default::default() - }) - .await?; - - { - handler - .push_project_to_gitbutler(project.id, 10) - .await - .unwrap(); - } - - cloud_code.find_commit(last_commit).unwrap(); - Ok(()) -} - -fn create_initial_commit(repo: &git::Repository) -> git::Oid { - let signature = git::Signature::now("test", "test@email.com").unwrap(); - - let mut index = repo.index().unwrap(); - let oid = index.write_tree().unwrap(); - - repo.commit( - None, - &signature, - &signature, - "initial commit", - &repo.find_tree(oid).unwrap(), - &[], - ) - .unwrap() -} - -fn create_test_commits(repo: &git::Repository, commits: usize) -> git::Oid { - let signature = git::Signature::now("test", "test@email.com").unwrap(); - - let mut last = None; - - for i in 0..commits { - let mut index = repo.index().unwrap(); - let oid = index.write_tree().unwrap(); - let head = repo.head().unwrap(); - - last = Some( - repo.commit( - Some(&head.name().unwrap()), - &signature, - &signature, - format!("commit {i}").as_str(), - &repo.find_tree(oid).unwrap(), - &[&repo - .find_commit(repo.refname_to_id("HEAD").unwrap()) - .unwrap()], - ) - .unwrap(), - ); - } - - last.unwrap() -} - -#[tokio::test] -async fn push_batches() -> Result<()> { - let mut fixture = Fixture::default(); - let handler = fixture.new_handler(); - let Case { - project, - gb_repository, - project_repository, - .. - } = &fixture.new_case(); - - fixture.sign_in(); - - { - let head: git::Oid = project_repository - .get_head() - .unwrap() - .peel_to_commit() - .unwrap() - .id(); - - let reference = project_repository.l(head, LogUntil::End).unwrap(); - assert_eq!(reference.len(), 2); - - let head = create_test_commits(&project_repository.git_repository, 10); - - let reference = project_repository.l(head, LogUntil::End).unwrap(); - assert_eq!(reference.len(), 12); - } - - set_test_target(project_repository).unwrap(); - - let target_id = gb_repository.default_target().unwrap().unwrap().sha; - let reference = project_repository.l(target_id, LogUntil::End).unwrap(); - let (cloud_code, _tmp) = test_remote_repository()?; - - let api_project = projects::ApiProject { - name: "test-sync".to_string(), - description: None, - repository_id: "123".to_string(), - git_url: String::new(), - code_git_url: Some(cloud_code.path().to_str().unwrap().to_string()), - created_at: 0_i32.to_string(), - updated_at: 0_i32.to_string(), - sync: true, - }; - - fixture - .projects - .update(&projects::UpdateRequest { - id: project.id, - api: Some(api_project.clone()), - ..Default::default() - }) - .await?; - - { - handler - .push_project_to_gitbutler(project.id, 2) - .await - .unwrap(); - } - - cloud_code.find_commit(target_id.into()).unwrap(); - - let pushed = log_walk(&cloud_code, target_id); - assert_eq!(reference.len(), pushed.len()); - assert_eq!(reference, pushed); - - assert_eq!( - fixture - .projects - .get(&project.id) - .unwrap() - .gitbutler_code_push_state - .unwrap() - .id, - target_id - ); - - Ok(()) -} - -#[tokio::test] -async fn push_again_no_change() -> Result<()> { - let mut fixture = Fixture::default(); - let handler = fixture.new_handler(); - let Case { - project, - gb_repository, - project_repository, - .. - } = &fixture.new_case_with_files(HashMap::from([(PathBuf::from("test.txt"), "test")])); - - fixture.sign_in(); - - set_test_target(project_repository).unwrap(); - let target_id = gb_repository.default_target().unwrap().unwrap().sha; - let reference = project_repository.l(target_id, LogUntil::End).unwrap(); - let (cloud_code, _tmp) = test_remote_repository()?; - - let api_project = projects::ApiProject { - name: "test-sync".to_string(), - description: None, - repository_id: "123".to_string(), - git_url: String::new(), - code_git_url: Some(cloud_code.path().to_str().unwrap().to_string()), - created_at: 0_i32.to_string(), - updated_at: 0_i32.to_string(), - sync: true, - }; - - fixture - .projects - .update(&projects::UpdateRequest { - id: project.id, - api: Some(api_project.clone()), - ..Default::default() - }) - .await?; - - cloud_code.find_commit(target_id.into()).unwrap_err(); - - { - handler - .push_project_to_gitbutler(project.id, 10) - .await - .unwrap(); - } - - cloud_code.find_commit(target_id.into()).unwrap(); - - let pushed = log_walk(&cloud_code, target_id); - assert_eq!(reference.len(), pushed.len()); - assert_eq!(reference, pushed); - - assert_eq!( - fixture - .projects - .get(&project.id) - .unwrap() - .gitbutler_code_push_state - .unwrap() - .id, - target_id - ); - - Ok(()) -}