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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
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`.
|
||||
/// 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.
|
||||
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()?;
|
||||
tree_builder.insert("virtual_branches", branch_tree_id, FileMode::Tree.into())?;
|
||||
|
||||
@ -374,6 +411,8 @@ impl Oplog for Project {
|
||||
.to_object(&repo)?
|
||||
.into_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
|
||||
.get_name("commits")
|
||||
.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() {
|
||||
// check for the oid in the repo
|
||||
let commit_oid = git2::Oid::from_str(commit_id)?;
|
||||
if repo.find_commit(commit_oid).is_ok() {
|
||||
continue; // commit is here, so keep going
|
||||
}
|
||||
|
||||
if repo.find_commit(commit_oid).is_err() {
|
||||
// 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
|
||||
let commit_tree = commit_entry
|
||||
@ -413,6 +449,26 @@ impl Oplog for Project {
|
||||
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};
|
||||
|
||||
/// 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)]
|
||||
pub struct Oplog {
|
||||
/// This is the sha of the last oplog commit
|
||||
|
@ -282,8 +282,6 @@ pub fn set_target_push_remote(
|
||||
) -> Result<(), errors::SetBaseBranchError> {
|
||||
let repo = &project_repository.git_repository;
|
||||
|
||||
dbg!(push_remote_name);
|
||||
|
||||
let remote = repo
|
||||
.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");
|
||||
}
|
||||
|
||||
// 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]
|
||||
async fn test_oplog_head_corrupt() {
|
||||
let Test {
|
||||
@ -177,7 +237,11 @@ async fn test_oplog_head_corrupt() {
|
||||
assert_eq!(snapshots.len(), 1);
|
||||
|
||||
// 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(
|
||||
file_path,
|
||||
"head_sha = \"758d54f587227fba3da3b61fbb54a99c17903d59\"",
|
||||
|
@ -126,7 +126,6 @@ pub mod commands {
|
||||
branch: &str,
|
||||
push_remote: Option<&str>, // optional different name of a remote to push to (defaults to same as the branch)
|
||||
) -> Result<BaseBranch, Error> {
|
||||
dbg!(&project_id, &branch, &push_remote);
|
||||
let branch_name = format!("refs/remotes/{}", branch)
|
||||
.parse()
|
||||
.context("Invalid branch name")?;
|
||||
|
Loading…
Reference in New Issue
Block a user