mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 23:02:31 +03:00
Merge pull request #3687 from gitbutlerapp/snapshot-on-file-changes
snapshot on file changes
This commit is contained in:
commit
81bc5b327c
@ -29,6 +29,7 @@ pub mod path;
|
|||||||
pub mod project_repository;
|
pub mod project_repository;
|
||||||
pub mod projects;
|
pub mod projects;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
|
pub mod repo;
|
||||||
pub mod sessions;
|
pub mod sessions;
|
||||||
pub mod snapshots;
|
pub mod snapshots;
|
||||||
pub mod ssh;
|
pub mod ssh;
|
||||||
|
@ -84,6 +84,13 @@ pub struct Project {
|
|||||||
pub omit_certificate_check: Option<bool>,
|
pub omit_certificate_check: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enable_snapshots: Option<bool>,
|
pub enable_snapshots: Option<bool>,
|
||||||
|
// The number of changed lines that will trigger a snapshot
|
||||||
|
#[serde(default = "default_snapshot_lines_threshold")]
|
||||||
|
pub snapshot_lines_threshold: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_snapshot_lines_threshold() -> usize {
|
||||||
|
20
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Project> for Project {
|
impl AsRef<Project> for Project {
|
||||||
|
18
crates/gitbutler-core/src/repo.rs
Normal file
18
crates/gitbutler-core/src/repo.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
/// The GbRepository trait provides methods which are building blocks for Gitbutler functionality.
|
||||||
|
pub trait GbRepository {
|
||||||
|
/// Returns the number of uncommitted lines of code (added plus removed) in the repository. Included untracked files.
|
||||||
|
fn changed_lines_count(&self) -> Result<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GbRepository for git2::Repository {
|
||||||
|
fn changed_lines_count(&self) -> Result<usize> {
|
||||||
|
let head_tree = self.head()?.peel_to_commit()?.tree()?;
|
||||||
|
let mut opts = git2::DiffOptions::new();
|
||||||
|
opts.include_untracked(true);
|
||||||
|
let diff = self.diff_tree_to_workdir_with_index(Some(&head_tree), Some(&mut opts));
|
||||||
|
let stats = diff?.stats()?;
|
||||||
|
Ok(stats.deletions() + stats.insertions())
|
||||||
|
}
|
||||||
|
}
|
@ -152,6 +152,7 @@ pub enum OperationType {
|
|||||||
ReorderCommit,
|
ReorderCommit,
|
||||||
InsertBlankCommit,
|
InsertBlankCommit,
|
||||||
MoveCommitFile,
|
MoveCommitFile,
|
||||||
|
FileChanges,
|
||||||
#[default]
|
#[default]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ futures = "0.3.30"
|
|||||||
tokio = { workspace = true, features = [ "macros" ] }
|
tokio = { workspace = true, features = [ "macros" ] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
git2.workspace = true
|
||||||
|
|
||||||
backoff = "0.4.0"
|
backoff = "0.4.0"
|
||||||
notify = { version = "6.0.1" }
|
notify = { version = "6.0.1" }
|
||||||
|
@ -8,12 +8,16 @@ use std::{path, time};
|
|||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use gitbutler_core::projects::ProjectId;
|
use gitbutler_core::projects::ProjectId;
|
||||||
|
use gitbutler_core::repo::GbRepository;
|
||||||
use gitbutler_core::sessions::SessionId;
|
use gitbutler_core::sessions::SessionId;
|
||||||
|
use gitbutler_core::snapshots::entry::{OperationType, SnapshotDetails, Trailer};
|
||||||
|
use gitbutler_core::snapshots::snapshot;
|
||||||
use gitbutler_core::virtual_branches::VirtualBranches;
|
use gitbutler_core::virtual_branches::VirtualBranches;
|
||||||
use gitbutler_core::{
|
use gitbutler_core::{
|
||||||
assets, deltas, gb_repository, git, project_repository, projects, reader, sessions, users,
|
assets, deltas, gb_repository, git, project_repository, projects, reader, sessions, users,
|
||||||
virtual_branches,
|
virtual_branches,
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{events, Change};
|
use super::{events, Change};
|
||||||
@ -39,6 +43,9 @@ pub struct Handler {
|
|||||||
/// A function to send events - decoupled from app-handle for testing purposes.
|
/// A function to send events - decoupled from app-handle for testing purposes.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
send_event: Arc<dyn Fn(Change) -> Result<()> + Send + Sync + 'static>,
|
send_event: Arc<dyn Fn(Change) -> Result<()> + Send + Sync + 'static>,
|
||||||
|
|
||||||
|
// The number of changed lines since the value was last reset
|
||||||
|
changed_lines_count: Arc<Mutex<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
@ -63,6 +70,7 @@ impl Handler {
|
|||||||
sessions_db,
|
sessions_db,
|
||||||
deltas_db,
|
deltas_db,
|
||||||
send_event: Arc::new(send_event),
|
send_event: Arc::new(send_event),
|
||||||
|
changed_lines_count: Arc::new(Mutex::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,15 +286,57 @@ impl Handler {
|
|||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let paths_string = paths
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
let calc_deltas = tokio::task::spawn_blocking({
|
let calc_deltas = tokio::task::spawn_blocking({
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
move || this.calculate_deltas(paths, project_id)
|
move || this.calculate_deltas(paths, project_id)
|
||||||
});
|
});
|
||||||
|
let changed_lines_before = *self.changed_lines_count.lock().await;
|
||||||
|
// Create a snapshot every time there are more than 20 new lines of code
|
||||||
|
let handle_snapshots = tokio::task::spawn_blocking({
|
||||||
|
let this = self.clone();
|
||||||
|
move || this.maybe_create_snapshot(project_id, changed_lines_before, paths_string)
|
||||||
|
});
|
||||||
|
// Set changed lines count to the newly observed value
|
||||||
|
*self.changed_lines_count.lock().await = handle_snapshots.await??;
|
||||||
self.calculate_virtual_branches(project_id).await?;
|
self.calculate_virtual_branches(project_id).await?;
|
||||||
calc_deltas.await??;
|
calc_deltas.await??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maybe_create_snapshot(
|
||||||
|
&self,
|
||||||
|
project_id: ProjectId,
|
||||||
|
changed_lines_before: usize,
|
||||||
|
paths: String,
|
||||||
|
) -> anyhow::Result<usize> {
|
||||||
|
let project = self
|
||||||
|
.projects
|
||||||
|
.get(&project_id)
|
||||||
|
.context("failed to get project")?;
|
||||||
|
let repo_path = project.path.as_path();
|
||||||
|
let repo = git2::Repository::init(repo_path)?;
|
||||||
|
let changed_lines = repo.changed_lines_count()? - changed_lines_before;
|
||||||
|
if changed_lines > project.snapshot_lines_threshold {
|
||||||
|
let details = SnapshotDetails {
|
||||||
|
version: Default::default(),
|
||||||
|
operation: OperationType::FileChanges,
|
||||||
|
title: OperationType::FileChanges.to_string(),
|
||||||
|
body: None,
|
||||||
|
trailers: vec![Trailer {
|
||||||
|
key: "files".to_string(),
|
||||||
|
value: paths,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
snapshot::create(&project, details)?;
|
||||||
|
}
|
||||||
|
Ok(changed_lines)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn git_file_change(
|
pub async fn git_file_change(
|
||||||
&self,
|
&self,
|
||||||
path: impl Into<PathBuf>,
|
path: impl Into<PathBuf>,
|
||||||
|
Loading…
Reference in New Issue
Block a user