You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
6.9 KiB
238 lines
6.9 KiB
use crate::AppContext;
|
|
use crate::utils::empty_to_none::EmptyToNone;
|
|
use anyhow::bail;
|
|
use std::fmt::Display;
|
|
use std::fmt::Formatter;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct BranchName(pub String);
|
|
|
|
impl Display for BranchName {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(&self.0)
|
|
}
|
|
}
|
|
|
|
/// Best effort identify git branch by looking into the .git folder
|
|
pub fn get_branch_name(ctx: &AppContext) -> Option<BranchName> {
|
|
let path_to_head = ctx.root.join(".git").join("HEAD");
|
|
|
|
if !path_to_head.is_file() {
|
|
return None;
|
|
}
|
|
|
|
let contents = std::fs::read_to_string(path_to_head).ok()?;
|
|
|
|
if let Some(branch) = contents.strip_prefix("ref: refs/heads/") {
|
|
let b = branch.trim();
|
|
if b.is_empty() {
|
|
None
|
|
} else {
|
|
Some(BranchName(branch.trim().to_owned()))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
impl BranchName {
|
|
/// Extract a value from a branch name using a regex given as string.
|
|
///
|
|
/// pat_s - the regex pattern
|
|
/// regex_conf_name - name of the conf field the regex came from, for error messages
|
|
fn parse_using_regex(
|
|
&self,
|
|
template: &str,
|
|
regex_conf_name: &str,
|
|
) -> anyhow::Result<Option<String>> {
|
|
let Some(pat_s) = as_regex_pattern(template) else {
|
|
bail!(
|
|
"Config field \"{regex_conf_name}\" must contain a regex (encased in slashes). Found: {template}"
|
|
);
|
|
};
|
|
|
|
let pat = match regex::Regex::new(pat_s) {
|
|
Ok(pat) => pat,
|
|
Err(e) => {
|
|
bail!("Invalid regex in \"{regex_conf_name}\": {pat_s}\nError: {e}");
|
|
}
|
|
};
|
|
|
|
let num_captures = pat.captures_len();
|
|
if num_captures != 2 {
|
|
// capture "0" is the whole matched string
|
|
bail!(
|
|
"The pattern \"{regex_conf_name}\" is not applicable: {pat_s}\nThere must be exactly one capturing group. Found {}",
|
|
num_captures - 1
|
|
);
|
|
}
|
|
|
|
let Some(matches) = pat.captures(&self.0) else {
|
|
return Ok(None);
|
|
};
|
|
let Some(amatch) = matches.get(1) else {
|
|
return Ok(None);
|
|
};
|
|
Ok(Some(amatch.as_str().to_owned()))
|
|
}
|
|
|
|
/// Parse version from this branch name.
|
|
///
|
|
/// Aborts if the configured regex pattern is invalid.
|
|
pub fn parse_version(&self, ctx: &AppContext) -> anyhow::Result<Option<String>> {
|
|
let Some(pat) = ctx.config.branch_version_pattern.as_ref().empty_to_none() else {
|
|
return Ok(None);
|
|
};
|
|
self.parse_using_regex(pat, "branch_version_pattern")
|
|
}
|
|
|
|
/// Parse issue number from this branch name.
|
|
///
|
|
/// Aborts if the configured regex pattern is invalid.
|
|
pub fn parse_issue(&self, ctx: &AppContext) -> anyhow::Result<Option<String>> {
|
|
let Some(pat) = ctx.config.branch_issue_pattern.as_ref().empty_to_none() else {
|
|
return Ok(None);
|
|
};
|
|
self.parse_using_regex(pat, "branch_issue_pattern")
|
|
}
|
|
|
|
/// Try to detect a release channel from this branch name (e.g. stable, EAP)
|
|
pub fn parse_channel(&self, ctx: &AppContext) -> anyhow::Result<Option<String>> {
|
|
for (channel_id, template) in &ctx.config.channels {
|
|
if template.is_empty() {
|
|
// Channel only for manual choosing
|
|
continue;
|
|
}
|
|
if let Some(pat_s) = as_regex_pattern(template) {
|
|
let pat = match regex::Regex::new(pat_s) {
|
|
Ok(pat) => pat,
|
|
Err(e) => {
|
|
bail!("Invalid regex for channel \"{channel_id}\": {template}\nError: {e}");
|
|
}
|
|
};
|
|
|
|
if pat.is_match(&self.0) {
|
|
return Ok(Some(channel_id.to_owned()));
|
|
}
|
|
} else {
|
|
// No regex - match it verbatim
|
|
if &self.0 == template {
|
|
return Ok(Some(channel_id.to_owned()));
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
/// If the string is encased in slashes, return the inner part. Otherwise, return None.
|
|
fn as_regex_pattern(input: &str) -> Option<&str> {
|
|
input.strip_prefix('/')?.strip_suffix('/')
|
|
}
|
|
|
|
pub trait BranchOpt {
|
|
fn as_str_or_default(&self) -> &str;
|
|
}
|
|
|
|
impl BranchOpt for Option<BranchName> {
|
|
fn as_str_or_default(&self) -> &str {
|
|
self.as_ref().map(|b| b.0.as_str()).unwrap_or_default()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use std::path::PathBuf;
|
|
|
|
#[test]
|
|
fn test_as_regex_pattern() {
|
|
assert_eq!(as_regex_pattern("foo"), None);
|
|
assert_eq!(as_regex_pattern("/foo"), None);
|
|
assert_eq!(as_regex_pattern("foo/"), None);
|
|
assert_eq!(as_regex_pattern("/foo/"), Some("foo"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_version() {
|
|
let ctx = AppContext {
|
|
binary_name: "cl".to_string(),
|
|
config: Default::default(),
|
|
root: PathBuf::from("/tmp/"), // will not be used
|
|
};
|
|
|
|
assert_eq!(
|
|
BranchName("rel/3.14".to_string())
|
|
.parse_version(&ctx)
|
|
.unwrap(),
|
|
Some("3.14".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
BranchName("rel/foo".to_string())
|
|
.parse_version(&ctx)
|
|
.unwrap(),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_issue() {
|
|
let ctx = AppContext {
|
|
binary_name: "cl".to_string(),
|
|
config: Default::default(),
|
|
root: PathBuf::from("/tmp/"), // will not be used
|
|
};
|
|
|
|
assert_eq!(
|
|
BranchName("1234-bober-kurwa".to_string())
|
|
.parse_issue(&ctx)
|
|
.unwrap(),
|
|
Some("1234".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
BranchName("SW-778-jakie-bydłe-jebane".to_string())
|
|
.parse_issue(&ctx)
|
|
.unwrap(),
|
|
Some("SW-778".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
BranchName("nie-spierdalaj-mordo".to_string())
|
|
.parse_issue(&ctx)
|
|
.unwrap(),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_channel() {
|
|
let ctx = AppContext {
|
|
binary_name: "cl".to_string(),
|
|
config: Default::default(),
|
|
root: PathBuf::from("/tmp/"), // will not be used
|
|
};
|
|
|
|
assert_eq!(
|
|
BranchName("main".to_string()).parse_channel(&ctx).unwrap(),
|
|
Some("default".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
BranchName("master".to_string())
|
|
.parse_channel(&ctx)
|
|
.unwrap(),
|
|
Some("default".to_string())
|
|
);
|
|
|
|
assert_eq!(
|
|
BranchName("my-cool-feature".to_string())
|
|
.parse_version(&ctx)
|
|
.unwrap(),
|
|
None
|
|
);
|
|
}
|
|
}
|
|
|