mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-06 01:27:24 +03:00
add requires_force calculation for vbranch
This commit is contained in:
parent
be86302d6b
commit
f7916c7907
@ -1,12 +1,21 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]
|
||||
pub struct Oid {
|
||||
oid: git2::Oid,
|
||||
}
|
||||
|
||||
impl Serialize for Oid {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.oid.to_string().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Oid {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
@ -14,6 +14,12 @@ pub enum Key {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<PrivateKey> for Key {
|
||||
fn from(value: PrivateKey) -> Self {
|
||||
Self::Generated(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrivateKey(ssh_key::PrivateKey);
|
||||
|
||||
|
@ -206,14 +206,26 @@ impl Repository {
|
||||
.context("failed to find remote")
|
||||
.map_err(RemoteError::Other)?;
|
||||
|
||||
if let Ok(Some(url)) = remote.url() {
|
||||
match url.as_ssh() {
|
||||
Ok(ssh_url) => Ok(self
|
||||
.git_repository
|
||||
.remote_anonymous(&ssh_url)
|
||||
.context("failed to get anonymous")
|
||||
.map_err(RemoteError::Other)?),
|
||||
Err(_) => Err(RemoteError::NonSSHUrl(url.to_string())),
|
||||
let remote_url = remote
|
||||
.url()
|
||||
.context("failed to get remote url")
|
||||
.map_err(RemoteError::Other)?;
|
||||
|
||||
if let Some(url) = remote_url {
|
||||
match url.scheme {
|
||||
#[cfg(test)]
|
||||
git::Scheme::File => Ok(remote),
|
||||
git::Scheme::Ssh => Ok(remote),
|
||||
_ => {
|
||||
let ssh_url = url
|
||||
.as_ssh()
|
||||
.map_err(|_| RemoteError::NonSSHUrl(url.to_string()))?;
|
||||
Ok(self
|
||||
.git_repository
|
||||
.remote_anonymous(&ssh_url)
|
||||
.context("failed to get anonymous")
|
||||
.map_err(RemoteError::Other)?)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(RemoteError::NoUrl)
|
||||
@ -373,7 +385,7 @@ pub enum RemoteError {
|
||||
#[error("authentication failed")]
|
||||
AuthError,
|
||||
#[error(transparent)]
|
||||
Other(anyhow::Error),
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl From<RemoteError> for crate::error::Error {
|
||||
|
@ -159,9 +159,7 @@ fn test_signed_commit() -> Result<()> {
|
||||
|
||||
let branches = list_virtual_branches(&gb_repository, &project_repository).unwrap();
|
||||
let commit_id = &branches[0].commits[0].id;
|
||||
let commit_obj = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit_id.parse().unwrap())?;
|
||||
let commit_obj = project_repository.git_repository.find_commit(*commit_id)?;
|
||||
// check the raw_header contains the string "SSH SIGNATURE"
|
||||
assert!(commit_obj.raw_header().unwrap().contains("SSH SIGNATURE"));
|
||||
|
||||
@ -248,9 +246,7 @@ fn test_track_binary_files() -> Result<()> {
|
||||
// status (no files)
|
||||
let branches = list_virtual_branches(&gb_repository, &project_repository).unwrap();
|
||||
let commit_id = &branches[0].commits[0].id;
|
||||
let commit_obj = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit_id.parse().unwrap())?;
|
||||
let commit_obj = project_repository.git_repository.find_commit(*commit_id)?;
|
||||
let tree = commit_obj.tree()?;
|
||||
let files = tree_to_entry_list(&project_repository.git_repository, &tree);
|
||||
assert_eq!(files[0].0, "image.bin");
|
||||
@ -279,9 +275,7 @@ fn test_track_binary_files() -> Result<()> {
|
||||
let branches = list_virtual_branches(&gb_repository, &project_repository).unwrap();
|
||||
let commit_id = &branches[0].commits[0].id;
|
||||
// get tree from commit_id
|
||||
let commit_obj = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit_id.parse().unwrap())?;
|
||||
let commit_obj = project_repository.git_repository.find_commit(*commit_id)?;
|
||||
let tree = commit_obj.tree()?;
|
||||
let files = tree_to_entry_list(&project_repository.git_repository, &tree);
|
||||
|
||||
@ -1033,7 +1027,7 @@ fn test_update_base_branch_base() -> Result<()> {
|
||||
let branch = &branches[0];
|
||||
assert_eq!(branch.files.len(), 1);
|
||||
assert_eq!(branch.commits.len(), 1); // branch commit, rebased
|
||||
let head_sha = branch.commits[0].id.parse::<git::Oid>()?;
|
||||
let head_sha = branch.commits[0].id;
|
||||
|
||||
let head_commit = project_repository.git_repository.find_commit(head_sha)?;
|
||||
let parent = head_commit.parent(0)?;
|
||||
@ -1403,9 +1397,7 @@ fn test_merge_vbranch_upstream_clean() -> Result<()> {
|
||||
|
||||
// make sure the last commit was signed
|
||||
let last_id = &branch1.commits[0].id;
|
||||
let last_commit = project_repository
|
||||
.git_repository
|
||||
.find_commit(last_id.parse::<git::Oid>()?)?;
|
||||
let last_commit = project_repository.git_repository.find_commit(*last_id)?;
|
||||
assert!(last_commit.raw_header().unwrap().contains("SSH SIGNATURE"));
|
||||
|
||||
Ok(())
|
||||
@ -1550,9 +1542,7 @@ fn test_merge_vbranch_upstream_conflict() -> Result<()> {
|
||||
|
||||
// make sure the last commit was a merge commit (2 parents)
|
||||
let last_id = &branch1.commits[0].id;
|
||||
let last_commit = project_repository
|
||||
.git_repository
|
||||
.find_commit(last_id.parse::<git::Oid>()?)?;
|
||||
let last_commit = project_repository.git_repository.find_commit(*last_id)?;
|
||||
assert_eq!(last_commit.parent_count(), 2);
|
||||
|
||||
Ok(())
|
||||
@ -3192,12 +3182,9 @@ fn test_commit_partial_by_file() -> Result<()> {
|
||||
|
||||
// branch one test.txt has just the 1st and 3rd hunks applied
|
||||
let commit2 = &branch1.commits[0].id;
|
||||
let commit2 = commit2
|
||||
.parse::<git::Oid>()
|
||||
.expect("failed to parse commit id");
|
||||
let commit2 = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit2)
|
||||
.find_commit(*commit2)
|
||||
.expect("failed to get commit object");
|
||||
|
||||
let tree = commit1.tree().expect("failed to get tree");
|
||||
@ -3266,12 +3253,9 @@ fn test_commit_add_and_delete_files() -> Result<()> {
|
||||
|
||||
// branch one test.txt has just the 1st and 3rd hunks applied
|
||||
let commit2 = &branch1.commits[0].id;
|
||||
let commit2 = commit2
|
||||
.parse::<git::Oid>()
|
||||
.expect("failed to parse commit id");
|
||||
let commit2 = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit2)
|
||||
.find_commit(*commit2)
|
||||
.expect("failed to get commit object");
|
||||
|
||||
let tree = commit1.tree().expect("failed to get tree");
|
||||
@ -3333,12 +3317,9 @@ fn test_commit_executable_and_symlinks() -> Result<()> {
|
||||
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
|
||||
|
||||
let commit = &branch1.commits[0].id;
|
||||
let commit = commit
|
||||
.parse::<git::Oid>()
|
||||
.expect("failed to parse commit id");
|
||||
let commit = project_repository
|
||||
.git_repository
|
||||
.find_commit(commit)
|
||||
.find_commit(*commit)
|
||||
.expect("failed to get commit object");
|
||||
|
||||
let tree = commit.tree().expect("failed to get tree");
|
||||
@ -3778,7 +3759,7 @@ fn test_apply_out_of_date_conflicting_vbranch() -> Result<()> {
|
||||
let branches = list_virtual_branches(&gb_repository, &project_repository)?;
|
||||
let branch1 = &branches.iter().find(|b| &b.id == branch_id).unwrap();
|
||||
let last_commit = branch1.commits.first().unwrap();
|
||||
let last_commit_oid = last_commit.id.parse::<git::Oid>()?;
|
||||
let last_commit_oid = last_commit.id;
|
||||
let commit = gb_repository.git_repository.find_commit(last_commit_oid)?;
|
||||
assert!(!branch1.conflicted);
|
||||
assert_eq!(commit.parent_count(), 2);
|
||||
@ -4107,3 +4088,65 @@ fn test_reset_to_target() {
|
||||
"2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requires_force() {
|
||||
let suite = Suite::default();
|
||||
let Case {
|
||||
project_repository,
|
||||
gb_repository,
|
||||
project,
|
||||
..
|
||||
} = suite.new_case();
|
||||
set_test_target(&gb_repository, &project_repository).unwrap();
|
||||
|
||||
let branch_id = create_virtual_branch(&gb_repository, &BranchCreateRequest::default())
|
||||
.expect("failed to create virtual branch")
|
||||
.id;
|
||||
|
||||
fs::write(project.path.join("file.txt"), "1").unwrap();
|
||||
commit(
|
||||
&gb_repository,
|
||||
&project_repository,
|
||||
&branch_id,
|
||||
"commit 1",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
fs::write(project.path.join("file.txt"), "2").unwrap();
|
||||
commit(
|
||||
&gb_repository,
|
||||
&project_repository,
|
||||
&branch_id,
|
||||
"commit 2",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
push(
|
||||
&project_repository,
|
||||
&gb_repository,
|
||||
&branch_id,
|
||||
&keys::Key::from(suite.keys.get_or_create().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let statuses = list_virtual_branches(&gb_repository, &project_repository).unwrap();
|
||||
assert!(!statuses[0].requires_force);
|
||||
|
||||
reset_branch(
|
||||
&gb_repository,
|
||||
&project_repository,
|
||||
&branch_id,
|
||||
statuses[0].commits[1].id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let statuses = list_virtual_branches(&gb_repository, &project_repository).unwrap();
|
||||
assert!(statuses[0].requires_force);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ use super::{
|
||||
//
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct VirtualBranch {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@ -40,6 +41,7 @@ pub struct VirtualBranch {
|
||||
pub active: bool,
|
||||
pub files: Vec<VirtualBranchFile>,
|
||||
pub commits: Vec<VirtualBranchCommit>,
|
||||
pub requires_force: bool, // does this branch require a force push to the upstream?
|
||||
pub conflicted: bool, // is this branch currently in a conflicted state (only for applied branches)
|
||||
pub order: usize, // the order in which this branch should be displayed in the UI
|
||||
pub upstream: Option<git::RemoteBranchName>, // the name of the upstream branch this branch this pushes to
|
||||
@ -59,7 +61,7 @@ pub struct VirtualBranch {
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VirtualBranchCommit {
|
||||
pub id: String,
|
||||
pub id: git::Oid,
|
||||
pub description: String,
|
||||
pub created_at: u128,
|
||||
pub author: Author,
|
||||
@ -637,6 +639,16 @@ pub fn list_virtual_branches(
|
||||
}
|
||||
}
|
||||
|
||||
let requires_force = if let Some(upstream) = &branch.upstream {
|
||||
let upstream_commit = repo
|
||||
.find_commit(repo.refname_to_id(&upstream.to_string())?)
|
||||
.context("failed to find upstream commit")?;
|
||||
let merge_base = repo.merge_base(upstream_commit.id(), branch.head)?;
|
||||
merge_base != upstream_commit.id()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let branch = VirtualBranch {
|
||||
id: branch.id.to_string(),
|
||||
name: branch.name.to_string(),
|
||||
@ -645,6 +657,7 @@ pub fn list_virtual_branches(
|
||||
files: vfiles,
|
||||
order: branch.order,
|
||||
commits,
|
||||
requires_force,
|
||||
upstream: branch.upstream.clone(),
|
||||
conflicted: conflicts::is_resolving(project_repository),
|
||||
base_current,
|
||||
@ -810,7 +823,6 @@ pub fn commit_to_vbranch_commit(
|
||||
let timestamp = commit.time().seconds() as u128;
|
||||
let signature = commit.author();
|
||||
let message = commit.message().unwrap().to_string();
|
||||
let sha = commit.id().to_string();
|
||||
|
||||
let is_remote = match upstream_commits {
|
||||
Some(commits) => commits.contains_key(&commit.id()),
|
||||
@ -823,7 +835,7 @@ pub fn commit_to_vbranch_commit(
|
||||
let is_integrated = is_commit_integrated(repository, target, commit)?;
|
||||
|
||||
let commit = VirtualBranchCommit {
|
||||
id: sha,
|
||||
id: commit.id(),
|
||||
created_at: timestamp * 1000,
|
||||
author: Author::from(signature),
|
||||
description: message,
|
||||
|
Loading…
Reference in New Issue
Block a user