use new snapshot types

This commit is contained in:
Kiril Videlov 2024-04-26 23:20:31 +02:00
parent 371a944e71
commit 5420fc3e3c
No known key found for this signature in database
5 changed files with 149 additions and 64 deletions

View File

@ -19,7 +19,7 @@ pub struct Snapshot {
/// Snapshot creation time in epoch milliseconds
pub created_at: i64,
/// Snapshot details as persisted in the commit message
pub details: SnapshotDetails,
pub details: Option<SnapshotDetails>,
}
/// The payload of a snapshot commit
@ -40,6 +40,19 @@ pub struct SnapshotDetails {
pub trailers: Vec<Trailer>,
}
impl SnapshotDetails {
pub fn new(operation: OperationType) -> Self {
let title = operation.to_string();
SnapshotDetails {
version: Default::default(),
operation,
title,
body: None,
trailers: vec![],
}
}
}
impl FromStr for SnapshotDetails {
type Err = anyhow::Error;
@ -128,11 +141,12 @@ pub enum OperationType {
DiscardFile,
AmendCommit,
UndoCommit,
UnapplyBranchError,
UnapplyBranch,
CherryPick,
SquashCommit,
UpdateCommitMessage,
MoveCommit,
RestoreFromSnapshot,
Unknown,
}
@ -164,11 +178,12 @@ impl FromStr for OperationType {
"DiscardFile" => Ok(OperationType::DiscardFile),
"AmendCommit" => Ok(OperationType::AmendCommit),
"UndoCommit" => Ok(OperationType::UndoCommit),
"UnapplyBranchError" => Ok(OperationType::UnapplyBranchError),
"UnapplyBranch" => Ok(OperationType::UnapplyBranch),
"CherryPick" => Ok(OperationType::CherryPick),
"SquashCommit" => Ok(OperationType::SquashCommit),
"UpdateCommitMessage" => Ok(OperationType::UpdateCommitMessage),
"MoveCommit" => Ok(OperationType::MoveCommit),
"RestoreFromSnapshot" => Ok(OperationType::RestoreFromSnapshot),
_ => Ok(OperationType::Unknown),
}
}
@ -176,6 +191,11 @@ impl FromStr for OperationType {
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Version(u32);
impl Default for Version {
fn default() -> Self {
Version(1)
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -292,24 +312,25 @@ mod tests {
let snapshot = Snapshot {
id: commit_sha.clone(),
created_at,
details,
details: Some(details),
};
assert_eq!(snapshot.id, commit_sha);
assert_eq!(snapshot.details.version.0, 1);
assert_eq!(snapshot.details.operation, OperationType::CreateCommit);
assert_eq!(snapshot.details.title, "Create a new snapshot");
assert_eq!(snapshot.created_at, created_at);
let details = snapshot.details.unwrap();
assert_eq!(details.version.0, 1);
assert_eq!(details.operation, OperationType::CreateCommit);
assert_eq!(details.title, "Create a new snapshot");
assert_eq!(
snapshot.details.body,
details.body,
Some("Body text 1\nBody text2\n\nBody text 3".to_string())
);
assert_eq!(
snapshot.details.trailers,
details.trailers,
vec![Trailer {
key: "Foo".to_string(),
value: "Bar".to_string(),
}]
);
assert_eq!(snapshot.created_at, created_at);
assert_eq!(snapshot.details.to_string(), commit_message);
assert_eq!(details.to_string(), commit_message);
}
}

View File

@ -1,4 +1,4 @@
mod entry;
pub mod entry;
mod reflog;
pub mod snapshot;
mod state;

View File

@ -1,22 +1,14 @@
use std::str::FromStr;
use anyhow::Result;
use serde::Serialize;
use crate::{projects::Project, virtual_branches::VirtualBranchesHandle};
use super::{reflog::set_reference_to_oplog, state::OplogHandle};
/// A snapshot of the repository and virtual branches state that GitButler can restore to.
/// It captures the state of the working directory, virtual branches and commits.
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SnapshotEntry {
/// The sha of the commit that represents the snapshot.
pub sha: String,
/// Textual description of the snapshot.
pub label: String,
/// The time the snapshot was created at in milliseconds since epoch.
pub created_at: i64,
}
use super::{
entry::{OperationType, Snapshot, SnapshotDetails, Trailer},
reflog::set_reference_to_oplog,
state::OplogHandle,
};
/// Creates a snapshot of the current state of the repository and virtual branches using the given label.
///
@ -25,7 +17,7 @@ pub struct SnapshotEntry {
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
///
/// The state of virtual branches `.git/gitbutler/virtual_branches.toml` is copied to the project root so that it is snapshotted.
pub fn create(project: &Project, label: &str) -> Result<()> {
pub fn create(project: &Project, details: SnapshotDetails) -> Result<()> {
if project.enable_snapshots.is_none() || project.enable_snapshots == Some(false) {
return Ok(());
}
@ -67,7 +59,7 @@ pub fn create(project: &Project, label: &str) -> Result<()> {
None,
&signature,
&signature,
label,
&details.to_string(),
&tree,
&[&oplog_head_commit],
)?;
@ -102,7 +94,7 @@ pub fn create(project: &Project, label: &str) -> Result<()> {
/// 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<SnapshotEntry>> {
pub fn list(project: Project, limit: usize) -> Result<Vec<Snapshot>> {
let repo_path = project.path.as_path();
let repo = git2::Repository::init(repo_path)?;
@ -128,9 +120,14 @@ pub fn list(project: Project, limit: usize) -> Result<Vec<SnapshotEntry>> {
if commit.parent_count() > 1 {
break;
}
snapshots.push(SnapshotEntry {
sha: commit_id.to_string(),
label: commit.summary().unwrap_or_default().to_string(),
let details = commit
.summary()
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
snapshots.push(Snapshot {
id: commit_id.to_string(),
details,
created_at: commit.time().seconds() * 1000,
});
@ -167,8 +164,17 @@ pub fn restore(project: &Project, sha: String) -> Result<()> {
)?;
// create new snapshot
let label = format!("Restored from {}", &sha);
create(project, &label)?;
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)?;
Ok(())
}

View File

@ -1,4 +1,10 @@
use crate::{error::Error, snapshots::snapshot};
use crate::{
error::Error,
snapshots::{
entry::{OperationType, SnapshotDetails},
snapshot,
},
};
use std::{collections::HashMap, path::Path, sync::Arc};
use anyhow::Context;
@ -407,7 +413,11 @@ impl ControllerInner {
run_hooks,
)
.map_err(Into::into);
snapshot::create(project_repository.project(), "create commit")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateCommit),
)?;
result
})
}
@ -455,7 +465,10 @@ 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(), "create branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateBranch),
)?;
Ok(branch_id)
})
}
@ -484,7 +497,10 @@ impl ControllerInner {
signing_key.as_ref(),
user,
)?;
snapshot::create(project_repository.project(), "create branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CreateBranch),
)?;
Ok(result)
})
}
@ -517,7 +533,10 @@ 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(), "set base branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::SetBaseBranch),
)?;
Ok(result)
}
@ -547,7 +566,10 @@ impl ControllerInner {
user,
)
.map_err(Into::into);
snapshot::create(project_repository.project(), "merge upstream")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::MergeUpstream),
)?;
result
})
}
@ -569,7 +591,10 @@ impl ControllerInner {
let result = super::update_base_branch(project_repository, user, signing_key.as_ref())
.map_err(Into::into);
snapshot::create(project_repository.project(), "update workspace base")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UpdateWorkspaceBase),
)?;
result
})
}
@ -582,23 +607,23 @@ impl ControllerInner {
let _permit = self.semaphore.acquire().await;
self.with_verify_branch(project_id, |project_repository, _| {
let label = if branch_update.ownership.is_some() {
"move hunk"
let details = if branch_update.ownership.is_some() {
SnapshotDetails::new(OperationType::MoveHunk)
} else if branch_update.name.is_some() {
"update branch name"
SnapshotDetails::new(OperationType::UpdateBranchName)
} else if branch_update.notes.is_some() {
"update branch notes"
SnapshotDetails::new(OperationType::UpdateBranchNotes)
} else if branch_update.order.is_some() {
"reorder branches"
SnapshotDetails::new(OperationType::ReorderBranches)
} else if branch_update.selected_for_changes.is_some() {
"select default branch"
SnapshotDetails::new(OperationType::SelectDefaultVirtualBranch)
} else if branch_update.upstream.is_some() {
"update remote branch name"
SnapshotDetails::new(OperationType::UpdateBranchRemoteName)
} else {
"update branch"
SnapshotDetails::new(OperationType::GenericBranchUpdate)
};
super::update_branch(project_repository, branch_update)?;
snapshot::create(project_repository.project(), label)?;
snapshot::create(project_repository.project(), details)?;
Ok(())
})
}
@ -612,7 +637,10 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
super::delete_branch(project_repository, branch_id)?;
snapshot::create(project_repository.project(), "delete branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DeleteBranch),
)?;
Ok(())
})
}
@ -639,7 +667,10 @@ 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(), "apply branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::ApplyBranch),
)?;
result
})
}
@ -654,7 +685,10 @@ 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(), "discard hunk")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DiscardHunk),
)?;
result
})
}
@ -668,7 +702,10 @@ 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(), "discard file")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::DiscardFile),
)?;
result
})
}
@ -683,7 +720,10 @@ impl ControllerInner {
self.with_verify_branch(project_id, |project_repository, _| {
let result = super::amend(project_repository, branch_id, ownership).map_err(Into::into);
snapshot::create(project_repository.project(), "amend commit")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::AmendCommit),
)?;
result
})
}
@ -699,7 +739,10 @@ 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(), "undo commit")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UndoCommit),
)?;
result
})
}
@ -715,7 +758,10 @@ impl ControllerInner {
let result = super::unapply_branch(project_repository, branch_id)
.map(|_| ())
.map_err(Into::into);
snapshot::create(project_repository.project(), "unapply branch")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UnapplyBranch),
)?;
result
})
}
@ -755,7 +801,10 @@ 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(), "cherry pick")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::CherryPick),
)?;
result
})
}
@ -790,7 +839,10 @@ 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(), "squash commit")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::SquashCommit),
)?;
result
})
}
@ -807,7 +859,10 @@ impl ControllerInner {
let result =
super::update_commit_message(project_repository, branch_id, commit_oid, message)
.map_err(Into::into);
snapshot::create(project_repository.project(), "update commit message")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::UpdateCommitMessage),
)?;
result
})
}
@ -886,7 +941,10 @@ impl ControllerInner {
signing_key.as_ref(),
)
.map_err(Into::into);
snapshot::create(project_repository.project(), "moved commit")?;
snapshot::create(
project_repository.project(),
SnapshotDetails::new(OperationType::MoveCommit),
)?;
result
})
}

View File

@ -1,7 +1,7 @@
use crate::error::Error;
use anyhow::Context;
use gitbutler_core::{
projects, projects::ProjectId, snapshots::snapshot, snapshots::snapshot::SnapshotEntry,
projects, projects::ProjectId, snapshots::entry::Snapshot, snapshots::snapshot,
};
use tauri::Manager;
use tracing::instrument;
@ -12,7 +12,7 @@ pub async fn list_snapshots(
handle: tauri::AppHandle,
project_id: ProjectId,
limit: usize,
) -> Result<Vec<SnapshotEntry>, Error> {
) -> Result<Vec<Snapshot>, Error> {
let project = handle
.state::<projects::Controller>()
.get(&project_id)