Merge pull request #3694 from gitbutlerapp/refactor-snapshot-functions-as-trait-methods

refactor snapshotting as an Oplog trait
This commit is contained in:
Kiril Videlov 2024-05-05 22:46:08 +02:00 committed by GitHub
commit f2b2434d7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 334 additions and 337 deletions

View File

@ -1,5 +1,5 @@
use anyhow::Result;
use gitbutler_core::{projects::Project, snapshots::snapshot};
use gitbutler_core::{projects::Project, snapshots::snapshot::Oplog};
use clap::{arg, Command};
#[cfg(not(windows))]
@ -49,7 +49,7 @@ fn main() -> Result<()> {
fn list_snapshots(repo_dir: &str) -> Result<()> {
let project = project_from_path(repo_dir);
let snapshots = snapshot::list(&project, 100)?;
let snapshots = project.list_snapshots(100)?;
for snapshot in snapshots {
let ts = chrono::DateTime::from_timestamp(snapshot.created_at / 1000, 0);
let details = snapshot.details;
@ -62,7 +62,7 @@ fn list_snapshots(repo_dir: &str) -> Result<()> {
fn restore_snapshot(repo_dir: &str, snapshot_id: &str) -> Result<()> {
let project = project_from_path(repo_dir);
snapshot::restore(&project, snapshot_id.to_owned())?;
project.restore_snapshot(snapshot_id.to_owned())?;
Ok(())
}

View File

@ -16,257 +16,270 @@ use super::{
const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024;
/// Creates a snapshot of the current state of the repository and virtual branches using the given label.
///
/// If this is the first shapshot created, supporting structures are initialized:
/// - The current oplog head is persisted in `.git/gitbutler/oplog.toml`.
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
///
/// The snapshot tree contains:
/// - The current state of the working directory under a subtree `workdir`.
/// - The state of virtual branches from `.git/gitbutler/virtual_branches.toml` as a blob `virtual_branches.toml`.
/// - The state of conflicts from `.git/base_merge_parent` and `.git/conflicts` if present as blobs under a subtree `conflicts`
///
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
/// Returns the sha of the created snapshot commit or None if snapshots are disabled.
pub fn create(project: &Project, details: SnapshotDetails) -> Result<Option<String>> {
if project.enable_snapshots.is_none() || project.enable_snapshots == Some(false) {
return Ok(None);
}
let repo_path = project.path.as_path();
let repo = git2::Repository::init(repo_path)?;
let vb_state = VirtualBranchesHandle::new(&project.gb_dir());
let default_target_sha = vb_state.get_default_target()?.sha;
let oplog_state = OplogHandle::new(&project.gb_dir());
let oplog_head_commit = match oplog_state.get_oplog_head()? {
Some(head_sha) => match repo.find_commit(git2::Oid::from_str(&head_sha)?) {
Ok(commit) => commit,
Err(_) => repo.find_commit(default_target_sha.into())?,
},
// This is the first snapshot - use the default target as starting point
None => repo.find_commit(default_target_sha.into())?,
};
// Create a blob out of `.git/gitbutler/virtual_branches.toml`
let vb_path = repo_path
.join(".git")
.join("gitbutler")
.join("virtual_branches.toml");
let vb_content = fs::read(vb_path)?;
let vb_blob = repo.blob(&vb_content)?;
// Create a tree out of the conflicts state if present
let conflicts_tree = write_conflicts_tree(repo_path, &repo)?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
// Add everything in the workdir to the index
let mut index = repo.index()?;
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
index.write()?;
// Create a tree out of the index
let tree_id = index.write_tree()?;
let mut tree_builder = repo.treebuilder(None)?;
tree_builder.insert("workdir", tree_id, FileMode::Tree.into())?;
tree_builder.insert("virtual_branches.toml", vb_blob, FileMode::Blob.into())?;
tree_builder.insert("conflicts", conflicts_tree, FileMode::Tree.into())?;
let tree_id = tree_builder.write()?;
let tree = repo.find_tree(tree_id)?;
// Construct a new commit
let name = "GitButler";
let email = "gitbutler@gitbutler.com";
let signature = git2::Signature::now(name, email).unwrap();
let new_commit_oid = repo.commit(
None,
&signature,
&signature,
&details.to_string(),
&tree,
&[&oplog_head_commit],
)?;
// Reset the workdir to how it was
let integration_branch = repo
.find_branch("gitbutler/integration", git2::BranchType::Local)?
.get()
.peel_to_commit()?;
repo.reset(
&integration_branch.into_object(),
git2::ResetType::Mixed,
None,
)?;
oplog_state.set_oplog_head(new_commit_oid.to_string())?;
set_reference_to_oplog(
project,
&default_target_sha.to_string(),
&new_commit_oid.to_string(),
)?;
Ok(Some(new_commit_oid.to_string()))
/// The Oplog trait allows for crating snapshots of the current state of the project as well as restoring to a previous snapshot.
/// Snapshots include the state of the working directory as well as all additional GitButler state (e.g virtual branches, conflict state).
pub trait Oplog {
/// Creates a snapshot of the current state of the repository and virtual branches using the given label.
///
/// If this is the first shapshot created, supporting structures are initialized:
/// - The current oplog head is persisted in `.git/gitbutler/oplog.toml`.
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
///
/// The snapshot tree contains:
/// - The current state of the working directory under a subtree `workdir`.
/// - The state of virtual branches from `.git/gitbutler/virtual_branches.toml` as a blob `virtual_branches.toml`.
/// - The state of conflicts from `.git/base_merge_parent` and `.git/conflicts` if present as blobs under a subtree `conflicts`
///
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
/// Returns the sha of the created snapshot commit or None if snapshots are disabled.
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<String>>;
/// Lists the snapshots that have been created for the given repository, up to the given limit.
/// An alternative way of retrieving the snapshots would be to manually the oplog head `git log <oplog_head>` available in `.git/gitbutler/oplog.toml`.
///
/// If there are no snapshots, an empty list is returned.
fn list_snapshots(&self, limit: usize) -> Result<Vec<Snapshot>>;
/// Reverts to a previous state of the working directory, virtual branches and commits.
/// The provided sha must refer to a valid snapshot commit.
/// Upon success, a new snapshot is created.
///
/// This will restore the following:
/// - The state of the working directory is checked out from the subtree `workdir` in the snapshot.
/// - The state of virtual branches is restored from the blob `virtual_branches.toml` in the snapshot.
/// - The state of conflicts (.git/base_merge_parent and .git/conflicts) is restored from the subtree `conflicts` in the snapshot (if not present, existing files are deleted).
///
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
/// Returns the sha of the created revert snapshot commit or None if snapshots are disabled.
fn restore_snapshot(&self, sha: String) -> Result<Option<String>>;
/// Returns the number of lines of code (added plus removed) since the last snapshot. Includes untracked files.
///
/// If there are no snapshots, 0 is returned.
fn lines_since_snapshot(&self) -> Result<usize>;
}
/// Lists the snapshots that have been created for the given repository, up to the given limit.
/// An alternative way of retrieving the snapshots would be to manually the oplog head `git log <oplog_head>` available in `.git/gitbutler/oplog.toml`.
///
/// If there are no snapshots, an empty list is returned.
pub fn list(project: &Project, limit: usize) -> Result<Vec<Snapshot>> {
let repo_path = project.path.as_path();
let repo = git2::Repository::init(repo_path)?;
let oplog_state = OplogHandle::new(&project.gb_dir());
let head_sha = oplog_state.get_oplog_head()?;
if head_sha.is_none() {
// there are no snapshots to return
return Ok(vec![]);
}
let head_sha = head_sha.unwrap();
let oplog_head_commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?;
let mut revwalk = repo.revwalk()?;
revwalk.push(oplog_head_commit.id())?;
let mut snapshots = Vec::new();
for commit_id in revwalk {
let commit_id = commit_id?;
let commit = repo.find_commit(commit_id)?;
if commit.parent_count() > 1 {
break;
impl Oplog for Project {
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<String>> {
if self.enable_snapshots.is_none() || self.enable_snapshots == Some(false) {
return Ok(None);
}
let details = commit
.message()
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
let repo_path = self.path.as_path();
let repo = git2::Repository::init(repo_path)?;
snapshots.push(Snapshot {
id: commit_id.to_string(),
details,
created_at: commit.time().seconds() * 1000,
});
let vb_state = VirtualBranchesHandle::new(&self.gb_dir());
let default_target_sha = vb_state.get_default_target()?.sha;
if snapshots.len() >= limit {
break;
}
}
let oplog_state = OplogHandle::new(&self.gb_dir());
let oplog_head_commit = match oplog_state.get_oplog_head()? {
Some(head_sha) => match repo.find_commit(git2::Oid::from_str(&head_sha)?) {
Ok(commit) => commit,
Err(_) => repo.find_commit(default_target_sha.into())?,
},
// This is the first snapshot - use the default target as starting point
None => repo.find_commit(default_target_sha.into())?,
};
Ok(snapshots)
}
/// Reverts to a previous state of the working directory, virtual branches and commits.
/// The provided sha must refer to a valid snapshot commit.
/// Upon success, a new snapshot is created.
///
/// This will restore the following:
/// - The state of the working directory is checked out from the subtree `workdir` in the snapshot.
/// - The state of virtual branches is restored from the blob `virtual_branches.toml` in the snapshot.
/// - The state of conflicts (.git/base_merge_parent and .git/conflicts) is restored from the subtree `conflicts` in the snapshot (if not present, existing files are deleted).
///
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
/// Returns the sha of the created revert snapshot commit or None if snapshots are disabled.
pub fn restore(project: &Project, sha: String) -> Result<Option<String>> {
let repo_path = project.path.as_path();
let repo = git2::Repository::init(repo_path)?;
let commit = repo.find_commit(git2::Oid::from_str(&sha)?)?;
// Top tree
let tree = commit.tree()?;
let vb_tree_entry = tree
.get_name("virtual_branches.toml")
.ok_or(anyhow!("failed to get virtual_branches tree entry"))?;
// virtual_branches.toml blob
let vb_blob = vb_tree_entry
.to_object(&repo)?
.into_blob()
.map_err(|_| anyhow!("failed to convert virtual_branches tree entry to blob"))?;
// Restore the state of .git/base_merge_parent and .git/conflicts from the snapshot
// Will remove those files if they are not present in the snapshot
_ = restore_conflicts_tree(&tree, &repo, repo_path);
let wd_tree_entry = tree
.get_name("workdir")
.ok_or(anyhow!("failed to get workdir tree entry"))?;
// workdir tree
let tree = repo.find_tree(wd_tree_entry.id())?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
// Define the checkout builder
let mut checkout_builder = git2::build::CheckoutBuilder::new();
checkout_builder.remove_untracked(true);
checkout_builder.force();
// Checkout the tree
repo.checkout_tree(tree.as_object(), Some(&mut checkout_builder))?;
// Update virtual_branches.toml with the state from the snapshot
fs::write(
repo_path
// Create a blob out of `.git/gitbutler/virtual_branches.toml`
let vb_path = repo_path
.join(".git")
.join("gitbutler")
.join("virtual_branches.toml"),
vb_blob.content(),
)?;
.join("virtual_branches.toml");
let vb_content = fs::read(vb_path)?;
let vb_blob = repo.blob(&vb_content)?;
// create new snapshot
let details = SnapshotDetails {
version: Default::default(),
operation: OperationType::RestoreFromSnapshot,
title: "Restored from snapshot".to_string(),
body: None,
trailers: vec![Trailer {
key: "restored_from".to_string(),
value: sha,
}],
};
create(project, details)
}
// Create a tree out of the conflicts state if present
let conflicts_tree = write_conflicts_tree(repo_path, &repo)?;
/// Returns the number of uncommitted lines of code (added plus removed) since the last snapshot. Included untracked files.
pub fn changed_lines_count(project: &Project) -> Result<usize> {
let repo_path = project.path.as_path();
let repo = git2::Repository::init(repo_path)?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
// Add everything in the workdir to the index
let mut index = repo.index()?;
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
index.write()?;
let oplog_state = OplogHandle::new(&project.gb_dir());
let head_sha = oplog_state.get_oplog_head()?;
if head_sha.is_none() {
return Ok(0);
// Create a tree out of the index
let tree_id = index.write_tree()?;
let mut tree_builder = repo.treebuilder(None)?;
tree_builder.insert("workdir", tree_id, FileMode::Tree.into())?;
tree_builder.insert("virtual_branches.toml", vb_blob, FileMode::Blob.into())?;
tree_builder.insert("conflicts", conflicts_tree, FileMode::Tree.into())?;
let tree_id = tree_builder.write()?;
let tree = repo.find_tree(tree_id)?;
// Construct a new commit
let name = "GitButler";
let email = "gitbutler@gitbutler.com";
let signature = git2::Signature::now(name, email).unwrap();
let new_commit_oid = repo.commit(
None,
&signature,
&signature,
&details.to_string(),
&tree,
&[&oplog_head_commit],
)?;
// Reset the workdir to how it was
let integration_branch = repo
.find_branch("gitbutler/integration", git2::BranchType::Local)?
.get()
.peel_to_commit()?;
repo.reset(
&integration_branch.into_object(),
git2::ResetType::Mixed,
None,
)?;
oplog_state.set_oplog_head(new_commit_oid.to_string())?;
set_reference_to_oplog(
self,
&default_target_sha.to_string(),
&new_commit_oid.to_string(),
)?;
Ok(Some(new_commit_oid.to_string()))
}
let head_sha = head_sha.unwrap();
let commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?;
let head_tree = commit.tree()?;
fn list_snapshots(&self, limit: usize) -> Result<Vec<Snapshot>> {
let repo_path = self.path.as_path();
let repo = git2::Repository::init(repo_path)?;
let wd_tree_entry = head_tree
.get_name("workdir")
.ok_or(anyhow!("failed to get workdir tree entry"))?;
let wd_tree = repo.find_tree(wd_tree_entry.id())?;
let oplog_state = OplogHandle::new(&self.gb_dir());
let head_sha = oplog_state.get_oplog_head()?;
if head_sha.is_none() {
// there are no snapshots to return
return Ok(vec![]);
}
let head_sha = head_sha.unwrap();
let mut opts = git2::DiffOptions::new();
opts.include_untracked(true);
let diff = repo.diff_tree_to_workdir_with_index(Some(&wd_tree), Some(&mut opts));
let stats = diff?.stats()?;
Ok(stats.deletions() + stats.insertions())
let oplog_head_commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?;
let mut revwalk = repo.revwalk()?;
revwalk.push(oplog_head_commit.id())?;
let mut snapshots = Vec::new();
for commit_id in revwalk {
let commit_id = commit_id?;
let commit = repo.find_commit(commit_id)?;
if commit.parent_count() > 1 {
break;
}
let details = commit
.message()
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
snapshots.push(Snapshot {
id: commit_id.to_string(),
details,
created_at: commit.time().seconds() * 1000,
});
if snapshots.len() >= limit {
break;
}
}
Ok(snapshots)
}
fn restore_snapshot(&self, sha: String) -> Result<Option<String>> {
let repo_path = self.path.as_path();
let repo = git2::Repository::init(repo_path)?;
let commit = repo.find_commit(git2::Oid::from_str(&sha)?)?;
// Top tree
let tree = commit.tree()?;
let vb_tree_entry = tree
.get_name("virtual_branches.toml")
.ok_or(anyhow!("failed to get virtual_branches tree entry"))?;
// virtual_branches.toml blob
let vb_blob = vb_tree_entry
.to_object(&repo)?
.into_blob()
.map_err(|_| anyhow!("failed to convert virtual_branches tree entry to blob"))?;
// Restore the state of .git/base_merge_parent and .git/conflicts from the snapshot
// Will remove those files if they are not present in the snapshot
_ = restore_conflicts_tree(&tree, &repo, repo_path);
let wd_tree_entry = tree
.get_name("workdir")
.ok_or(anyhow!("failed to get workdir tree entry"))?;
// workdir tree
let tree = repo.find_tree(wd_tree_entry.id())?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
// Define the checkout builder
let mut checkout_builder = git2::build::CheckoutBuilder::new();
checkout_builder.remove_untracked(true);
checkout_builder.force();
// Checkout the tree
repo.checkout_tree(tree.as_object(), Some(&mut checkout_builder))?;
// Update virtual_branches.toml with the state from the snapshot
fs::write(
repo_path
.join(".git")
.join("gitbutler")
.join("virtual_branches.toml"),
vb_blob.content(),
)?;
// create new snapshot
let details = SnapshotDetails {
version: Default::default(),
operation: OperationType::RestoreFromSnapshot,
title: "Restored from snapshot".to_string(),
body: None,
trailers: vec![Trailer {
key: "restored_from".to_string(),
value: sha,
}],
};
self.create_snapshot(details)
}
fn lines_since_snapshot(&self) -> Result<usize> {
let repo_path = self.path.as_path();
let repo = git2::Repository::init(repo_path)?;
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
let files_to_exclude = get_exclude_list(&repo)?;
// In-memory, libgit2 internal ignore rule
repo.add_ignore_rule(&files_to_exclude)?;
let oplog_state = OplogHandle::new(&self.gb_dir());
let head_sha = oplog_state.get_oplog_head()?;
if head_sha.is_none() {
return Ok(0);
}
let head_sha = head_sha.unwrap();
let commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?;
let head_tree = commit.tree()?;
let wd_tree_entry = head_tree
.get_name("workdir")
.ok_or(anyhow!("failed to get workdir tree entry"))?;
let wd_tree = repo.find_tree(wd_tree_entry.id())?;
let mut opts = git2::DiffOptions::new();
opts.include_untracked(true);
let diff = repo.diff_tree_to_workdir_with_index(Some(&wd_tree), Some(&mut opts));
let stats = diff?.stats()?;
Ok(stats.deletions() + stats.insertions())
}
}
fn restore_conflicts_tree(
@ -443,7 +456,9 @@ mod tests {
std::fs::write(&base_merge_parent_path, "parent A").unwrap();
// create a snapshot
create(&project, SnapshotDetails::new(OperationType::CreateCommit)).unwrap();
project
.create_snapshot(SnapshotDetails::new(OperationType::CreateCommit))
.unwrap();
// The large file is still here but it will not be part of the snapshot
let file_path = dir.path().join("large.txt");
@ -473,14 +488,14 @@ mod tests {
std::fs::remove_file(&conflicts_path).unwrap();
std::fs::remove_file(&base_merge_parent_path).unwrap();
// New snapshot with the conflicts removed
let conflicts_removed_snapshot = create(
&project,
SnapshotDetails::new(OperationType::UpdateWorkspaceBase),
)
.unwrap();
let conflicts_removed_snapshot = project
.create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase))
.unwrap();
// restore from the initial snapshot
restore(&project, list(&project, 10).unwrap()[1].id.clone()).unwrap();
project
.restore_snapshot(project.list_snapshots(10).unwrap()[1].id.clone())
.unwrap();
let file_path = dir.path().join("1.txt");
let file_lines = std::fs::read_to_string(file_path).unwrap();
@ -509,7 +524,9 @@ mod tests {
assert_eq!(file_lines, "parent A");
// Restore from the second snapshot
restore(&project, conflicts_removed_snapshot.unwrap()).unwrap();
project
.restore_snapshot(conflicts_removed_snapshot.unwrap())
.unwrap();
// The conflicts are not present
assert!(!&conflicts_path.exists());

View File

@ -2,7 +2,7 @@ use crate::{
error::Error,
snapshots::{
entry::{OperationType, SnapshotDetails},
snapshot,
snapshot::Oplog,
},
};
use std::{collections::HashMap, path::Path, sync::Arc};
@ -473,10 +473,9 @@ impl ControllerInner {
)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::CreateCommit));
result
})
}
@ -524,10 +523,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let branch_id = super::create_virtual_branch(project_repository, create)?.id;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::CreateBranch));
Ok(branch_id)
})
}
@ -556,10 +554,9 @@ impl ControllerInner {
signing_key.as_ref(),
user,
)?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::CreateBranch));
Ok(result)
})
}
@ -592,10 +589,9 @@ impl ControllerInner {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::open(&project)?;
let result = super::set_base_branch(&project_repository, target_branch)?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::SetBaseBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::SetBaseBranch));
Ok(result)
}
@ -625,10 +621,9 @@ impl ControllerInner {
user,
)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::MergeUpstream),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::MergeUpstream));
result
})
}
@ -650,10 +645,9 @@ impl ControllerInner {
let result = super::update_base_branch(project_repository, user, signing_key.as_ref())
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UpdateWorkspaceBase),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase));
result
})
}
@ -682,7 +676,7 @@ impl ControllerInner {
SnapshotDetails::new(OperationType::GenericBranchUpdate)
};
super::update_branch(project_repository, branch_update)?;
snapshot::create(project_repository.project(), details)?;
let _ = project_repository.project().create_snapshot(details);
Ok(())
})
}
@ -696,10 +690,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
super::delete_branch(project_repository, branch_id)?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DeleteBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::DeleteBranch));
Ok(())
})
}
@ -726,10 +719,9 @@ impl ControllerInner {
let result =
super::apply_branch(project_repository, branch_id, signing_key.as_ref(), user)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::ApplyBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::ApplyBranch));
result
})
}
@ -744,10 +736,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result =
super::unapply_ownership(project_repository, ownership).map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DiscardHunk),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::DiscardHunk));
result
})
}
@ -761,10 +752,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result = super::reset_files(project_repository, ownership).map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DiscardFile),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::DiscardFile));
result
})
}
@ -781,10 +771,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result = super::amend(project_repository, branch_id, commit_oid, ownership)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::AmendCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::AmendCommit));
result
})
}
@ -808,10 +797,9 @@ impl ControllerInner {
ownership,
)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::MoveCommitFile),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::MoveCommitFile));
result
})
}
@ -827,10 +815,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result =
super::undo_commit(project_repository, branch_id, commit_oid).map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UndoCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::UndoCommit));
result
})
}
@ -848,10 +835,9 @@ impl ControllerInner {
let result =
super::insert_blank_commit(project_repository, branch_id, commit_oid, user, offset)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::InsertBlankCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::InsertBlankCommit));
result
})
}
@ -868,10 +854,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result = super::reorder_commit(project_repository, branch_id, commit_oid, offset)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::ReorderCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::ReorderCommit));
result
})
}
@ -887,10 +872,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result = super::reset_branch(project_repository, branch_id, target_commit_oid)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UndoCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::UndoCommit));
result
})
}
@ -906,10 +890,9 @@ impl ControllerInner {
let result = super::unapply_branch(project_repository, branch_id)
.map(|_| ())
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UnapplyBranch),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::UnapplyBranch));
result
})
}
@ -949,10 +932,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result =
super::cherry_pick(project_repository, branch_id, commit_oid).map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CherryPick),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::CherryPick));
result
})
}
@ -987,10 +969,9 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result =
super::squash(project_repository, branch_id, commit_oid).map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::SquashCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::SquashCommit));
result
})
}
@ -1007,10 +988,9 @@ impl ControllerInner {
let result =
super::update_commit_message(project_repository, branch_id, commit_oid, message)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UpdateCommitMessage),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::UpdateCommitMessage));
result
})
}
@ -1089,10 +1069,9 @@ impl ControllerInner {
signing_key.as_ref(),
)
.map_err(Into::into);
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::MoveCommit),
)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationType::MoveCommit));
result
})
}

