Use gix for branch normalization

This commit is contained in:
Sebastian Thiel 2024-08-09 20:46:07 +02:00
parent 0d1ac25ef4
commit 107eea6743
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
4 changed files with 47 additions and 28 deletions

1
Cargo.lock generated
View File

@ -2333,7 +2333,6 @@ dependencies = [
"git2",
"gitbutler-tagged-string",
"gix",
"regex",
"serde",
"thiserror",
]

View File

@ -6,7 +6,6 @@ authors = ["GitButler <gitbutler@gitbutler.com>"]
publish = false
[dependencies]
regex = "1.10"
anyhow.workspace = true
git2.workspace = true
gix.workspace = true

View File

@ -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<String> {
// 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)]

View File

@ -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"),
("a<b", "a-b"),
("a>b", "a-b"),
("a<b", "a<b"),
("a>b", "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"