mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-02 07:53:55 +03:00
merged upstream
This commit is contained in:
commit
44a032f492
3
.github/workflows/push.yaml
vendored
3
.github/workflows/push.yaml
vendored
@ -77,5 +77,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/init-env-rust
|
- uses: ./.github/actions/init-env-rust
|
||||||
- run: cargo install cargo-cranky
|
- run: cargo clippy --all-targets --all-features --tests
|
||||||
- run: cargo cranky --all-targets --all-features
|
|
||||||
|
77
Cargo.toml
77
Cargo.toml
@ -10,3 +10,80 @@ codegen-units = 1 # Compile crates one after another so the compiler can optimiz
|
|||||||
lto = true # Enables link to optimizations
|
lto = true # Enables link to optimizations
|
||||||
opt-level = "s" # Optimize for binary size
|
opt-level = "s" # Optimize for binary size
|
||||||
debug = true # Enable debug symbols, for sentry
|
debug = true # Enable debug symbols, for sentry
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
all = "deny"
|
||||||
|
perf = "deny"
|
||||||
|
correctness = "deny"
|
||||||
|
complexity = "deny"
|
||||||
|
style = "deny"
|
||||||
|
pedantic = "deny"
|
||||||
|
|
||||||
|
# selection from clippy::restriction (see https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=restriction)
|
||||||
|
as_underscore = "deny"
|
||||||
|
assertions_on_result_states = "deny"
|
||||||
|
clone_on_ref_ptr = "deny"
|
||||||
|
create_dir = "deny"
|
||||||
|
dbg_macro = "deny"
|
||||||
|
decimal_literal_representation = "deny"
|
||||||
|
default_numeric_fallback = "deny"
|
||||||
|
empty_drop = "deny"
|
||||||
|
empty_structs_with_brackets = "deny"
|
||||||
|
exit = "deny"
|
||||||
|
filetype_is_file = "deny"
|
||||||
|
float_cmp_const = "deny"
|
||||||
|
fn_to_numeric_cast_any = "deny"
|
||||||
|
format_push_string = "deny"
|
||||||
|
get_unwrap = "deny"
|
||||||
|
integer_division = "deny"
|
||||||
|
lossy_float_literal = "deny"
|
||||||
|
mem_forget = "deny"
|
||||||
|
mixed_read_write_in_expression = "deny"
|
||||||
|
mutex_atomic = "deny"
|
||||||
|
needless_raw_strings = "deny"
|
||||||
|
non_ascii_literal = "deny"
|
||||||
|
panic = "deny"
|
||||||
|
print_stderr = "deny"
|
||||||
|
pub_without_shorthand = "deny"
|
||||||
|
rc_buffer = "deny"
|
||||||
|
rc_mutex = "deny"
|
||||||
|
redundant_type_annotations = "deny"
|
||||||
|
ref_patterns = "deny"
|
||||||
|
rest_pat_in_fully_bound_structs = "deny"
|
||||||
|
same_name_method = "deny"
|
||||||
|
string_add = "deny"
|
||||||
|
string_lit_chars_any = "deny"
|
||||||
|
string_slice = "deny"
|
||||||
|
string_to_string = "deny"
|
||||||
|
suspicious_xor_used_as_pow = "deny"
|
||||||
|
todo = "deny"
|
||||||
|
try_err = "deny"
|
||||||
|
unimplemented = "deny"
|
||||||
|
unnecessary_self_imports = "deny"
|
||||||
|
unneeded_field_pattern = "deny"
|
||||||
|
unseparated_literal_suffix = "deny"
|
||||||
|
if_then_some_else_none = "deny"
|
||||||
|
use_debug = "deny"
|
||||||
|
# TODO
|
||||||
|
# partial_pub_fields = "deny"
|
||||||
|
# print_stdout = "deny"
|
||||||
|
# unwrap_used = "deny"
|
||||||
|
# unwrap_in_result = "deny"
|
||||||
|
|
||||||
|
# noise and or false-positives
|
||||||
|
missing_errors_doc = { level = "allow", priority = 1 }
|
||||||
|
used_underscore_binding = { level = "allow", priority = 1 }
|
||||||
|
must_use_candidate = { level = "allow", priority = 1 }
|
||||||
|
module_name_repetitions = { level = "allow", priority = 1 }
|
||||||
|
missing_panics_doc = { level = "allow", priority = 1 }
|
||||||
|
too_many_lines = { level = "allow", priority = 1 }
|
||||||
|
implicit_hasher = { level = "allow", priority = 1 }
|
||||||
|
if_not_else = { level = "allow", priority = 1 }
|
||||||
|
return_self_not_must_use = { level = "allow", priority = 1 }
|
||||||
|
inconsistent_struct_constructor = { level = "allow", priority = 1 }
|
||||||
|
match_wildcard_for_single_variants = { level = "allow", priority = 1 }
|
||||||
|
unnested_or_patterns = { level = "allow", priority = 1 }
|
||||||
|
similar_names = { level = "allow", priority = 1 }
|
||||||
|
78
Cranky.toml
78
Cranky.toml
@ -1,78 +0,0 @@
|
|||||||
# https://github.com/ericseppanen/cargo-cranky
|
|
||||||
# cargo install cargo-cranky && cargo cranky
|
|
||||||
|
|
||||||
deny = [
|
|
||||||
"clippy::all",
|
|
||||||
"clippy::perf",
|
|
||||||
"clippy::correctness",
|
|
||||||
"clippy::complexity",
|
|
||||||
"clippy::style",
|
|
||||||
"clippy::pedantic",
|
|
||||||
# selection from clippy::restriction (see https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=restriction)
|
|
||||||
"clippy::as_underscore",
|
|
||||||
"clippy::assertions_on_result_states",
|
|
||||||
"clippy::clone_on_ref_ptr",
|
|
||||||
"clippy::create_dir",
|
|
||||||
"clippy::dbg_macro",
|
|
||||||
"clippy::decimal_literal_representation",
|
|
||||||
"clippy::default_numeric_fallback",
|
|
||||||
"clippy::empty_drop",
|
|
||||||
"clippy::empty_structs_with_brackets",
|
|
||||||
"clippy::exit",
|
|
||||||
"clippy::filetype_is_file",
|
|
||||||
"clippy::float_cmp_const",
|
|
||||||
"clippy::fn_to_numeric_cast_any",
|
|
||||||
"clippy::format_push_string",
|
|
||||||
"clippy::get_unwrap",
|
|
||||||
"clippy::integer_division",
|
|
||||||
"clippy::lossy_float_literal",
|
|
||||||
"clippy::mem_forget",
|
|
||||||
"clippy::mixed_read_write_in_expression",
|
|
||||||
"clippy::mutex_atomic",
|
|
||||||
"clippy::needless_raw_strings",
|
|
||||||
"clippy::non_ascii_literal",
|
|
||||||
"clippy::panic",
|
|
||||||
"clippy::print_stderr",
|
|
||||||
"clippy::pub_without_shorthand",
|
|
||||||
"clippy::rc_buffer",
|
|
||||||
"clippy::rc_mutex",
|
|
||||||
"clippy::redundant_type_annotations",
|
|
||||||
"clippy::ref_patterns",
|
|
||||||
"clippy::rest_pat_in_fully_bound_structs",
|
|
||||||
"clippy::same_name_method",
|
|
||||||
"clippy::string_add",
|
|
||||||
"clippy::string_lit_chars_any",
|
|
||||||
"clippy::string_slice",
|
|
||||||
"clippy::string_to_string",
|
|
||||||
"clippy::suspicious_xor_used_as_pow",
|
|
||||||
"clippy::todo",
|
|
||||||
"clippy::try_err",
|
|
||||||
"clippy::unimplemented",
|
|
||||||
"clippy::unnecessary_self_imports",
|
|
||||||
"clippy::unneeded_field_pattern",
|
|
||||||
"clippy::unseparated_literal_suffix",
|
|
||||||
"clippy::if_then_some_else_none",
|
|
||||||
"clippy::use_debug",
|
|
||||||
#TODO:
|
|
||||||
# "clippy::partial_pub_fields"
|
|
||||||
# "clippy::print_stdout"
|
|
||||||
# "clippy::unwrap_used"
|
|
||||||
# "clippy::unwrap_in_result"
|
|
||||||
]
|
|
||||||
|
|
||||||
allow = [
|
|
||||||
# reason = "noise and or false-positives"
|
|
||||||
"clippy::missing_errors_doc",
|
|
||||||
"clippy::used_underscore_binding",
|
|
||||||
"clippy::must_use_candidate",
|
|
||||||
"clippy::module_name_repetitions",
|
|
||||||
"clippy::missing_panics_doc",
|
|
||||||
"clippy::too_many_lines",
|
|
||||||
"clippy::implicit_hasher",
|
|
||||||
"clippy::if_not_else",
|
|
||||||
"clippy::return_self_not_must_use",
|
|
||||||
"clippy::inconsistent_struct_constructor",
|
|
||||||
"clippy::match_wildcard_for_single_variants",
|
|
||||||
"clippy::unnested_or_patterns",
|
|
||||||
"clippy::similar_names",
|
|
||||||
]
|
|
@ -16,3 +16,6 @@ dialoguer = "0.11.0"
|
|||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
git2 = { version = "0.18.1", features = ["vendored-openssl", "vendored-libgit2"] }
|
git2 = { version = "0.18.1", features = ["vendored-openssl", "vendored-libgit2"] }
|
||||||
gitbutler = { path = "../tauri" }
|
gitbutler = { path = "../tauri" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
@ -84,3 +84,6 @@ devtools = ["tauri/devtools"]
|
|||||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||||
# DO NOT remove this
|
# DO NOT remove this
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#![forbid(unsafe_code)]
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use tauri::{generate_context, Manager};
|
use tauri::{generate_context, Manager};
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ use crate::git;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("branch name is invalid: {0}")]
|
#[error("branch name is invalid: {0}")]
|
||||||
InvalidName(String),
|
InvalidName(String),
|
||||||
|
#[error("reference is not a tag: {0}")]
|
||||||
|
NotTag(String),
|
||||||
#[error("branch is not local: {0}")]
|
#[error("branch is not local: {0}")]
|
||||||
NotLocal(String),
|
NotLocal(String),
|
||||||
#[error("branch is not remote: {0}")]
|
#[error("branch is not remote: {0}")]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod error;
|
mod error;
|
||||||
mod local;
|
mod local;
|
||||||
mod remote;
|
mod remote;
|
||||||
|
mod tag;
|
||||||
mod r#virtual;
|
mod r#virtual;
|
||||||
|
|
||||||
use std::{fmt, str::FromStr};
|
use std::{fmt, str::FromStr};
|
||||||
@ -11,12 +12,15 @@ pub use error::Error;
|
|||||||
pub use local::Refname as LocalRefname;
|
pub use local::Refname as LocalRefname;
|
||||||
pub use r#virtual::Refname as VirtualRefname;
|
pub use r#virtual::Refname as VirtualRefname;
|
||||||
pub use remote::Refname as RemoteRefname;
|
pub use remote::Refname as RemoteRefname;
|
||||||
|
pub use tag::Refname as TagRefname;
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Refname {
|
pub enum Refname {
|
||||||
HEAD,
|
HEAD,
|
||||||
|
STASH,
|
||||||
|
Tag(TagRefname),
|
||||||
Remote(RemoteRefname),
|
Remote(RemoteRefname),
|
||||||
Local(LocalRefname),
|
Local(LocalRefname),
|
||||||
Virtual(VirtualRefname),
|
Virtual(VirtualRefname),
|
||||||
@ -59,12 +63,12 @@ impl From<&LocalRefname> for Refname {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Refname {
|
impl Refname {
|
||||||
pub fn branch(&self) -> &str {
|
pub fn branch(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::HEAD => "HEAD",
|
Self::HEAD | Self::Tag(_) | Self::STASH => None,
|
||||||
Self::Remote(remote) => remote.branch(),
|
Self::Remote(remote) => Some(remote.branch()),
|
||||||
Self::Local(local) => local.branch(),
|
Self::Local(local) => Some(local.branch()),
|
||||||
Self::Virtual(r#virtual) => r#virtual.branch(),
|
Self::Virtual(r#virtual) => Some(r#virtual.branch()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,20 +77,14 @@ impl FromStr for Refname {
|
|||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
if value == "HEAD" {
|
match value {
|
||||||
Ok(Self::HEAD)
|
"HEAD" => Ok(Self::HEAD),
|
||||||
} else if value.starts_with("refs") {
|
"refs/stash" => Ok(Self::STASH),
|
||||||
if value.starts_with("refs/remotes/") {
|
value if value.starts_with("refs/tags/") => Ok(Self::Tag(value.parse()?)),
|
||||||
Ok(Self::Remote(value.parse()?))
|
value if value.starts_with("refs/remotes/") => Ok(Self::Remote(value.parse()?)),
|
||||||
} else if value.starts_with("refs/heads/") {
|
value if value.starts_with("refs/heads/") => Ok(Self::Local(value.parse()?)),
|
||||||
Ok(Self::Local(value.parse()?))
|
value if value.starts_with("refs/gitbutler/") => Ok(Self::Virtual(value.parse()?)),
|
||||||
} else if value.starts_with("refs/gitbutler/") {
|
_ => Err(Error::InvalidName(value.to_string())),
|
||||||
Ok(Self::Virtual(value.parse()?))
|
|
||||||
} else {
|
|
||||||
Err(Error::InvalidName(value.to_string()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Self::Local(value.parse()?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,6 +105,8 @@ impl fmt::Display for Refname {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::HEAD => write!(f, "HEAD"),
|
Self::HEAD => write!(f, "HEAD"),
|
||||||
|
Self::STASH => write!(f, "refs/stash"),
|
||||||
|
Self::Tag(tag) => tag.fmt(f),
|
||||||
Self::Remote(remote) => remote.fmt(f),
|
Self::Remote(remote) => remote.fmt(f),
|
||||||
Self::Local(local) => local.fmt(f),
|
Self::Local(local) => local.fmt(f),
|
||||||
Self::Virtual(r#virtual) => r#virtual.fmt(f),
|
Self::Virtual(r#virtual) => r#virtual.fmt(f),
|
||||||
@ -117,7 +117,9 @@ impl fmt::Display for Refname {
|
|||||||
impl Serialize for Refname {
|
impl Serialize for Refname {
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::STASH => serializer.serialize_str("refs/stash"),
|
||||||
Self::HEAD => serializer.serialize_str("HEAD"),
|
Self::HEAD => serializer.serialize_str("HEAD"),
|
||||||
|
Self::Tag(tag) => tag.serialize(serializer),
|
||||||
Self::Remote(remote) => remote.serialize(serializer),
|
Self::Remote(remote) => remote.serialize(serializer),
|
||||||
Self::Local(local) => local.serialize(serializer),
|
Self::Local(local) => local.serialize(serializer),
|
||||||
Self::Virtual(r#virtual) => r#virtual.serialize(serializer),
|
Self::Virtual(r#virtual) => r#virtual.serialize(serializer),
|
||||||
|
53
packages/tauri/src/git/reference/refname/tag.rs
Normal file
53
packages/tauri/src/git/reference/refname/tag.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::error::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Refname {
|
||||||
|
tag: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Refname {
|
||||||
|
pub fn tag(&self) -> &str {
|
||||||
|
&self.tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Refname {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Deserialize<'d> for Refname {
|
||||||
|
fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let name = String::deserialize(deserializer)?;
|
||||||
|
name.as_str().parse().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Refname {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "refs/tags/{}", self.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Refname {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
if !value.starts_with("refs/tags/") {
|
||||||
|
return Err(Error::NotTag(value.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tag) = value.strip_prefix("refs/tags/") {
|
||||||
|
Ok(Self {
|
||||||
|
tag: tag.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidName(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ use std::{fmt, str::FromStr};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{git, virtual_branches::Branch};
|
use crate::virtual_branches::Branch;
|
||||||
|
|
||||||
use super::error::Error;
|
use super::error::Error;
|
||||||
|
|
||||||
@ -62,16 +62,3 @@ impl FromStr for Refname {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&git::Branch<'_>> for Refname {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &git::Branch<'_>) -> std::result::Result<Self, Self::Error> {
|
|
||||||
let branch_name = String::from_utf8(value.refname_bytes().to_vec()).map_err(Error::Utf8)?;
|
|
||||||
if value.is_remote() {
|
|
||||||
Err(Error::NotLocal(branch_name))
|
|
||||||
} else {
|
|
||||||
branch_name.parse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -330,6 +330,8 @@ impl Repository {
|
|||||||
.find_branch(
|
.find_branch(
|
||||||
&match name {
|
&match name {
|
||||||
Refname::HEAD => "HEAD".to_string(),
|
Refname::HEAD => "HEAD".to_string(),
|
||||||
|
Refname::STASH => "stash".to_string(),
|
||||||
|
Refname::Tag(tag) => tag.tag().to_string(),
|
||||||
Refname::Virtual(virtual_refname) => virtual_refname.branch().to_string(),
|
Refname::Virtual(virtual_refname) => virtual_refname.branch().to_string(),
|
||||||
Refname::Local(local) => local.branch().to_string(),
|
Refname::Local(local) => local.branch().to_string(),
|
||||||
Refname::Remote(remote) => {
|
Refname::Remote(remote) => {
|
||||||
@ -337,9 +339,11 @@ impl Repository {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
match name {
|
match name {
|
||||||
Refname::HEAD | Refname::Virtual(_) | Refname::Local(_) => {
|
Refname::STASH
|
||||||
git2::BranchType::Local
|
| Refname::HEAD
|
||||||
}
|
| Refname::Virtual(_)
|
||||||
|
| Refname::Local(_)
|
||||||
|
| Refname::Tag(_) => git2::BranchType::Local,
|
||||||
Refname::Remote(_) => git2::BranchType::Remote,
|
Refname::Remote(_) => git2::BranchType::Remote,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#![forbid(unsafe_code)]
|
|
||||||
|
|
||||||
pub mod analytics;
|
pub mod analytics;
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
|
@ -580,6 +580,12 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
applied: Option<bool>,
|
applied: Option<bool>,
|
||||||
user: Option<&users::User>,
|
user: Option<&users::User>,
|
||||||
) -> Result<branch::Branch, CreateVirtualBranchFromBranchError> {
|
) -> Result<branch::Branch, CreateVirtualBranchFromBranchError> {
|
||||||
|
if !matches!(upstream, git::Refname::Local(_) | git::Refname::Remote(_)) {
|
||||||
|
return Err(errors::CreateVirtualBranchFromBranchError::BranchNotFound(
|
||||||
|
upstream.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let current_session = gb_repository
|
let current_session = gb_repository
|
||||||
.get_or_create_current_session()
|
.get_or_create_current_session()
|
||||||
.context("failed to get or create current session")?;
|
.context("failed to get or create current session")?;
|
||||||
@ -623,8 +629,11 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
|
|
||||||
// only set upstream if it's not the default target
|
// only set upstream if it's not the default target
|
||||||
let upstream_branch = match upstream {
|
let upstream_branch = match upstream {
|
||||||
git::Refname::Virtual(_) | git::Refname::HEAD => {
|
git::Refname::STASH
|
||||||
// we don't support creating virtual branches from virtual branches
|
| git::Refname::Virtual(_)
|
||||||
|
| git::Refname::HEAD
|
||||||
|
| git::Refname::Tag(_) => {
|
||||||
|
// we only support local or remote branches
|
||||||
return Err(errors::CreateVirtualBranchFromBranchError::BranchNotFound(
|
return Err(errors::CreateVirtualBranchFromBranchError::BranchNotFound(
|
||||||
upstream.clone(),
|
upstream.clone(),
|
||||||
));
|
));
|
||||||
@ -639,7 +648,10 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
|
|
||||||
let mut branch = branch::Branch {
|
let mut branch = branch::Branch {
|
||||||
id: BranchId::generate(),
|
id: BranchId::generate(),
|
||||||
name: upstream.branch().to_string(),
|
name: upstream
|
||||||
|
.branch()
|
||||||
|
.expect("always a branch reference")
|
||||||
|
.to_string(),
|
||||||
notes: String::new(),
|
notes: String::new(),
|
||||||
applied: applied.unwrap_or(false),
|
applied: applied.unwrap_or(false),
|
||||||
upstream: upstream_branch,
|
upstream: upstream_branch,
|
||||||
|
@ -66,7 +66,7 @@ pub fn list_remote_branches(
|
|||||||
.context("failed to convert branches")?
|
.context("failed to convert branches")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|branch| branch.name.branch() != default_target.branch.branch())
|
.filter(|branch| branch.name.branch() != Some(default_target.branch.branch()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(remote_branches)
|
Ok(remote_branches)
|
||||||
|
@ -5,7 +5,7 @@ use tauri::AppHandle;
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gb_repository,
|
gb_repository, git,
|
||||||
paths::DataDir,
|
paths::DataDir,
|
||||||
project_repository,
|
project_repository,
|
||||||
projects::{self, CodePushState, ProjectId},
|
projects::{self, CodePushState, ProjectId},
|
||||||
@ -144,10 +144,16 @@ impl HandlerInner {
|
|||||||
Err(err) => return Err(err).context("failed to push"),
|
Err(err) => return Err(err).context("failed to push"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let refs = gb_refs(&project_repository)?;
|
let refnames = gb_refs(&project_repository)?;
|
||||||
|
|
||||||
let all_refs = refs
|
let all_refs = refnames
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|r| {
|
||||||
|
matches!(
|
||||||
|
r,
|
||||||
|
git::Refname::Remote(_) | git::Refname::Virtual(_) | git::Refname::Local(_)
|
||||||
|
)
|
||||||
|
})
|
||||||
.map(|r| format!("+{}:{}", r, r))
|
.map(|r| format!("+{}:{}", r, r))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -169,11 +175,13 @@ impl HandlerInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gb_refs(project_repository: &project_repository::Repository) -> anyhow::Result<Vec<String>> {
|
fn gb_refs(
|
||||||
|
project_repository: &project_repository::Repository,
|
||||||
|
) -> anyhow::Result<Vec<git::Refname>> {
|
||||||
Ok(project_repository
|
Ok(project_repository
|
||||||
.git_repository
|
.git_repository
|
||||||
.references_glob("refs/*")?
|
.references_glob("refs/*")?
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter_map(|r| r.name().map(|name| name.to_string()))
|
.filter_map(|r| r.name())
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,13 @@ impl TestProject {
|
|||||||
|
|
||||||
/// works like if we'd open and merge a PR on github. does not update local.
|
/// works like if we'd open and merge a PR on github. does not update local.
|
||||||
pub fn merge(&self, branch_name: &git::Refname) {
|
pub fn merge(&self, branch_name: &git::Refname) {
|
||||||
let branch_name: git::Refname = format!("refs/heads/{}", branch_name.branch())
|
let branch_name: git::Refname = match branch_name {
|
||||||
.parse()
|
git::Refname::Local(local) => format!("refs/heads/{}", local.branch()).parse().unwrap(),
|
||||||
.unwrap();
|
git::Refname::Remote(remote) => {
|
||||||
|
format!("refs/heads/{}", remote.branch()).parse().unwrap()
|
||||||
|
}
|
||||||
|
_ => "INVALID".parse().unwrap(), // todo
|
||||||
|
};
|
||||||
let branch = self.remote_repository.find_branch(&branch_name).unwrap();
|
let branch = self.remote_repository.find_branch(&branch_name).unwrap();
|
||||||
let branch_commit = branch.peel_to_commit().unwrap();
|
let branch_commit = branch.peel_to_commit().unwrap();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { invoke } from '$lib/backend/ipc';
|
import { invoke } from '$lib/backend/ipc';
|
||||||
import type { Project as CloudProject } from '$lib/backend/cloud';
|
import type { Project as CloudProject } from '$lib/backend/cloud';
|
||||||
import { BehaviorSubject, catchError, from, switchMap } from 'rxjs';
|
import { BehaviorSubject, Observable, catchError, from, map, shareReplay, switchMap } from 'rxjs';
|
||||||
|
|
||||||
export type Key =
|
export type Key =
|
||||||
| 'generated'
|
| 'generated'
|
||||||
@ -22,6 +22,7 @@ export class ProjectService {
|
|||||||
error$ = new BehaviorSubject<any>(undefined);
|
error$ = new BehaviorSubject<any>(undefined);
|
||||||
projects$ = this.reload$.pipe(
|
projects$ = this.reload$.pipe(
|
||||||
switchMap(() => from(invoke<Project[]>('list_projects'))),
|
switchMap(() => from(invoke<Project[]>('list_projects'))),
|
||||||
|
shareReplay(1),
|
||||||
catchError((e) => {
|
catchError((e) => {
|
||||||
this.error$.next(e);
|
this.error$.next(e);
|
||||||
return [];
|
return [];
|
||||||
@ -31,20 +32,33 @@ export class ProjectService {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
getProject(projectId: string) {
|
getProject(projectId: string) {
|
||||||
return from(invoke<Project>('get_project', { id: projectId }));
|
return this.projects$.pipe(
|
||||||
|
map((projects) => {
|
||||||
|
const project = projects.find((p) => (p.id = projectId));
|
||||||
|
if (!project) {
|
||||||
|
// We need to abort loading of /[projectId]/ if no project exists, such
|
||||||
|
// that the type here is of Project rather than Project | undefined.
|
||||||
|
throw 'Project not found';
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProject(params: {
|
async updateProject(params: {
|
||||||
id: string;
|
id: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
api?: CloudProject & { sync: boolean };
|
api?: CloudProject & { sync: boolean };
|
||||||
preferred_key?: Key;
|
preferred_key?: Key;
|
||||||
}) {
|
}) {
|
||||||
return invoke<Project>('update_project', { project: params });
|
await invoke<Project>('update_project', { project: params });
|
||||||
|
this.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(path: string) {
|
async add(path: string) {
|
||||||
return await invoke<Project>('add_project', { path });
|
const project = await invoke<Project>('add_project', { path });
|
||||||
|
this.reload();
|
||||||
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProject(id: string) {
|
async deleteProject(id: string) {
|
||||||
|
@ -7,7 +7,7 @@ set -o pipefail
|
|||||||
function rust() {
|
function rust() {
|
||||||
cargo fmt --check
|
cargo fmt --check
|
||||||
cargo sort -c -w
|
cargo sort -c -w
|
||||||
cargo cranky --all-targets --all-features
|
cargo clippy --all-targets --all-features --tests
|
||||||
cargo test
|
cargo test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user