diff --git a/Cargo.lock b/Cargo.lock index bea09a47c..4b37a4de8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2333,7 +2333,6 @@ dependencies = [ "git2", "gitbutler-tagged-string", "gix", - "regex", "serde", "thiserror", ] diff --git a/crates/gitbutler-reference/Cargo.toml b/crates/gitbutler-reference/Cargo.toml index 44ca4ba1f..26d68c8f4 100644 --- a/crates/gitbutler-reference/Cargo.toml +++ b/crates/gitbutler-reference/Cargo.toml @@ -6,7 +6,6 @@ authors = ["GitButler "] publish = false [dependencies] -regex = "1.10" anyhow.workspace = true git2.workspace = true gix.workspace = true diff --git a/crates/gitbutler-reference/src/lib.rs b/crates/gitbutler-reference/src/lib.rs index c44d9b2f6..a4de9ef79 100644 --- a/crates/gitbutler-reference/src/lib.rs +++ b/crates/gitbutler-reference/src/lib.rs @@ -3,27 +3,45 @@ mod refname; use anyhow::bail; use gitbutler_tagged_string::TaggedString; pub use refname::{LocalRefname, Refname, RemoteRefname, VirtualRefname}; -use regex::Regex; +// TODO(ST): return `BString`, probably take BString, as branch names don't have to be valid UTF8 pub fn normalize_branch_name(name: &str) -> anyhow::Result { - // Remove specific symbols - let exclude_pattern = Regex::new(r"[|\+^~<>\\:*]").unwrap(); - let mut result = exclude_pattern.replace_all(name, "-").to_string(); - - // Replace spaces with hyphens - let space_pattern = Regex::new(r"\s+").unwrap(); - result = space_pattern.replace_all(&result, "-").to_string(); - - // Remove leading and trailing hyphens and slashes and dots - let trim_pattern = Regex::new(r"^[-/\.]+|[-/\.]+$").unwrap(); - result = trim_pattern.replace_all(&result, "").to_string(); - - let refname = format!("refs/gitbutler/{result}"); - if gix::validate::reference::name(refname.as_str().into()).is_err() { - bail!("Could not turn {result:?} into a valid reference name") + let mut sanitized = gix::validate::reference::name_partial_or_sanitize(name.into()); + fn is_forbidden_in_trailer_or_leader(b: u8) -> bool { + b == b'-' || b == b'.' || b == b'/' + } + while let Some(last) = sanitized.last() { + if is_forbidden_in_trailer_or_leader(*last) { + sanitized.pop(); + } else { + break; + } + } + while let Some(first) = sanitized.first() { + if is_forbidden_in_trailer_or_leader(*first) { + sanitized.remove(0); + } else { + break; + } } - Ok(result) + let mut previous_is_hyphen = false; + sanitized.retain(|b| { + if *b == b'-' { + if previous_is_hyphen { + return false; + } + previous_is_hyphen = true; + } else { + previous_is_hyphen = false; + } + true + }); + + if sanitized.is_empty() { + bail!("Could not turn {name:?} into a valid reference name") + } + Ok(sanitized.to_string()) } #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/gitbutler-reference/tests/reference.rs b/crates/gitbutler-reference/tests/reference.rs index 624b2ec86..d3fd99218 100644 --- a/crates/gitbutler-reference/tests/reference.rs +++ b/crates/gitbutler-reference/tests/reference.rs @@ -5,11 +5,12 @@ mod normalize_branch_name { fn valid_substitutions() { for (input, expected) in [ ("a", "a"), - ("a+b", "a-b"), + ("a+b", "a+b"), ("a^b", "a-b"), + ("a^^^b", "a-b"), ("a~b", "a-b"), - ("ab", "a-b"), + ("ab", "a>b"), ("a\\b", "a-b"), ("a:b", "a-b"), ("a*b", "a-b"), @@ -24,7 +25,11 @@ mod normalize_branch_name { ("/-a-/", "a"), (".a.", "a"), ] { - assert_eq!(normalize_branch_name(input).expect("valid"), expected); + assert_eq!( + normalize_branch_name(input).expect("valid"), + expected, + "{input} -> {expected}" + ); } } @@ -32,17 +37,15 @@ mod normalize_branch_name { fn clear_error_on_failure() { assert_eq!( normalize_branch_name("-").unwrap_err().to_string(), - "Could not turn \"\" into a valid reference name" - ); - assert_eq!( - normalize_branch_name("#[test]").unwrap_err().to_string(), - "Could not turn \"#[test]\" into a valid reference name" + "Could not turn \"-\" into a valid reference name", + "show the original value, not the processed one to be familiar to the user" ); } #[test] fn complex_valid() -> anyhow::Result<()> { assert_eq!(normalize_branch_name("feature/branch")?, "feature/branch"); + assert_eq!(normalize_branch_name("#[test]")?, "#-test]"); assert_eq!(normalize_branch_name("foo#branch")?, "foo#branch"); assert_eq!(normalize_branch_name("foo!branch")?, "foo!branch"); let input = r#"Revert "GitButler Integration Commit"