mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 14:31:30 +03:00
Merge pull request #3694 from gitbutlerapp/refactor-snapshot-functions-as-trait-methods
refactor snapshotting as an Oplog trait
This commit is contained in:
commit
f2b2434d7c
@ -1,5 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gitbutler_core::{projects::Project, snapshots::snapshot};
|
use gitbutler_core::{projects::Project, snapshots::snapshot::Oplog};
|
||||||
|
|
||||||
use clap::{arg, Command};
|
use clap::{arg, Command};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
@ -49,7 +49,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
fn list_snapshots(repo_dir: &str) -> Result<()> {
|
fn list_snapshots(repo_dir: &str) -> Result<()> {
|
||||||
let project = project_from_path(repo_dir);
|
let project = project_from_path(repo_dir);
|
||||||
let snapshots = snapshot::list(&project, 100)?;
|
let snapshots = project.list_snapshots(100)?;
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
let ts = chrono::DateTime::from_timestamp(snapshot.created_at / 1000, 0);
|
let ts = chrono::DateTime::from_timestamp(snapshot.created_at / 1000, 0);
|
||||||
let details = snapshot.details;
|
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<()> {
|
fn restore_snapshot(repo_dir: &str, snapshot_id: &str) -> Result<()> {
|
||||||
let project = project_from_path(repo_dir);
|
let project = project_from_path(repo_dir);
|
||||||
snapshot::restore(&project, snapshot_id.to_owned())?;
|
project.restore_snapshot(snapshot_id.to_owned())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,257 +16,270 @@ use super::{
|
|||||||
|
|
||||||
const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024;
|
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.
|
/// 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).
|
||||||
/// If this is the first shapshot created, supporting structures are initialized:
|
pub trait Oplog {
|
||||||
/// - The current oplog head is persisted in `.git/gitbutler/oplog.toml`.
|
/// Creates a snapshot of the current state of the repository and virtual branches using the given label.
|
||||||
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
|
///
|
||||||
///
|
/// If this is the first shapshot created, supporting structures are initialized:
|
||||||
/// The snapshot tree contains:
|
/// - The current oplog head is persisted in `.git/gitbutler/oplog.toml`.
|
||||||
/// - The current state of the working directory under a subtree `workdir`.
|
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
|
||||||
/// - 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`
|
/// The snapshot tree contains:
|
||||||
///
|
/// - The current state of the working directory under a subtree `workdir`.
|
||||||
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
|
/// - The state of virtual branches from `.git/gitbutler/virtual_branches.toml` as a blob `virtual_branches.toml`.
|
||||||
/// Returns the sha of the created snapshot commit or None if snapshots are disabled.
|
/// - The state of conflicts from `.git/base_merge_parent` and `.git/conflicts` if present as blobs under a subtree `conflicts`
|
||||||
pub fn create(project: &Project, details: SnapshotDetails) -> Result<Option<String>> {
|
///
|
||||||
if project.enable_snapshots.is_none() || project.enable_snapshots == Some(false) {
|
/// If there are files that are untracked and larger than SNAPSHOT_FILE_LIMIT_BYTES, they are excluded from snapshot creation and restoring.
|
||||||
return Ok(None);
|
/// 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.
|
||||||
let repo_path = project.path.as_path();
|
/// An alternative way of retrieving the snapshots would be to manually the oplog head `git log <oplog_head>` available in `.git/gitbutler/oplog.toml`.
|
||||||
let repo = git2::Repository::init(repo_path)?;
|
///
|
||||||
|
/// If there are no snapshots, an empty list is returned.
|
||||||
let vb_state = VirtualBranchesHandle::new(&project.gb_dir());
|
fn list_snapshots(&self, limit: usize) -> Result<Vec<Snapshot>>;
|
||||||
let default_target_sha = vb_state.get_default_target()?.sha;
|
/// Reverts to a previous state of the working directory, virtual branches and commits.
|
||||||
|
/// The provided sha must refer to a valid snapshot commit.
|
||||||
let oplog_state = OplogHandle::new(&project.gb_dir());
|
/// Upon success, a new snapshot is created.
|
||||||
let oplog_head_commit = match oplog_state.get_oplog_head()? {
|
///
|
||||||
Some(head_sha) => match repo.find_commit(git2::Oid::from_str(&head_sha)?) {
|
/// This will restore the following:
|
||||||
Ok(commit) => commit,
|
/// - The state of the working directory is checked out from the subtree `workdir` in the snapshot.
|
||||||
Err(_) => repo.find_commit(default_target_sha.into())?,
|
/// - 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).
|
||||||
// This is the first snapshot - use the default target as starting point
|
///
|
||||||
None => repo.find_commit(default_target_sha.into())?,
|
/// 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>>;
|
||||||
// Create a blob out of `.git/gitbutler/virtual_branches.toml`
|
/// Returns the number of lines of code (added plus removed) since the last snapshot. Includes untracked files.
|
||||||
let vb_path = repo_path
|
///
|
||||||
.join(".git")
|
/// If there are no snapshots, 0 is returned.
|
||||||
.join("gitbutler")
|
fn lines_since_snapshot(&self) -> Result<usize>;
|
||||||
.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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lists the snapshots that have been created for the given repository, up to the given limit.
|
impl Oplog for Project {
|
||||||
/// An alternative way of retrieving the snapshots would be to manually the oplog head `git log <oplog_head>` available in `.git/gitbutler/oplog.toml`.
|
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<String>> {
|
||||||
///
|
if self.enable_snapshots.is_none() || self.enable_snapshots == Some(false) {
|
||||||
/// If there are no snapshots, an empty list is returned.
|
return Ok(None);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let details = commit
|
let repo_path = self.path.as_path();
|
||||||
.message()
|
let repo = git2::Repository::init(repo_path)?;
|
||||||
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
|
|
||||||
|
|
||||||
snapshots.push(Snapshot {
|
let vb_state = VirtualBranchesHandle::new(&self.gb_dir());
|
||||||
id: commit_id.to_string(),
|
let default_target_sha = vb_state.get_default_target()?.sha;
|
||||||
details,
|
|
||||||
created_at: commit.time().seconds() * 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if snapshots.len() >= limit {
|
let oplog_state = OplogHandle::new(&self.gb_dir());
|
||||||
break;
|
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)
|
// Create a blob out of `.git/gitbutler/virtual_branches.toml`
|
||||||
}
|
let vb_path = repo_path
|
||||||
|
|
||||||
/// 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
|
|
||||||
.join(".git")
|
.join(".git")
|
||||||
.join("gitbutler")
|
.join("gitbutler")
|
||||||
.join("virtual_branches.toml"),
|
.join("virtual_branches.toml");
|
||||||
vb_blob.content(),
|
let vb_content = fs::read(vb_path)?;
|
||||||
)?;
|
let vb_blob = repo.blob(&vb_content)?;
|
||||||
|
|
||||||
// create new snapshot
|
// Create a tree out of the conflicts state if present
|
||||||
let details = SnapshotDetails {
|
let conflicts_tree = write_conflicts_tree(repo_path, &repo)?;
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of uncommitted lines of code (added plus removed) since the last snapshot. Included untracked files.
|
// Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed)
|
||||||
pub fn changed_lines_count(project: &Project) -> Result<usize> {
|
let files_to_exclude = get_exclude_list(&repo)?;
|
||||||
let repo_path = project.path.as_path();
|
// In-memory, libgit2 internal ignore rule
|
||||||
let repo = git2::Repository::init(repo_path)?;
|
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)
|
// Add everything in the workdir to the index
|
||||||
let files_to_exclude = get_exclude_list(&repo)?;
|
let mut index = repo.index()?;
|
||||||
// In-memory, libgit2 internal ignore rule
|
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
|
||||||
repo.add_ignore_rule(&files_to_exclude)?;
|
index.write()?;
|
||||||
|
|
||||||
let oplog_state = OplogHandle::new(&project.gb_dir());
|
// Create a tree out of the index
|
||||||
let head_sha = oplog_state.get_oplog_head()?;
|
let tree_id = index.write_tree()?;
|
||||||
if head_sha.is_none() {
|
|
||||||
return Ok(0);
|
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)?)?;
|
fn list_snapshots(&self, limit: usize) -> Result<Vec<Snapshot>> {
|
||||||
let head_tree = commit.tree()?;
|
let repo_path = self.path.as_path();
|
||||||
|
let repo = git2::Repository::init(repo_path)?;
|
||||||
|
|
||||||
let wd_tree_entry = head_tree
|
let oplog_state = OplogHandle::new(&self.gb_dir());
|
||||||
.get_name("workdir")
|
let head_sha = oplog_state.get_oplog_head()?;
|
||||||
.ok_or(anyhow!("failed to get workdir tree entry"))?;
|
if head_sha.is_none() {
|
||||||
let wd_tree = repo.find_tree(wd_tree_entry.id())?;
|
// there are no snapshots to return
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
let head_sha = head_sha.unwrap();
|
||||||
|
|
||||||
let mut opts = git2::DiffOptions::new();
|
let oplog_head_commit = repo.find_commit(git2::Oid::from_str(&head_sha)?)?;
|
||||||
opts.include_untracked(true);
|
|
||||||
let diff = repo.diff_tree_to_workdir_with_index(Some(&wd_tree), Some(&mut opts));
|
let mut revwalk = repo.revwalk()?;
|
||||||
let stats = diff?.stats()?;
|
revwalk.push(oplog_head_commit.id())?;
|
||||||
Ok(stats.deletions() + stats.insertions())
|
|
||||||
|
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(
|
fn restore_conflicts_tree(
|
||||||
@ -443,7 +456,9 @@ mod tests {
|
|||||||
std::fs::write(&base_merge_parent_path, "parent A").unwrap();
|
std::fs::write(&base_merge_parent_path, "parent A").unwrap();
|
||||||
|
|
||||||
// create a snapshot
|
// 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
|
// The large file is still here but it will not be part of the snapshot
|
||||||
let file_path = dir.path().join("large.txt");
|
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(&conflicts_path).unwrap();
|
||||||
std::fs::remove_file(&base_merge_parent_path).unwrap();
|
std::fs::remove_file(&base_merge_parent_path).unwrap();
|
||||||
// New snapshot with the conflicts removed
|
// New snapshot with the conflicts removed
|
||||||
let conflicts_removed_snapshot = create(
|
let conflicts_removed_snapshot = project
|
||||||
&project,
|
.create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase))
|
||||||
SnapshotDetails::new(OperationType::UpdateWorkspaceBase),
|
.unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// restore from the initial snapshot
|
// 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_path = dir.path().join("1.txt");
|
||||||
let file_lines = std::fs::read_to_string(file_path).unwrap();
|
let file_lines = std::fs::read_to_string(file_path).unwrap();
|
||||||
@ -509,7 +524,9 @@ mod tests {
|
|||||||
assert_eq!(file_lines, "parent A");
|
assert_eq!(file_lines, "parent A");
|
||||||
|
|
||||||
// Restore from the second snapshot
|
// 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
|
// The conflicts are not present
|
||||||
assert!(!&conflicts_path.exists());
|
assert!(!&conflicts_path.exists());
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
snapshots::{
|
snapshots::{
|
||||||
entry::{OperationType, SnapshotDetails},
|
entry::{OperationType, SnapshotDetails},
|
||||||
snapshot,
|
snapshot::Oplog,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||||
@ -473,10 +473,9 @@ impl ControllerInner {
|
|||||||
)
|
)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
|
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::CreateCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::CreateCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -524,10 +523,9 @@ impl ControllerInner {
|
|||||||
|
|
||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let branch_id = super::create_virtual_branch(project_repository, create)?.id;
|
let branch_id = super::create_virtual_branch(project_repository, create)?.id;
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::CreateBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::CreateBranch));
|
||||||
)?;
|
|
||||||
Ok(branch_id)
|
Ok(branch_id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -556,10 +554,9 @@ impl ControllerInner {
|
|||||||
signing_key.as_ref(),
|
signing_key.as_ref(),
|
||||||
user,
|
user,
|
||||||
)?;
|
)?;
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::CreateBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::CreateBranch));
|
||||||
)?;
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -592,10 +589,9 @@ impl ControllerInner {
|
|||||||
let project = self.projects.get(project_id)?;
|
let project = self.projects.get(project_id)?;
|
||||||
let project_repository = project_repository::Repository::open(&project)?;
|
let project_repository = project_repository::Repository::open(&project)?;
|
||||||
let result = super::set_base_branch(&project_repository, target_branch)?;
|
let result = super::set_base_branch(&project_repository, target_branch)?;
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::SetBaseBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::SetBaseBranch));
|
||||||
)?;
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,10 +621,9 @@ impl ControllerInner {
|
|||||||
user,
|
user,
|
||||||
)
|
)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::MergeUpstream),
|
.create_snapshot(SnapshotDetails::new(OperationType::MergeUpstream));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -650,10 +645,9 @@ impl ControllerInner {
|
|||||||
|
|
||||||
let result = super::update_base_branch(project_repository, user, signing_key.as_ref())
|
let result = super::update_base_branch(project_repository, user, signing_key.as_ref())
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::UpdateWorkspaceBase),
|
.create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -682,7 +676,7 @@ impl ControllerInner {
|
|||||||
SnapshotDetails::new(OperationType::GenericBranchUpdate)
|
SnapshotDetails::new(OperationType::GenericBranchUpdate)
|
||||||
};
|
};
|
||||||
super::update_branch(project_repository, branch_update)?;
|
super::update_branch(project_repository, branch_update)?;
|
||||||
snapshot::create(project_repository.project(), details)?;
|
let _ = project_repository.project().create_snapshot(details);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -696,10 +690,9 @@ impl ControllerInner {
|
|||||||
|
|
||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
super::delete_branch(project_repository, branch_id)?;
|
super::delete_branch(project_repository, branch_id)?;
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::DeleteBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::DeleteBranch));
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -726,10 +719,9 @@ impl ControllerInner {
|
|||||||
let result =
|
let result =
|
||||||
super::apply_branch(project_repository, branch_id, signing_key.as_ref(), user)
|
super::apply_branch(project_repository, branch_id, signing_key.as_ref(), user)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::ApplyBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::ApplyBranch));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -744,10 +736,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result =
|
let result =
|
||||||
super::unapply_ownership(project_repository, ownership).map_err(Into::into);
|
super::unapply_ownership(project_repository, ownership).map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::DiscardHunk),
|
.create_snapshot(SnapshotDetails::new(OperationType::DiscardHunk));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -761,10 +752,9 @@ impl ControllerInner {
|
|||||||
|
|
||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result = super::reset_files(project_repository, ownership).map_err(Into::into);
|
let result = super::reset_files(project_repository, ownership).map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::DiscardFile),
|
.create_snapshot(SnapshotDetails::new(OperationType::DiscardFile));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -781,10 +771,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result = super::amend(project_repository, branch_id, commit_oid, ownership)
|
let result = super::amend(project_repository, branch_id, commit_oid, ownership)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::AmendCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::AmendCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -808,10 +797,9 @@ impl ControllerInner {
|
|||||||
ownership,
|
ownership,
|
||||||
)
|
)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::MoveCommitFile),
|
.create_snapshot(SnapshotDetails::new(OperationType::MoveCommitFile));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -827,10 +815,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result =
|
let result =
|
||||||
super::undo_commit(project_repository, branch_id, commit_oid).map_err(Into::into);
|
super::undo_commit(project_repository, branch_id, commit_oid).map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::UndoCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::UndoCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -848,10 +835,9 @@ impl ControllerInner {
|
|||||||
let result =
|
let result =
|
||||||
super::insert_blank_commit(project_repository, branch_id, commit_oid, user, offset)
|
super::insert_blank_commit(project_repository, branch_id, commit_oid, user, offset)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::InsertBlankCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::InsertBlankCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -868,10 +854,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result = super::reorder_commit(project_repository, branch_id, commit_oid, offset)
|
let result = super::reorder_commit(project_repository, branch_id, commit_oid, offset)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::ReorderCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::ReorderCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -887,10 +872,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result = super::reset_branch(project_repository, branch_id, target_commit_oid)
|
let result = super::reset_branch(project_repository, branch_id, target_commit_oid)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::UndoCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::UndoCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -906,10 +890,9 @@ impl ControllerInner {
|
|||||||
let result = super::unapply_branch(project_repository, branch_id)
|
let result = super::unapply_branch(project_repository, branch_id)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::UnapplyBranch),
|
.create_snapshot(SnapshotDetails::new(OperationType::UnapplyBranch));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -949,10 +932,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result =
|
let result =
|
||||||
super::cherry_pick(project_repository, branch_id, commit_oid).map_err(Into::into);
|
super::cherry_pick(project_repository, branch_id, commit_oid).map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::CherryPick),
|
.create_snapshot(SnapshotDetails::new(OperationType::CherryPick));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -987,10 +969,9 @@ impl ControllerInner {
|
|||||||
self.with_verify_branch(project_id, |project_repository, _| {
|
self.with_verify_branch(project_id, |project_repository, _| {
|
||||||
let result =
|
let result =
|
||||||
super::squash(project_repository, branch_id, commit_oid).map_err(Into::into);
|
super::squash(project_repository, branch_id, commit_oid).map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::SquashCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::SquashCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1007,10 +988,9 @@ impl ControllerInner {
|
|||||||
let result =
|
let result =
|
||||||
super::update_commit_message(project_repository, branch_id, commit_oid, message)
|
super::update_commit_message(project_repository, branch_id, commit_oid, message)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::UpdateCommitMessage),
|
.create_snapshot(SnapshotDetails::new(OperationType::UpdateCommitMessage));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1089,10 +1069,9 @@ impl ControllerInner {
|
|||||||
signing_key.as_ref(),
|
signing_key.as_ref(),
|
||||||
)
|
)
|
||||||
.map_err(Into::into);
|
.map_err(Into::into);
|
||||||
snapshot::create(
|
let _ = project_repository
|
||||||
project_repository.project(),
|
.project()
|
||||||
SnapshotDetails::new(OperationType::MoveCommit),
|
.create_snapshot(SnapshotDetails::new(OperationType::MoveCommit));
|
||||||
)?;
|
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gitbutler_core::{
|
use gitbutler_core::{
|
||||||
projects, projects::ProjectId, snapshots::entry::Snapshot, snapshots::snapshot,
|
projects::{self, ProjectId},
|
||||||
|
snapshots::{entry::Snapshot, snapshot::Oplog},
|
||||||
};
|
};
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@ -17,7 +18,7 @@ pub async fn list_snapshots(
|
|||||||
.state::<projects::Controller>()
|
.state::<projects::Controller>()
|
||||||
.get(&project_id)
|
.get(&project_id)
|
||||||
.context("failed to get project")?;
|
.context("failed to get project")?;
|
||||||
let snapshots = snapshot::list(&project, limit)?;
|
let snapshots = project.list_snapshots(limit)?;
|
||||||
Ok(snapshots)
|
Ok(snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +33,6 @@ pub async fn restore_snapshot(
|
|||||||
.state::<projects::Controller>()
|
.state::<projects::Controller>()
|
||||||
.get(&project_id)
|
.get(&project_id)
|
||||||
.context("failed to get project")?;
|
.context("failed to get project")?;
|
||||||
snapshot::restore(&project, sha)?;
|
project.restore_snapshot(sha)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use anyhow::{bail, Context, Result};
|
|||||||
use gitbutler_core::projects::ProjectId;
|
use gitbutler_core::projects::ProjectId;
|
||||||
use gitbutler_core::sessions::SessionId;
|
use gitbutler_core::sessions::SessionId;
|
||||||
use gitbutler_core::snapshots::entry::{OperationType, SnapshotDetails, Trailer};
|
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::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,
|
||||||
@ -305,7 +305,7 @@ impl Handler {
|
|||||||
.projects
|
.projects
|
||||||
.get(&project_id)
|
.get(&project_id)
|
||||||
.context("failed to get project")?;
|
.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 {
|
if changed_lines > project.snapshot_lines_threshold {
|
||||||
let details = SnapshotDetails {
|
let details = SnapshotDetails {
|
||||||
version: Default::default(),
|
version: Default::default(),
|
||||||
@ -317,7 +317,7 @@ impl Handler {
|
|||||||
value: paths,
|
value: paths,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
snapshot::create(&project, details)?;
|
project.create_snapshot(details)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user