mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 14:31:30 +03:00
Merge pull request #3787 from gitbutlerapp/save-and-restore-the-gitbutler/integration-branch
save and restore the gitbutler/integration branch
This commit is contained in:
commit
070a88beb9
@ -24,7 +24,7 @@ pub trait Oplog {
|
|||||||
/// Creates a snapshot of the current state of the repository and virtual branches using the given label.
|
/// 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:
|
/// If this is the first shapshot created, supporting structures are initialized:
|
||||||
/// - The current oplog head is persisted in `.git/gitbutler/oplog.toml`.
|
/// - The current oplog head is persisted in `.git/gitbutler/operations-log.toml`.
|
||||||
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
|
/// - A fake branch `gitbutler/target` is created and maintained in order to keep the oplog head reachable.
|
||||||
///
|
///
|
||||||
/// The snapshot tree contains:
|
/// The snapshot tree contains:
|
||||||
@ -36,7 +36,7 @@ pub trait Oplog {
|
|||||||
/// Returns the sha of the created snapshot commit or None if snapshots are disabled.
|
/// Returns the sha of the created snapshot commit or None if snapshots are disabled.
|
||||||
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<String>>;
|
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.
|
/// 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`.
|
/// An alternative way of retrieving the snapshots would be to manually the oplog head `git log <oplog_head>` available in `.git/gitbutler/operations-log.toml`.
|
||||||
///
|
///
|
||||||
/// If there are no snapshots, an empty list is returned.
|
/// If there are no snapshots, an empty list is returned.
|
||||||
fn list_snapshots(&self, limit: usize, sha: Option<String>) -> Result<Vec<Snapshot>>;
|
fn list_snapshots(&self, limit: usize, sha: Option<String>) -> Result<Vec<Snapshot>>;
|
||||||
@ -169,6 +169,43 @@ impl Oplog for Project {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// also add the gitbutler/integration commit to the branches tree
|
||||||
|
let head = repo.head()?;
|
||||||
|
if head.is_branch() && head.name().unwrap() == "refs/heads/gitbutler/integration" {
|
||||||
|
let commit = head.peel_to_commit()?;
|
||||||
|
let commit_tree = commit.tree()?;
|
||||||
|
|
||||||
|
let mut commit_tree_builder = repo.treebuilder(None)?;
|
||||||
|
|
||||||
|
// get the raw commit data
|
||||||
|
let commit_header = commit.raw_header_bytes();
|
||||||
|
let commit_message = commit.message_raw_bytes();
|
||||||
|
let commit_data = [commit_header, b"\n", commit_message].concat();
|
||||||
|
|
||||||
|
// convert that data into a blob
|
||||||
|
let commit_data_blob = repo.blob(&commit_data)?;
|
||||||
|
commit_tree_builder.insert("commit", commit_data_blob, FileMode::Blob.into())?;
|
||||||
|
commit_tree_builder.insert("tree", commit_tree.id(), FileMode::Tree.into())?;
|
||||||
|
|
||||||
|
let commit_tree_id = commit_tree_builder.write()?;
|
||||||
|
|
||||||
|
// gotta make a subtree to match
|
||||||
|
let mut commits_tree_builder = repo.treebuilder(None)?;
|
||||||
|
commits_tree_builder.insert(
|
||||||
|
commit.id().to_string(),
|
||||||
|
commit_tree_id,
|
||||||
|
FileMode::Tree.into(),
|
||||||
|
)?;
|
||||||
|
let commits_tree_id = commits_tree_builder.write()?;
|
||||||
|
|
||||||
|
let mut branch_tree_builder = repo.treebuilder(None)?;
|
||||||
|
branch_tree_builder.insert("tree", commit_tree.id(), FileMode::Tree.into())?;
|
||||||
|
branch_tree_builder.insert("commits", commits_tree_id, FileMode::Tree.into())?;
|
||||||
|
let branch_tree_id = branch_tree_builder.write()?;
|
||||||
|
|
||||||
|
branches_tree_builder.insert("integration", branch_tree_id, FileMode::Tree.into())?;
|
||||||
|
}
|
||||||
|
|
||||||
let branch_tree_id = branches_tree_builder.write()?;
|
let branch_tree_id = branches_tree_builder.write()?;
|
||||||
tree_builder.insert("virtual_branches", branch_tree_id, FileMode::Tree.into())?;
|
tree_builder.insert("virtual_branches", branch_tree_id, FileMode::Tree.into())?;
|
||||||
|
|
||||||
@ -374,6 +411,8 @@ impl Oplog for Project {
|
|||||||
.to_object(&repo)?
|
.to_object(&repo)?
|
||||||
.into_tree()
|
.into_tree()
|
||||||
.map_err(|_| anyhow!("failed to convert virtual_branches tree entry to tree"))?;
|
.map_err(|_| anyhow!("failed to convert virtual_branches tree entry to tree"))?;
|
||||||
|
let branch_name = branch_entry.name();
|
||||||
|
|
||||||
let commits_tree_entry = branch_tree
|
let commits_tree_entry = branch_tree
|
||||||
.get_name("commits")
|
.get_name("commits")
|
||||||
.ok_or(anyhow!("failed to get commits tree entry"))?;
|
.ok_or(anyhow!("failed to get commits tree entry"))?;
|
||||||
@ -389,10 +428,7 @@ impl Oplog for Project {
|
|||||||
if let Some(commit_id) = commit_entry.name() {
|
if let Some(commit_id) = commit_entry.name() {
|
||||||
// check for the oid in the repo
|
// check for the oid in the repo
|
||||||
let commit_oid = git2::Oid::from_str(commit_id)?;
|
let commit_oid = git2::Oid::from_str(commit_id)?;
|
||||||
if repo.find_commit(commit_oid).is_ok() {
|
if repo.find_commit(commit_oid).is_err() {
|
||||||
continue; // commit is here, so keep going
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit is not in the repo, let's build it from our data
|
// commit is not in the repo, let's build it from our data
|
||||||
// we get the data from the blob entry and create a commit object from it, which should match the oid of the entry
|
// we get the data from the blob entry and create a commit object from it, which should match the oid of the entry
|
||||||
let commit_tree = commit_entry
|
let commit_tree = commit_entry
|
||||||
@ -413,6 +449,26 @@ impl Oplog for Project {
|
|||||||
return Err(anyhow!("commit oid mismatch"));
|
return Err(anyhow!("commit oid mismatch"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if branch_name is 'integration', we need to create or update the gitbutler/integration branch
|
||||||
|
if let Some(branch_name) = branch_name {
|
||||||
|
if branch_name == "integration" {
|
||||||
|
let integration_commit = repo.find_commit(commit_oid)?;
|
||||||
|
// reset the branch if it's there
|
||||||
|
let branch =
|
||||||
|
repo.find_branch("gitbutler/integration", git2::BranchType::Local);
|
||||||
|
if let Ok(mut branch) = branch {
|
||||||
|
// need to detatch the head for just a minuto
|
||||||
|
repo.set_head_detached(commit_oid)?;
|
||||||
|
branch.delete()?;
|
||||||
|
}
|
||||||
|
// ok, now we set the branch to what it was and update HEAD
|
||||||
|
repo.branch("gitbutler/integration", &integration_commit, true)?;
|
||||||
|
// make sure head is gitbutler/integration
|
||||||
|
repo.set_head("refs/heads/gitbutler/integration")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// This tracks the head of the oplog, persisted in oplog.toml.
|
/// This tracks the head of the oplog, persisted in operations-log.toml.
|
||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct Oplog {
|
pub struct Oplog {
|
||||||
/// This is the sha of the last oplog commit
|
/// This is the sha of the last oplog commit
|
||||||
|
@ -282,8 +282,6 @@ pub fn set_target_push_remote(
|
|||||||
) -> Result<(), errors::SetBaseBranchError> {
|
) -> Result<(), errors::SetBaseBranchError> {
|
||||||
let repo = &project_repository.git_repository;
|
let repo = &project_repository.git_repository;
|
||||||
|
|
||||||
dbg!(push_remote_name);
|
|
||||||
|
|
||||||
let remote = repo
|
let remote = repo
|
||||||
.find_remote(push_remote_name)
|
.find_remote(push_remote_name)
|
||||||
.context(format!("failed to find remote {}", push_remote_name))?;
|
.context(format!("failed to find remote {}", push_remote_name))?;
|
||||||
|
@ -156,8 +156,68 @@ async fn test_basic_oplog() {
|
|||||||
assert_eq!(file_lines, "content");
|
assert_eq!(file_lines, "content");
|
||||||
}
|
}
|
||||||
|
|
||||||
// test oplog.toml head is not a commit
|
#[tokio::test]
|
||||||
|
async fn test_oplog_restores_gitbutler_integration() {
|
||||||
|
let Test {
|
||||||
|
repository,
|
||||||
|
project_id,
|
||||||
|
controller,
|
||||||
|
project,
|
||||||
|
..
|
||||||
|
} = &Test::default();
|
||||||
|
|
||||||
|
controller
|
||||||
|
.set_base_branch(project_id, &"refs/remotes/origin/master".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let branch_id = controller
|
||||||
|
.create_virtual_branch(project_id, &branch::BranchCreateRequest::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// create commit
|
||||||
|
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||||
|
let _commit1_id = controller
|
||||||
|
.create_commit(project_id, &branch_id, "commit one", None, false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let repo = git2::Repository::open(&project.path).unwrap();
|
||||||
|
|
||||||
|
// check the integration commit
|
||||||
|
let head = repo.head();
|
||||||
|
let commit = &head.as_ref().unwrap().peel_to_commit().unwrap();
|
||||||
|
let commit_oid = commit.id();
|
||||||
|
let message = commit.summary().unwrap();
|
||||||
|
assert_eq!(message, "GitButler Integration Commit");
|
||||||
|
|
||||||
|
// create second commit
|
||||||
|
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||||
|
let _commit2_id = controller
|
||||||
|
.create_commit(project_id, &branch_id, "commit one", None, false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// check the integration commit changed
|
||||||
|
let head = repo.head();
|
||||||
|
let commit = &head.as_ref().unwrap().peel_to_commit().unwrap();
|
||||||
|
let commit2_oid = commit.id();
|
||||||
|
let message = commit.summary().unwrap();
|
||||||
|
assert_eq!(message, "GitButler Integration Commit");
|
||||||
|
assert_ne!(commit_oid, commit2_oid);
|
||||||
|
|
||||||
|
// restore the first
|
||||||
|
let snapshots = project.list_snapshots(10, None).unwrap();
|
||||||
|
project.restore_snapshot(snapshots[1].clone().id).unwrap();
|
||||||
|
|
||||||
|
let head = repo.head();
|
||||||
|
let commit = &head.as_ref().unwrap().peel_to_commit().unwrap();
|
||||||
|
let commit_restore_oid = commit.id();
|
||||||
|
assert_eq!(commit_oid, commit_restore_oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test operations-log.toml head is not a commit
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_oplog_head_corrupt() {
|
async fn test_oplog_head_corrupt() {
|
||||||
let Test {
|
let Test {
|
||||||
@ -177,7 +237,11 @@ async fn test_oplog_head_corrupt() {
|
|||||||
assert_eq!(snapshots.len(), 1);
|
assert_eq!(snapshots.len(), 1);
|
||||||
|
|
||||||
// overwrite oplog head with a non-commit sha
|
// overwrite oplog head with a non-commit sha
|
||||||
let file_path = repository.path().join(".git").join("operations-log.toml");
|
let file_path = repository
|
||||||
|
.path()
|
||||||
|
.join(".git")
|
||||||
|
.join("gitbutler")
|
||||||
|
.join("operations-log.toml");
|
||||||
fs::write(
|
fs::write(
|
||||||
file_path,
|
file_path,
|
||||||
"head_sha = \"758d54f587227fba3da3b61fbb54a99c17903d59\"",
|
"head_sha = \"758d54f587227fba3da3b61fbb54a99c17903d59\"",
|
||||||
|
@ -126,7 +126,6 @@ pub mod commands {
|
|||||||
branch: &str,
|
branch: &str,
|
||||||
push_remote: Option<&str>, // optional different name of a remote to push to (defaults to same as the branch)
|
push_remote: Option<&str>, // optional different name of a remote to push to (defaults to same as the branch)
|
||||||
) -> Result<BaseBranch, Error> {
|
) -> Result<BaseBranch, Error> {
|
||||||
dbg!(&project_id, &branch, &push_remote);
|
|
||||||
let branch_name = format!("refs/remotes/{}", branch)
|
let branch_name = format!("refs/remotes/{}", branch)
|
||||||
.parse()
|
.parse()
|
||||||
.context("Invalid branch name")?;
|
.context("Invalid branch name")?;
|
||||||
|
Loading…
Reference in New Issue
Block a user