mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-29 20:43:37 +03:00
first stab at resurecting git sync
This commit is contained in:
parent
f71b76ccb4
commit
762bb31dea
@ -29,6 +29,7 @@ pub mod projects;
|
||||
pub mod reader;
|
||||
pub mod ssh;
|
||||
pub mod storage;
|
||||
pub mod synchronize;
|
||||
pub mod time;
|
||||
pub mod types;
|
||||
pub mod users;
|
||||
|
@ -81,6 +81,8 @@ pub trait Oplog {
|
||||
///
|
||||
/// This is useful to show what has changed in this particular snapshot
|
||||
fn snapshot_diff(&self, sha: String) -> Result<HashMap<PathBuf, FileDiff>>;
|
||||
/// Gets the sha of the last snapshot commit if present.
|
||||
fn oplog_head(&self) -> Result<Option<String>>;
|
||||
}
|
||||
|
||||
impl Oplog for Project {
|
||||
@ -625,6 +627,10 @@ impl Oplog for Project {
|
||||
let hunks = hunks_by_filepath(None, &diff)?;
|
||||
Ok(hunks)
|
||||
}
|
||||
fn oplog_head(&self) -> Result<Option<String>> {
|
||||
let oplog_state = OplogHandle::new(&self.gb_dir());
|
||||
oplog_state.get_oplog_head()
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_conflicts_tree(
|
||||
|
192
crates/gitbutler-core/src/synchronize/mod.rs
Normal file
192
crates/gitbutler-core/src/synchronize/mod.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use std::time;
|
||||
|
||||
use crate::id::Id;
|
||||
use crate::ops::oplog::Oplog;
|
||||
use crate::{
|
||||
git::{self, Oid},
|
||||
project_repository,
|
||||
projects::{self, CodePushState},
|
||||
users,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub async fn sync_with_gitbutler(
|
||||
project_repository: &project_repository::Repository,
|
||||
user: &users::User,
|
||||
projects: &projects::Controller,
|
||||
) -> Result<()> {
|
||||
let project = project_repository.project();
|
||||
let vb_state = project.virtual_branches();
|
||||
let default_target = vb_state.get_default_target()?;
|
||||
let gb_code_last_commit = project
|
||||
.gitbutler_code_push_state
|
||||
.as_ref()
|
||||
.map(|state| &state.id)
|
||||
.copied();
|
||||
|
||||
// Push target
|
||||
push_target(
|
||||
projects,
|
||||
project_repository,
|
||||
&default_target,
|
||||
gb_code_last_commit,
|
||||
project.id,
|
||||
user,
|
||||
12,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Push all refs
|
||||
push_all_refs(project_repository, user, project.id)?;
|
||||
|
||||
// Push Oplog head
|
||||
let oplog_refspec = project_repository
|
||||
.project()
|
||||
.oplog_head()?
|
||||
.map(|sha| format!("+{}:refs/gitbutler/oplog/oplog", sha));
|
||||
|
||||
if let Some(oplog_refspec) = oplog_refspec {
|
||||
let x = project_repository.push_to_gitbutler_server(Some(user), &[&oplog_refspec]);
|
||||
println!("\n\n\nHERE: {:?}", x?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn push_target(
|
||||
projects: &projects::Controller,
|
||||
project_repository: &project_repository::Repository,
|
||||
default_target: &crate::virtual_branches::target::Target,
|
||||
gb_code_last_commit: Option<Oid>,
|
||||
project_id: Id<projects::Project>,
|
||||
user: &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(Some(user), &[&refspec])?;
|
||||
update_project(projects, 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(
|
||||
Some(user),
|
||||
&[&format!("+{}:refs/{}", default_target.sha, project_id)],
|
||||
)?;
|
||||
|
||||
//TODO: remove push-tmp ref
|
||||
tracing::info!(
|
||||
%project_id,
|
||||
"project target ref fully pushed",
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn batch_rev_walk(
|
||||
repo: &crate::git::Repository,
|
||||
batch_size: usize,
|
||||
from: Oid,
|
||||
until: Option<Oid>,
|
||||
) -> Result<Vec<Oid>> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn collect_refs(
|
||||
project_repository: &project_repository::Repository,
|
||||
) -> anyhow::Result<Vec<git::Refname>> {
|
||||
Ok(project_repository
|
||||
.git_repository
|
||||
.references_glob("refs/*")?
|
||||
.flatten()
|
||||
.filter_map(|r| r.name())
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn push_all_refs(
|
||||
project_repository: &project_repository::Repository,
|
||||
user: &users::User,
|
||||
project_id: Id<projects::Project>,
|
||||
) -> 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(Some(user), &all_refs)?;
|
||||
if anything_pushed {
|
||||
tracing::info!(
|
||||
%project_id,
|
||||
"refs pushed",
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn update_project(
|
||||
projects: &projects::Controller,
|
||||
project_id: Id<projects::Project>,
|
||||
id: Oid,
|
||||
) -> Result<(), project_repository::RemoteError> {
|
||||
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(())
|
||||
}
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::{Context, Result};
|
||||
use futures::executor::block_on;
|
||||
use gitbutler_core::projects::{self, Project, ProjectId};
|
||||
use gitbutler_core::{assets, virtual_branches};
|
||||
use gitbutler_core::{assets, users, virtual_branches};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tracing::instrument;
|
||||
|
||||
@ -75,11 +75,13 @@ pub struct Watchers {
|
||||
|
||||
fn handler_from_app(app: &AppHandle) -> anyhow::Result<gitbutler_watcher::Handler> {
|
||||
let projects = app.state::<projects::Controller>().inner().clone();
|
||||
let users = app.state::<users::Controller>().inner().clone();
|
||||
let vbranches = app.state::<virtual_branches::Controller>().inner().clone();
|
||||
let assets_proxy = app.state::<assets::Proxy>().inner().clone();
|
||||
|
||||
Ok(gitbutler_watcher::Handler::new(
|
||||
projects,
|
||||
users,
|
||||
vbranches,
|
||||
assets_proxy,
|
||||
{
|
||||
|
@ -5,8 +5,9 @@ use anyhow::{Context, Result};
|
||||
use gitbutler_core::ops::entry::{OperationType, SnapshotDetails};
|
||||
use gitbutler_core::ops::oplog::Oplog;
|
||||
use gitbutler_core::projects::ProjectId;
|
||||
use gitbutler_core::synchronize::sync_with_gitbutler;
|
||||
use gitbutler_core::virtual_branches::VirtualBranches;
|
||||
use gitbutler_core::{assets, git, project_repository, projects, virtual_branches};
|
||||
use gitbutler_core::{assets, git, project_repository, projects, users, virtual_branches};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::{events, Change};
|
||||
@ -22,6 +23,7 @@ pub struct Handler {
|
||||
// the tauri app, assuming that such application would not be `Send + Sync` everywhere and thus would
|
||||
// need extra protection.
|
||||
projects: projects::Controller,
|
||||
users: users::Controller,
|
||||
vbranch_controller: virtual_branches::Controller,
|
||||
assets_proxy: assets::Proxy,
|
||||
|
||||
@ -35,12 +37,14 @@ impl Handler {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
projects: projects::Controller,
|
||||
users: users::Controller,
|
||||
vbranch_controller: virtual_branches::Controller,
|
||||
assets_proxy: assets::Proxy,
|
||||
send_event: impl Fn(Change) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Handler {
|
||||
projects,
|
||||
users,
|
||||
vbranch_controller,
|
||||
assets_proxy,
|
||||
send_event: Arc::new(send_event),
|
||||
@ -185,8 +189,22 @@ impl Handler {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn gitbutler_oplog_change(&self, _project_id: ProjectId) -> Result<()> {
|
||||
// TODO: Queue up pushing of data here, if configured
|
||||
|
||||
/// Invoked whenever there's a new oplog entry.
|
||||
/// If synchronizing with GitButler's servers is enabled it will push Oplog refs
|
||||
async fn gitbutler_oplog_change(&self, project_id: ProjectId) -> Result<()> {
|
||||
let project = self
|
||||
.projects
|
||||
.get(&project_id)
|
||||
.context("failed to get project")?;
|
||||
|
||||
if project.is_sync_enabled() && project.has_code_url() {
|
||||
if let Some(user) = self.users.get_user()? {
|
||||
let repository = project_repository::Repository::open(&project)
|
||||
.context("failed to open project repository for project")?;
|
||||
return sync_with_gitbutler(&repository, &user, &self.projects).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user