add requires_force calculation for vbranch

This commit is contained in:
Nikita Galaiko 2023-10-11 14:33:01 +02:00 committed by GitButler
parent be86302d6b
commit f7916c7907
5 changed files with 124 additions and 42 deletions

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}

View File

@ -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,