templater: add types for local/remote ref names

Both local and remote refs are backed by the same value type since we'll need
some kind of runtime abstraction to represent "branches" keyword (which is a
list of local + remote branches.) It's tedious to implement separate
local/remote/both ref types.

The "unsynced" flag is inverted just because the positive term is slightly
easier to document.
This commit is contained in:
Yuya Nishihara 2023-10-25 15:36:22 +09:00
parent f7f08cbe77
commit 2b33a97c35
2 changed files with 111 additions and 1 deletions

View file

@ -35,7 +35,7 @@ use crate::template_parser::{
self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult, self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult,
}; };
use crate::templater::{ use crate::templater::{
IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty, self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty,
TemplatePropertyFn, TemplatePropertyFn,
}; };
use crate::text_util; use crate::text_util;
@ -79,6 +79,18 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo, '_> {
|item| self.wrap_commit(item), |item| self.wrap_commit(item),
) )
} }
CommitTemplatePropertyKind::RefName(property) => {
build_ref_name_method(self, build_ctx, property, function)
}
CommitTemplatePropertyKind::RefNameList(property) => {
template_builder::build_formattable_list_method(
self,
build_ctx,
property,
function,
|item| self.wrap_ref_name(item),
)
}
CommitTemplatePropertyKind::CommitOrChangeId(property) => { CommitTemplatePropertyKind::CommitOrChangeId(property) => {
build_commit_or_change_id_method(self, build_ctx, property, function) build_commit_or_change_id_method(self, build_ctx, property, function)
} }
@ -106,6 +118,21 @@ impl<'repo> CommitTemplateLanguage<'repo, '_> {
CommitTemplatePropertyKind::CommitList(Box::new(property)) CommitTemplatePropertyKind::CommitList(Box::new(property))
} }
fn wrap_ref_name(
&self,
property: impl TemplateProperty<Commit, Output = RefName> + 'repo,
) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::RefName(Box::new(property))
}
#[allow(unused)] // TODO
fn wrap_ref_name_list(
&self,
property: impl TemplateProperty<Commit, Output = Vec<RefName>> + 'repo,
) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::RefNameList(Box::new(property))
}
fn wrap_commit_or_change_id( fn wrap_commit_or_change_id(
&self, &self,
property: impl TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo, property: impl TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo,
@ -125,6 +152,8 @@ enum CommitTemplatePropertyKind<'repo> {
Core(CoreTemplatePropertyKind<'repo, Commit>), Core(CoreTemplatePropertyKind<'repo, Commit>),
Commit(Box<dyn TemplateProperty<Commit, Output = Commit> + 'repo>), Commit(Box<dyn TemplateProperty<Commit, Output = Commit> + 'repo>),
CommitList(Box<dyn TemplateProperty<Commit, Output = Vec<Commit>> + 'repo>), CommitList(Box<dyn TemplateProperty<Commit, Output = Vec<Commit>> + 'repo>),
RefName(Box<dyn TemplateProperty<Commit, Output = RefName> + 'repo>),
RefNameList(Box<dyn TemplateProperty<Commit, Output = Vec<RefName>> + 'repo>),
CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo>), CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo>),
ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>), ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>),
} }
@ -137,6 +166,10 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'
CommitTemplatePropertyKind::CommitList(property) => { CommitTemplatePropertyKind::CommitList(property) => {
Some(Box::new(TemplateFunction::new(property, |l| !l.is_empty()))) Some(Box::new(TemplateFunction::new(property, |l| !l.is_empty())))
} }
CommitTemplatePropertyKind::RefName(_) => None,
CommitTemplatePropertyKind::RefNameList(property) => {
Some(Box::new(TemplateFunction::new(property, |l| !l.is_empty())))
}
CommitTemplatePropertyKind::CommitOrChangeId(_) => None, CommitTemplatePropertyKind::CommitOrChangeId(_) => None,
CommitTemplatePropertyKind::ShortestIdPrefix(_) => None, CommitTemplatePropertyKind::ShortestIdPrefix(_) => None,
} }
@ -166,6 +199,8 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'
CommitTemplatePropertyKind::Core(property) => property.try_into_template(), CommitTemplatePropertyKind::Core(property) => property.try_into_template(),
CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::Commit(_) => None,
CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::CommitList(_) => None,
CommitTemplatePropertyKind::RefName(property) => Some(property.into_template()),
CommitTemplatePropertyKind::RefNameList(property) => Some(property.into_template()),
CommitTemplatePropertyKind::CommitOrChangeId(property) => { CommitTemplatePropertyKind::CommitOrChangeId(property) => {
Some(property.into_template()) Some(property.into_template())
} }
@ -336,6 +371,74 @@ fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> String {
names.join(" ") names.join(" ")
} }
/// Branch or tag name with metadata.
#[derive(Clone, Debug, Eq, PartialEq)]
struct RefName {
/// Local name.
name: String,
/// Remote name if this is a remote or Git-tracking ref.
remote: Option<String>,
/// Ref target has conflicts.
conflict: bool,
/// Local ref is synchronized with all tracking remotes.
synced: bool,
}
impl RefName {
fn is_local(&self) -> bool {
self.remote.is_none()
}
}
impl Template<()> for RefName {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
write!(formatter.labeled("name"), "{}", self.name)?;
if let Some(remote) = &self.remote {
write!(formatter, "@")?;
write!(formatter.labeled("remote"), "{remote}")?;
}
// Don't show both conflict and unsynced sigils as conflicted ref wouldn't
// be pushed.
if self.conflict {
write!(formatter, "??")?;
} else if self.is_local() && !self.synced {
write!(formatter, "*")?;
}
Ok(())
}
}
impl Template<()> for Vec<RefName> {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
templater::format_joined(&(), formatter, self, " ")
}
}
fn build_ref_name_method<'repo>(
language: &CommitTemplateLanguage<'repo, '_>,
_build_ctx: &BuildContext<CommitTemplatePropertyKind<'repo>>,
self_property: impl TemplateProperty<Commit, Output = RefName> + 'repo,
function: &FunctionCallNode,
) -> TemplateParseResult<CommitTemplatePropertyKind<'repo>> {
let property = match function.name {
"name" => {
template_parser::expect_no_arguments(function)?;
language.wrap_string(TemplateFunction::new(self_property, |ref_name| {
ref_name.name
}))
}
"remote" => {
template_parser::expect_no_arguments(function)?;
language.wrap_string(TemplateFunction::new(self_property, |ref_name| {
ref_name.remote.unwrap_or_default()
}))
}
// TODO: expose conflict, synced, remote.is_some()
_ => return Err(TemplateParseError::no_such_method("RefName", function)),
};
Ok(property)
}
/// Cache for reverse lookup refs. /// Cache for reverse lookup refs.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct RefNamesIndex { struct RefNamesIndex {

View file

@ -115,6 +115,13 @@ The following methods are defined.
* `.short([len: Integer]) -> String` * `.short([len: Integer]) -> String`
### RefName type
The following methods are defined.
* `.name() -> String`: Local branch or tag name.
* `.remote() -> String`: Remote name or empty if this is a local ref.
### ShortestIdPrefix type ### ShortestIdPrefix type
The following methods are defined. The following methods are defined.