View File

@ -1,7 +1,8 @@
use crate::error::Error;
use anyhow::Context;
use gitbutler_core::{
projects, projects::ProjectId, snapshots::entry::Snapshot, snapshots::snapshot,
projects::{self, ProjectId},
snapshots::{entry::Snapshot, snapshot::Oplog},
};
use tauri::Manager;
use tracing::instrument;
@ -17,7 +18,7 @@ pub async fn list_snapshots(
.state::<projects::Controller>()
.get(&project_id)
.context("failed to get project")?;
let snapshots = snapshot::list(&project, limit)?;
let snapshots = project.list_snapshots(limit)?;
Ok(snapshots)
}
@ -32,6 +33,6 @@ pub async fn restore_snapshot(
.state::<projects::Controller>()
.get(&project_id)
.context("failed to get project")?;
snapshot::restore(&project, sha)?;
project.restore_snapshot(sha)?;
Ok(())
}

View File

@ -10,7 +10,7 @@ use anyhow::{bail, Context, Result};
use gitbutler_core::projects::ProjectId;
use gitbutler_core::sessions::SessionId;
use gitbutler_core::snapshots::entry::{OperationType, SnapshotDetails, Trailer};
use gitbutler_core::snapshots::snapshot;
use gitbutler_core::snapshots::snapshot::Oplog;
use gitbutler_core::virtual_branches::VirtualBranches;
use gitbutler_core::{
assets, deltas, gb_repository, git, project_repository, projects, reader, sessions, users,
@ -305,7 +305,7 @@ impl Handler {
.projects
.get(&project_id)
.context("failed to get project")?;
let changed_lines = snapshot::changed_lines_count(&project)?;
let changed_lines = project.lines_since_snapshot()?;
if changed_lines > project.snapshot_lines_threshold {
let details = SnapshotDetails {
version: Default::default(),
@ -317,7 +317,7 @@ impl Handler {
value: paths,
}],
};
snapshot::create(&project, details)?;
project.create_snapshot(details)?;
}
Ok(())
}