Merge pull request #3704 from gitbutlerapp/add-workdir-snapshot-details

add-workdir-snapshot-details
This commit is contained in:
Kiril Videlov 2024-05-06 16:08:08 +02:00 committed by GitHub
commit a6e80d9d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 87 additions and 27 deletions

View File

@ -27,6 +27,9 @@
};
type Snapshot = {
id: string;
linesAdded: number;
linesRemoved: number;
filesChanged: string[];
details: SnapshotDetails | undefined;
createdAt: number;
};
@ -79,17 +82,23 @@
</div>
<div style="padding-left: 16px; hidden;">
{#if entry.details?.operation === 'RestoreFromSnapshot'}
from: {entry.details?.trailers
restored_from: {entry.details?.trailers
.find((t) => t.key === 'restored_from')
?.value?.slice(0, 7)}
{:else if entry.details?.operation === 'FileChanges'}
{#each entry.details?.trailers
.find((t) => t.key === 'files')
?.value?.split(',') || [] as file}
<div>{file}</div>
{/each}
{/if}
</div>
<div>
lines added: {entry.linesAdded}
</div>
<div>
lines removed: {entry.linesRemoved}
</div>
<div>
<div>files changed:</div>
{#each entry.filesChanged as filePath}
<div style="padding-left: 16px;">{filePath}</div>
{/each}
</div>
</div>
</div>
{/each}

View File

@ -5,6 +5,7 @@ use serde::Deserialize;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::PathBuf;
use std::str::FromStr;
use strum::EnumString;
@ -19,6 +20,12 @@ pub struct Snapshot {
pub id: String,
/// Snapshot creation time in epoch milliseconds
pub created_at: i64,
/// The number of working directory lines added in the snapshot
pub lines_added: usize,
/// The number of working directory lines removed in the snapshot
pub lines_removed: usize,
/// The list of working directory files that were changed in the snapshot
pub files_changed: Vec<PathBuf>,
/// Snapshot details as persisted in the commit message
pub details: Option<SnapshotDetails>,
}
@ -298,6 +305,9 @@ mod tests {
let snapshot = Snapshot {
id: commit_sha.clone(),
created_at,
lines_added: 1,
lines_removed: 1,
files_changed: vec![PathBuf::from("foo.txt")],
details: Some(details),
};
assert_eq!(snapshot.id, commit_sha);

View File

@ -173,6 +173,35 @@ impl Oplog for Project {
break;
}
let tree = commit.tree()?;
let wd_tree_entry = tree.get_name("workdir");
let tree = if let Some(wd_tree_entry) = wd_tree_entry {
repo.find_tree(wd_tree_entry.id())?
} else {
// We reached a tree that is not a snapshot
continue;
};
let parent_tree = commit.parent(0)?.tree()?;
let parent_tree_entry = parent_tree.get_name("workdir");
let parent_tree = parent_tree_entry
.map(|entry| repo.find_tree(entry.id()))
.transpose()?;
let diff = repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)?;
let stats = diff.stats()?;
let mut files_changed = Vec::new();
diff.print(git2::DiffFormat::NameOnly, |delta, _, _| {
if let Some(path) = delta.new_file().path() {
files_changed.push(path.to_path_buf());
}
true
})?;
let lines_added = stats.insertions();
let lines_removed = stats.deletions();
let details = commit
.message()
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
@ -180,6 +209,9 @@ impl Oplog for Project {
snapshots.push(Snapshot {
id: commit_id.to_string(),
details,
lines_added,
lines_removed,
files_changed,
created_at: commit.time().seconds() * 1000,
});
@ -492,9 +524,33 @@ mod tests {
.create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase))
.unwrap();
let initial_snapshot = &project.list_snapshots(10).unwrap()[1];
assert_eq!(
initial_snapshot.files_changed,
vec![
PathBuf::from_str("1.txt").unwrap(),
PathBuf::from_str("2.txt").unwrap(),
PathBuf::from_str("uncommitted.txt").unwrap()
]
);
assert_eq!(initial_snapshot.lines_added, 3);
assert_eq!(initial_snapshot.lines_removed, 0);
let second_snapshot = &project.list_snapshots(10).unwrap()[0];
assert_eq!(
second_snapshot.files_changed,
vec![
PathBuf::from_str("1.txt").unwrap(),
PathBuf::from_str("2.txt").unwrap(),
PathBuf::from_str("3.txt").unwrap(),
PathBuf::from_str("uncommitted.txt").unwrap()
]
);
assert_eq!(second_snapshot.lines_added, 3);
assert_eq!(second_snapshot.lines_removed, 3);
// restore from the initial snapshot
project
.restore_snapshot(project.list_snapshots(10).unwrap()[1].id.clone())
.restore_snapshot(initial_snapshot.id.clone())
.unwrap();
let file_path = dir.path().join("1.txt");

View File

@ -9,7 +9,7 @@ use std::{path, time};
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::entry::{OperationType, SnapshotDetails};
use gitbutler_core::snapshots::snapshot::Oplog;
use gitbutler_core::virtual_branches::VirtualBranches;
use gitbutler_core::{
@ -280,11 +280,6 @@ impl Handler {
paths: Vec<PathBuf>,
project_id: ProjectId,
) -> 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 this = self.clone();
move || this.calculate_deltas(paths, project_id)
@ -292,7 +287,7 @@ impl Handler {
// Create a snapshot every time there are more than a configurable number of new lines of code (default 20)
let handle_snapshots = tokio::task::spawn_blocking({
let this = self.clone();
move || this.maybe_create_snapshot(project_id, paths_string)
move || this.maybe_create_snapshot(project_id)
});
self.calculate_virtual_branches(project_id).await?;
let _ = handle_snapshots.await;
@ -300,24 +295,14 @@ impl Handler {
Ok(())
}
fn maybe_create_snapshot(&self, project_id: ProjectId, paths: String) -> anyhow::Result<()> {
fn maybe_create_snapshot(&self, project_id: ProjectId) -> anyhow::Result<()> {
let project = self
.projects
.get(&project_id)
.context("failed to get project")?;
let changed_lines = project.lines_since_snapshot()?;
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,
}],
};
project.create_snapshot(details)?;
project.create_snapshot(SnapshotDetails::new(OperationType::FileChanges))?;
}
Ok(())
}