mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-10-27 13:39:19 +03:00
use branch::Name for virtual_branches
This commit is contained in:
parent
e458bfd077
commit
2b5e981e28
@ -365,7 +365,7 @@ impl App {
|
||||
pub async fn create_virtual_branch_from_branch(
|
||||
&self,
|
||||
project_id: &str,
|
||||
branch: &str,
|
||||
branch: &project_repository::branch::Name,
|
||||
) -> Result<String> {
|
||||
let gb_repository = self.gb_repository(project_id)?;
|
||||
let project = self.gb_project(project_id)?;
|
||||
|
@ -607,11 +607,11 @@ async fn create_virtual_branch(
|
||||
async fn create_virtual_branch_from_branch(
|
||||
handle: tauri::AppHandle,
|
||||
project_id: &str,
|
||||
branch: &str,
|
||||
branch: branch::Name,
|
||||
) -> Result<String, Error> {
|
||||
let app = handle.state::<app::App>();
|
||||
let branch_id = app
|
||||
.create_virtual_branch_from_branch(project_id, branch)
|
||||
.create_virtual_branch_from_branch(project_id, &branch)
|
||||
.await
|
||||
.context("failed to create virtual branch from branch")?;
|
||||
Ok(branch_id)
|
||||
|
@ -1,113 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteName {
|
||||
// contains name of the remote, e.x. "origin" or "upstream"
|
||||
remote: String,
|
||||
// contains name of the branch, e.x. "master" or "main"
|
||||
branch: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for RemoteName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.remote, self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for RemoteName {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for RemoteName {
|
||||
type Error = git2::Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
if !value.get().is_remote() {
|
||||
return Err(git2::Error::from_str("not a remote branch"));
|
||||
}
|
||||
let name = String::from_utf8(value.name_bytes()?.to_vec())
|
||||
.map_err(|e| git2::Error::from_str(&e.to_string()))?;
|
||||
|
||||
if let Some((remote, branch)) = name.split_once('/') {
|
||||
Ok(Self {
|
||||
remote: remote.to_string(),
|
||||
branch: branch.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(git2::Error::from_str("invalid remote branch name"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalName {
|
||||
// contains name of the branch, e.x. "master" or "main"
|
||||
branch: String,
|
||||
// contains name of the remote branch, if the local branch is tracking a remote branch
|
||||
remote: Option<RemoteName>,
|
||||
}
|
||||
|
||||
impl Serialize for LocalName {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for LocalName {
|
||||
type Error = git2::Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
if value.get().is_remote() {
|
||||
return Err(git2::Error::from_str("not a local branch"));
|
||||
}
|
||||
let name = String::from_utf8(value.name_bytes()?.to_vec())
|
||||
.map_err(|e| git2::Error::from_str(&e.to_string()))?;
|
||||
match value.upstream() {
|
||||
Ok(upstream) => {
|
||||
let remote_branch_name = RemoteName::try_from(&upstream)?;
|
||||
Ok(Self {
|
||||
branch: name,
|
||||
remote: Some(remote_branch_name),
|
||||
})
|
||||
}
|
||||
Err(error) => {
|
||||
if error.code() == git2::ErrorCode::NotFound {
|
||||
Ok(Self {
|
||||
branch: name,
|
||||
remote: None,
|
||||
})
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Name {
|
||||
Remote(RemoteName),
|
||||
Local(LocalName),
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for Name {
|
||||
type Error = git2::Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
if value.get().is_remote() {
|
||||
Ok(Self::Remote(RemoteName::try_from(value)?))
|
||||
} else {
|
||||
Ok(Self::Local(LocalName::try_from(value)?))
|
||||
}
|
||||
}
|
||||
}
|
3
src-tauri/src/project_repository/branch/mod.rs
Normal file
3
src-tauri/src/project_repository/branch/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod names;
|
||||
|
||||
pub use names::*;
|
13
src-tauri/src/project_repository/branch/names/error.rs
Normal file
13
src-tauri/src/project_repository/branch/names/error.rs
Normal file
@ -0,0 +1,13 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("branch name is invalid")]
|
||||
InvalidName(String),
|
||||
#[error("branch is not local")]
|
||||
NotLocal(String),
|
||||
#[error("branch is not remote")]
|
||||
NotRemote(String),
|
||||
#[error(transparent)]
|
||||
GitError(#[from] git2::Error),
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] std::string::FromUtf8Error),
|
||||
}
|
85
src-tauri/src/project_repository/branch/names/local.rs
Normal file
85
src-tauri/src/project_repository/branch/names/local.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{error::Error, remote};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Name {
|
||||
// contains name of the branch, e.x. "master" or "main"
|
||||
branch: String,
|
||||
// contains name of the remote branch, if the local branch is tracking a remote branch
|
||||
remote: Option<remote::Name>,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn branch(&self) -> &str {
|
||||
&self.branch
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Name {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Deserialize<'d> for Name {
|
||||
fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let name = String::deserialize(deserializer)?;
|
||||
Self::try_from(name.as_str()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "refs/heads/{}", self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
if !value.starts_with("refs/heads/") {
|
||||
return Err(Error::NotLocal(value.to_string()));
|
||||
}
|
||||
|
||||
if let Some(branch) = value.strip_prefix("refs/heads/") {
|
||||
Ok(Self {
|
||||
branch: branch.to_string(),
|
||||
remote: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidName(value.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
let branch = String::from_utf8(value.name_bytes()?.to_vec()).map_err(Error::Utf8Error)?;
|
||||
if value.get().is_remote() {
|
||||
Err(Error::NotLocal(branch))
|
||||
} else {
|
||||
match value.upstream() {
|
||||
Ok(upstream) => Ok(Self {
|
||||
branch,
|
||||
remote: Some(remote::Name::try_from(&upstream)?),
|
||||
}),
|
||||
Err(error) => {
|
||||
if error.code() == git2::ErrorCode::NotFound {
|
||||
Ok(Self {
|
||||
branch,
|
||||
remote: None,
|
||||
})
|
||||
} else {
|
||||
Err(error.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
src-tauri/src/project_repository/branch/names/mod.rs
Normal file
81
src-tauri/src/project_repository/branch/names/mod.rs
Normal file
@ -0,0 +1,81 @@
|
||||
mod error;
|
||||
mod local;
|
||||
mod remote;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use error::Error;
|
||||
pub use local::Name as LocalName;
|
||||
pub use remote::Name as RemoteName;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Name {
|
||||
Remote(RemoteName),
|
||||
Local(LocalName),
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn branch(&self) -> &str {
|
||||
match self {
|
||||
Self::Remote(remote) => remote.branch(),
|
||||
Self::Local(local) => local.branch(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
||||
if value.starts_with("refs") {
|
||||
if value.starts_with("refs/remotes/") {
|
||||
Ok(Self::Remote(RemoteName::try_from(value)?))
|
||||
} else if value.starts_with("refs/heads/") {
|
||||
Ok(Self::Local(LocalName::try_from(value)?))
|
||||
} else {
|
||||
Err(Error::InvalidName(value.to_string()))
|
||||
}
|
||||
} else {
|
||||
Ok(Self::Local(LocalName::try_from(value)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
if value.get().is_remote() {
|
||||
Ok(Self::Remote(RemoteName::try_from(value)?))
|
||||
} else {
|
||||
Ok(Self::Local(LocalName::try_from(value)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Remote(remote) => remote.fmt(f),
|
||||
Self::Local(local) => local.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Name {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Self::Remote(remote) => remote.serialize(serializer),
|
||||
Self::Local(local) => local.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Deserialize<'d> for Name {
|
||||
fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let name = String::deserialize(deserializer)?;
|
||||
Self::try_from(name.as_str()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
77
src-tauri/src/project_repository/branch/names/remote.rs
Normal file
77
src-tauri/src/project_repository/branch/names/remote.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::error::Error;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Name {
|
||||
// contains name of the remote, e.x. "origin" or "upstream"
|
||||
remote: String,
|
||||
// contains name of the branch, e.x. "master" or "main"
|
||||
branch: String,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn branch(&self) -> &str {
|
||||
&self.branch
|
||||
}
|
||||
|
||||
pub fn remote(&self) -> &str {
|
||||
&self.remote
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "refs/remotes/{}/{}", self.remote, self.branch)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Name {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Deserialize<'d> for Name {
|
||||
fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let name = String::deserialize(deserializer)?;
|
||||
Self::try_from(name.as_str()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
||||
if !value.starts_with("refs/remotes/") {
|
||||
return Err(Error::NotRemote(value.to_string()));
|
||||
};
|
||||
|
||||
if let Some((remote, branch)) = value.strip_prefix("refs/remotes/").unwrap().split_once('/')
|
||||
{
|
||||
Ok(Self {
|
||||
remote: remote.to_string(),
|
||||
branch: branch.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidName(value.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&git2::Branch<'_>> for Name {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &git2::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
||||
let refname =
|
||||
String::from_utf8(value.get().name_bytes().to_vec()).map_err(Error::Utf8Error)?;
|
||||
|
||||
if !value.get().is_remote() {
|
||||
return Err(Error::NotRemote(refname));
|
||||
}
|
||||
|
||||
Self::try_from(refname.as_str())
|
||||
}
|
||||
}
|
@ -357,10 +357,18 @@ impl<'repository> Repository<'repository> {
|
||||
allowed_types.get()
|
||||
}
|
||||
|
||||
pub fn push(&self, head: &git2::Oid, upstream: &str) -> Result<(), Error> {
|
||||
pub fn push(&self, head: &git2::Oid, branch: &branch::Name) -> Result<(), Error> {
|
||||
let upstream = match branch {
|
||||
branch::Name::Local(_) => {
|
||||
// TODO: no need to actually push to local branch
|
||||
return Ok(());
|
||||
}
|
||||
branch::Name::Remote(upstream) => upstream,
|
||||
};
|
||||
|
||||
let mut remote = self
|
||||
.git_repository
|
||||
.find_remote("origin")
|
||||
.find_remote(upstream.remote())
|
||||
.context("failed to find remote")
|
||||
.map_err(Error::Other)?;
|
||||
|
||||
@ -372,8 +380,8 @@ impl<'repository> Repository<'repository> {
|
||||
|
||||
let output = Command::new("git")
|
||||
.arg("push")
|
||||
.arg("origin")
|
||||
.arg(format!("{}:{}", head, upstream))
|
||||
.arg(upstream.remote())
|
||||
.arg(format!("{}:{}", head, upstream.to_string()))
|
||||
.current_dir(&self.project.path)
|
||||
.output()
|
||||
.context("failed to fork exec")
|
||||
|
@ -14,6 +14,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::project_repository::branch;
|
||||
|
||||
// this is the struct for the virtual branch data that is stored in our data
|
||||
// store. it is more or less equivalent to a git branch reference, but it is not
|
||||
// stored or accessible from the git repository itself. it is stored in our
|
||||
@ -23,7 +25,7 @@ pub struct Branch {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub applied: bool,
|
||||
pub upstream: Option<String>,
|
||||
pub upstream: Option<branch::Name>,
|
||||
pub created_timestamp_ms: u128,
|
||||
pub updated_timestamp_ms: u128,
|
||||
// tree is the last git tree written to a session, or merge base tree if this is new. use this for delta calculation from the session data
|
||||
@ -92,18 +94,19 @@ impl TryFrom<&dyn crate::reader::Reader> for Branch {
|
||||
if upstream.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(upstream))
|
||||
branch::Name::try_from(upstream.as_str())
|
||||
.map(Some)
|
||||
.map_err(|e| {
|
||||
crate::reader::Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("meta/upstream: {}", e),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(crate::reader::Error::NotFound) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
.map_err(|e| {
|
||||
crate::reader::Error::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("meta/upstream: {}", e),
|
||||
))
|
||||
})?;
|
||||
}?;
|
||||
|
||||
let tree = reader.read_string("meta/tree").map_err(|e| {
|
||||
crate::reader::Error::IOError(std::io::Error::new(
|
||||
|
@ -48,7 +48,12 @@ mod tests {
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
order: unsafe { TEST_INDEX },
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
|
@ -62,7 +62,10 @@ impl<'writer> BranchWriter<'writer> {
|
||||
.context("Failed to write branch applied")?;
|
||||
if let Some(upstream) = &branch.upstream {
|
||||
self.writer
|
||||
.write_string(&format!("branches/{}/meta/upstream", branch.id), upstream)
|
||||
.write_string(
|
||||
&format!("branches/{}/meta/upstream", branch.id),
|
||||
&upstream.to_string(),
|
||||
)
|
||||
.context("Failed to write branch upstream")?;
|
||||
};
|
||||
self.writer
|
||||
@ -121,7 +124,12 @@ mod tests {
|
||||
id: format!("branch_{}", unsafe { TEST_INDEX }),
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
@ -194,7 +202,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join("meta").join("upstream").to_str().unwrap())
|
||||
.context("Failed to read branch upstream")?,
|
||||
branch.upstream.clone().unwrap()
|
||||
branch.upstream.clone().unwrap().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(
|
||||
@ -269,7 +277,7 @@ mod tests {
|
||||
let updated_branch = Branch {
|
||||
name: "updated_name".to_string(),
|
||||
applied: false,
|
||||
upstream: Some("updated_upstream".to_string()),
|
||||
upstream: Some("refs/remotes/origin/upstream_updated".try_into().unwrap()),
|
||||
created_timestamp_ms: 2,
|
||||
updated_timestamp_ms: 3,
|
||||
ownership: branch::Ownership { files: vec![] },
|
||||
@ -294,7 +302,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join("meta").join("upstream").to_str().unwrap())
|
||||
.context("Failed to read branch upstream")?,
|
||||
updated_branch.upstream.unwrap()
|
||||
updated_branch.upstream.unwrap().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(
|
||||
|
@ -79,7 +79,12 @@ mod tests {
|
||||
id: format!("branch_{}", unsafe { TEST_INDEX }),
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
|
@ -44,7 +44,7 @@ pub struct VirtualBranch {
|
||||
pub merge_conflicts: Vec<String>,
|
||||
pub conflicted: bool,
|
||||
pub order: usize,
|
||||
pub upstream: Option<String>,
|
||||
pub upstream: Option<project_repository::branch::Name>,
|
||||
pub base_current: bool, // is this vbranch based on the current base branch?
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ pub struct RemoteBranch {
|
||||
pub first_commit_ts: u128,
|
||||
pub ahead: u32,
|
||||
pub behind: u32,
|
||||
pub upstream: Option<String>,
|
||||
pub upstream: Option<project_repository::branch::RemoteName>,
|
||||
pub authors: Vec<Author>,
|
||||
pub mergeable: bool,
|
||||
pub merge_conflicts: Vec<String>,
|
||||
@ -458,7 +458,8 @@ pub fn remote_branches(
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
.into_iter()
|
||||
.filter_map(|branch| branch.upstream.map(|u| u.replace("refs/heads/", "")))
|
||||
.filter_map(|branch| branch.upstream)
|
||||
.map(|upstream| upstream.branch().to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut most_recent_branches_by_hash: HashMap<git2::Oid, (git2::Branch, u64)> = HashMap::new();
|
||||
|
||||
@ -481,20 +482,17 @@ pub fn remote_branches(
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name = branch
|
||||
.name()
|
||||
.context("could not get branch name")?
|
||||
.context("could not get branch name")?
|
||||
.to_string();
|
||||
let branch_name = branch_name.replace("origin/", "");
|
||||
let branch_name = project_repository::branch::Name::try_from(&branch);
|
||||
println!("branch_name: {:?}", branch_name);
|
||||
let branch_name = branch_name.context("could not get branch name")?;
|
||||
|
||||
if virtual_branches_names.contains(&branch_name) {
|
||||
if virtual_branches_names.contains(branch_name.branch()) {
|
||||
continue;
|
||||
}
|
||||
if branch_name == "HEAD" {
|
||||
if branch_name.branch().eq("HEAD") {
|
||||
continue;
|
||||
}
|
||||
if branch_name == "gitbutler/integration" {
|
||||
if branch_name.branch().eq("gitbutler/integration") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -539,7 +537,6 @@ pub fn remote_branches(
|
||||
let mut branches: Vec<RemoteBranch> = Vec::new();
|
||||
for branch in &top_branches {
|
||||
let branch_name = branch.get().name().context("could not get branch name")?;
|
||||
let upstream_branch = branch.upstream();
|
||||
match branch.get().target() {
|
||||
Some(branch_oid) => {
|
||||
// get the branch ref
|
||||
@ -564,12 +561,13 @@ pub fn remote_branches(
|
||||
.map(Author::from)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let upstream = match upstream_branch {
|
||||
Ok(upstream_branch) => {
|
||||
upstream_branch.get().name().map(|name| name.to_string())
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
let upstream = branch
|
||||
.upstream()
|
||||
.ok()
|
||||
.map(|upstream_branch| {
|
||||
project_repository::branch::RemoteName::try_from(&upstream_branch)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
if count_ahead > 0 {
|
||||
if let Ok(base_tree) = find_base_tree(repo, &branch_commit, &target_commit) {
|
||||
@ -788,8 +786,7 @@ pub fn list_virtual_branches(
|
||||
}
|
||||
}
|
||||
if let Some(remote) = upstream_remote {
|
||||
// remove "refs/heads/" from the branch name
|
||||
let branch_name = branch_upstream.replace("refs/heads/", "");
|
||||
let branch_name = branch_upstream.branch();
|
||||
let full_branch_name =
|
||||
format!("refs/remotes/{}/{}", remote.name().unwrap(), branch_name);
|
||||
if let Ok(upstream_oid) = repo.refname_to_id(&full_branch_name) {
|
||||
@ -880,10 +877,7 @@ pub fn list_virtual_branches(
|
||||
commits,
|
||||
mergeable,
|
||||
merge_conflicts,
|
||||
upstream: branch
|
||||
.upstream
|
||||
.as_ref()
|
||||
.map(|u| u.replace("refs/heads/", "")),
|
||||
upstream: branch.upstream.clone(),
|
||||
conflicted: conflicts::is_resolving(project_repository),
|
||||
base_current,
|
||||
};
|
||||
@ -896,13 +890,8 @@ pub fn list_virtual_branches(
|
||||
pub fn create_virtual_branch_from_branch(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
project_repository: &project_repository::Repository,
|
||||
branch_ref: &str,
|
||||
upstream: &project_repository::branch::Name,
|
||||
) -> Result<String> {
|
||||
let name = branch_ref
|
||||
.replace("refs/heads/", "")
|
||||
.replace("refs/remotes/", "")
|
||||
.replace("origin/", ""); // TODO: get this properly
|
||||
let upstream = Some(format!("refs/heads/{}", &name));
|
||||
let current_session = gb_repository
|
||||
.get_or_create_current_session()
|
||||
.context("failed to get or create current session")?;
|
||||
@ -914,7 +903,7 @@ pub fn create_virtual_branch_from_branch(
|
||||
.context("no default target found")?;
|
||||
|
||||
let repo = &project_repository.git_repository;
|
||||
let head = repo.revparse_single(branch_ref)?;
|
||||
let head = repo.revparse_single(&upstream.to_string())?;
|
||||
let head_commit = head.peel_to_commit()?;
|
||||
let tree = head_commit.tree().context("failed to find tree")?;
|
||||
|
||||
@ -933,9 +922,9 @@ pub fn create_virtual_branch_from_branch(
|
||||
let branch_id = Uuid::new_v4().to_string();
|
||||
let mut branch = Branch {
|
||||
id: branch_id.clone(),
|
||||
name,
|
||||
name: upstream.branch().to_string(),
|
||||
applied: false,
|
||||
upstream,
|
||||
upstream: Some(upstream.clone()),
|
||||
tree: tree.id(),
|
||||
head: head_commit.id(),
|
||||
created_timestamp_ms: now,
|
||||
@ -2015,13 +2004,17 @@ pub fn commit(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn name_to_branch(name: &str) -> String {
|
||||
|
||||
fn name_to_branch(name: &str) -> project_repository::branch::LocalName {
|
||||
let cleaned_name = name
|
||||
.chars()
|
||||
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
|
||||
.collect::<String>();
|
||||
|
||||
format!("refs/heads/{}", cleaned_name)
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@ -2065,15 +2058,16 @@ pub fn push(
|
||||
|
||||
let upstream = vbranch
|
||||
.upstream
|
||||
.unwrap_or_else(|| name_to_branch(&vbranch.name));
|
||||
.unwrap_or_else(|| project_repository::branch::Name::Local(name_to_branch(&vbranch.name)));
|
||||
|
||||
match project_repository.push(&vbranch.head, &upstream) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(project_repository::Error::UnsupportedAuthCredentials(cred_type)) => {
|
||||
return Err(Error::UnsupportedAuthCredentials(cred_type))
|
||||
}
|
||||
Err(err) => Err(Error::Other(err.into())),
|
||||
}?;
|
||||
project_repository
|
||||
.push(&vbranch.head, &upstream)
|
||||
.map_err(|err| match err {
|
||||
project_repository::Error::UnsupportedAuthCredentials(cred_type) => {
|
||||
Error::UnsupportedAuthCredentials(cred_type)
|
||||
}
|
||||
err => Error::Other(err.into()),
|
||||
})?;
|
||||
|
||||
vbranch.upstream = Some(upstream);
|
||||
branch_writer
|
||||
|
@ -54,7 +54,12 @@ mod tests {
|
||||
id: format!("branch_{}", unsafe { TEST_INDEX }),
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
|
@ -104,8 +104,13 @@ mod tests {
|
||||
id: format!("branch_{}", unsafe { TEST_INDEX }),
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
"0123456789abcdef0123456789abcdef0123456{}",
|
||||
@ -207,7 +212,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join("meta").join("upstream").to_str().unwrap())
|
||||
.context("Failed to read branch upstream")?,
|
||||
branch.upstream.unwrap()
|
||||
branch.upstream.unwrap().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(
|
||||
|
@ -1918,15 +1918,12 @@ fn test_detect_remote_commits() -> Result<()> {
|
||||
// push the commit upstream
|
||||
let branch1 = branch_reader.read(&branch1_id)?;
|
||||
let up_target = branch1.head;
|
||||
repository.reference(
|
||||
"refs/remotes/origin/remote_branch",
|
||||
up_target,
|
||||
true,
|
||||
"update target",
|
||||
)?;
|
||||
let remote_branch: project_repository::branch::Name =
|
||||
"refs/remotes/origin/remote_branch".try_into().unwrap();
|
||||
repository.reference(&remote_branch.to_string(), up_target, true, "update target")?;
|
||||
// set the upstream reference
|
||||
branch_writer.write(&Branch {
|
||||
upstream: Some("remote_branch".to_string()),
|
||||
upstream: Some(remote_branch),
|
||||
..branch1
|
||||
})?;
|
||||
|
||||
@ -1995,14 +1992,18 @@ fn test_create_vbranch_from_remote_branch() -> Result<()> {
|
||||
"line1\nline2\nline3\nline4\nbranch\n",
|
||||
)?;
|
||||
commit_all(&repository)?;
|
||||
|
||||
let upstream: project_repository::branch::Name =
|
||||
"refs/remotes/origin/branch1".try_into().unwrap();
|
||||
|
||||
repository.reference(
|
||||
"refs/remotes/branch1",
|
||||
&upstream.to_string(),
|
||||
repository.head().unwrap().target().unwrap(),
|
||||
true,
|
||||
"update target",
|
||||
)?;
|
||||
|
||||
repository.set_head("refs/heads/gitbutler/integration")?;
|
||||
repository.set_head(&upstream.to_string())?;
|
||||
repository.checkout_head(Some(&mut git2::build::CheckoutBuilder::default().force()))?;
|
||||
|
||||
// reset the first file
|
||||
@ -2021,8 +2022,7 @@ fn test_create_vbranch_from_remote_branch() -> Result<()> {
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
|
||||
// create a new virtual branch from the remote branch
|
||||
let branch2_id =
|
||||
create_virtual_branch_from_branch(&gb_repo, &project_repository, "refs/remotes/branch1")?;
|
||||
let branch2_id = create_virtual_branch_from_branch(&gb_repo, &project_repository, &upstream)?;
|
||||
|
||||
// shouldn't be anything on either of our branches
|
||||
let branches = list_virtual_branches(&gb_repo, &project_repository, true)?;
|
||||
@ -2157,8 +2157,10 @@ fn test_create_vbranch_from_behind_remote_branch() -> Result<()> {
|
||||
commit_all(&repository)?;
|
||||
let remote_commit = repository.head().unwrap().target().unwrap();
|
||||
|
||||
let remote_branch: project_repository::branch::Name =
|
||||
"refs/remotes/origin/branch1".try_into().unwrap();
|
||||
repository.reference(
|
||||
"refs/remotes/origin/branch1",
|
||||
&remote_branch.to_string(),
|
||||
remote_commit,
|
||||
true,
|
||||
"update target",
|
||||
@ -2181,7 +2183,7 @@ fn test_create_vbranch_from_behind_remote_branch() -> Result<()> {
|
||||
let branch1_id = create_virtual_branch_from_branch(
|
||||
&gb_repo,
|
||||
&project_repository,
|
||||
"refs/remotes/origin/branch1",
|
||||
&remote_branch,
|
||||
)?;
|
||||
|
||||
let branches = list_virtual_branches(&gb_repo, &project_repository, true)?;
|
||||
|
@ -34,7 +34,12 @@ fn test_branch() -> virtual_branches::branch::Branch {
|
||||
id: format!("branch_{}", unsafe { TEST_INDEX }),
|
||||
name: format!("branch_name_{}", unsafe { TEST_INDEX }),
|
||||
applied: true,
|
||||
upstream: Some(format!("upstream_{}", unsafe { TEST_INDEX })),
|
||||
upstream: Some(
|
||||
format!("refs/remotes/origin/upstream_{}", unsafe { TEST_INDEX })
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
created_timestamp_ms: unsafe { TEST_INDEX } as u128,
|
||||
updated_timestamp_ms: unsafe { TEST_INDEX + 100 } as u128,
|
||||
head: git2::Oid::from_str(&format!(
|
||||
|
Loading…
Reference in New Issue
Block a user