commit_templater: extract runtime objects from templater.rs

This should reduce future merge conflicts around use statements.
This commit is contained in:
Yuya Nishihara 2023-02-18 17:42:43 +09:00
parent c396b4cecf
commit c5ddd14c13
2 changed files with 230 additions and 230 deletions

View file

@ -12,21 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use jujutsu_lib::backend::{Signature, Timestamp};
use std::cmp::max;
use std::io;
use itertools::Itertools as _;
use jujutsu_lib::backend::{ChangeId, CommitId, ObjectId as _, Signature, Timestamp};
use jujutsu_lib::commit::Commit;
use jujutsu_lib::hex_util::to_reverse_hex;
use jujutsu_lib::op_store::WorkspaceId;
use jujutsu_lib::repo::Repo;
use jujutsu_lib::rewrite;
use crate::cli_util;
use crate::formatter::Formatter;
use crate::template_parser::{
self, CoreTemplatePropertyKind, FunctionCallNode, IntoTemplateProperty, TemplateAliasesMap,
TemplateLanguage, TemplateParseError, TemplateParseResult,
};
use crate::templater::{
BranchProperty, CommitOrChangeId, FormattablePropertyTemplate, GitHeadProperty,
GitRefsProperty, PlainTextFormattedProperty, ShortestIdPrefix, TagProperty, Template,
TemplateProperty, TemplatePropertyFn, WorkingCopiesProperty,
FormattablePropertyTemplate, PlainTextFormattedProperty, Template, TemplateProperty,
TemplatePropertyFn,
};
struct CommitTemplateLanguage<'a, 'b> {
@ -201,6 +206,210 @@ fn build_commit_keyword<'a>(
Ok(property)
}
struct WorkingCopiesProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for WorkingCopiesProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let wc_commit_ids = self.repo.view().wc_commit_ids();
if wc_commit_ids.len() <= 1 {
return "".to_string();
}
let mut names = vec![];
for (workspace_id, wc_commit_id) in wc_commit_ids.iter().sorted() {
if wc_commit_id == context.id() {
names.push(format!("{}@", workspace_id.as_str()));
}
}
names.join(" ")
}
}
struct BranchProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for BranchProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (branch_name, branch_target) in self.repo.view().branches() {
let local_target = branch_target.local_target.as_ref();
if let Some(local_target) = local_target {
if local_target.has_add(context.id()) {
if local_target.is_conflict() {
names.push(format!("{branch_name}??"));
} else if branch_target
.remote_targets
.values()
.any(|remote_target| remote_target != local_target)
{
names.push(format!("{branch_name}*"));
} else {
names.push(branch_name.clone());
}
}
}
for (remote_name, remote_target) in &branch_target.remote_targets {
if Some(remote_target) != local_target && remote_target.has_add(context.id()) {
if remote_target.is_conflict() {
names.push(format!("{branch_name}@{remote_name}?"));
} else {
names.push(format!("{branch_name}@{remote_name}"));
}
}
}
}
names.join(" ")
}
}
struct TagProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for TagProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (tag_name, target) in self.repo.view().tags() {
if target.has_add(context.id()) {
if target.is_conflict() {
names.push(format!("{tag_name}?"));
} else {
names.push(tag_name.clone());
}
}
}
names.join(" ")
}
}
struct GitRefsProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for GitRefsProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
// TODO: We should keep a map from commit to ref names so we don't have to walk
// all refs here.
let mut names = vec![];
for (name, target) in self.repo.view().git_refs() {
if target.has_add(context.id()) {
if target.is_conflict() {
names.push(format!("{name}?"));
} else {
names.push(name.clone());
}
}
}
names.join(" ")
}
}
struct GitHeadProperty<'a> {
repo: &'a dyn Repo,
}
impl<'a> GitHeadProperty<'a> {
pub fn new(repo: &'a dyn Repo) -> Self {
Self { repo }
}
}
impl TemplateProperty<Commit> for GitHeadProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> String {
match self.repo.view().git_head() {
Some(ref_target) if ref_target.has_add(context.id()) => {
if ref_target.is_conflict() {
"HEAD@git?".to_string()
} else {
"HEAD@git".to_string()
}
}
_ => "".to_string(),
}
}
}
/// Type-erased `CommitId`/`ChangeId`.
#[derive(Clone)]
struct CommitOrChangeId<'a> {
repo: &'a dyn Repo,
id_bytes: Vec<u8>,
is_commit_id: bool,
}
impl<'a> CommitOrChangeId<'a> {
pub fn commit_id(repo: &'a dyn Repo, id: &CommitId) -> Self {
CommitOrChangeId {
repo,
id_bytes: id.to_bytes(),
is_commit_id: true,
}
}
pub fn change_id(repo: &'a dyn Repo, id: &ChangeId) -> Self {
CommitOrChangeId {
repo,
id_bytes: id.to_bytes(),
is_commit_id: false,
}
}
pub fn hex(&self) -> String {
if self.is_commit_id {
hex::encode(&self.id_bytes)
} else {
// TODO: We can avoid the unwrap() and make this more efficient by converting
// straight from bytes.
to_reverse_hex(&hex::encode(&self.id_bytes)).unwrap()
}
}
pub fn short(&self, total_len: usize) -> String {
let mut hex = self.hex();
hex.truncate(total_len);
hex
}
/// The length of the id printed will be the maximum of `total_len` and the
/// length of the shortest unique prefix
pub fn shortest(&self, total_len: usize) -> ShortestIdPrefix {
let mut hex = self.hex();
let prefix_len = if self.is_commit_id {
self.repo
.index()
.shortest_unique_commit_id_prefix_len(&CommitId::from_bytes(&self.id_bytes))
} else {
self.repo
.shortest_unique_change_id_prefix_len(&ChangeId::from_bytes(&self.id_bytes))
};
hex.truncate(max(prefix_len, total_len));
let rest = hex.split_off(prefix_len);
ShortestIdPrefix { prefix: hex, rest }
}
}
impl Template<()> for CommitOrChangeId<'_> {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(&self.hex())
}
fn has_content(&self, _: &()) -> bool {
!self.id_bytes.is_empty()
}
}
fn build_commit_or_change_id_method<'a>(
language: &CommitTemplateLanguage<'a, '_>,
self_property: impl TemplateProperty<Commit, Output = CommitOrChangeId<'a>> + 'a,
@ -247,6 +456,22 @@ fn build_commit_or_change_id_method<'a>(
Ok(property)
}
struct ShortestIdPrefix {
pub prefix: String,
pub rest: String,
}
impl Template<()> for ShortestIdPrefix {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.with_label("prefix", |fmt| fmt.write_str(&self.prefix))?;
formatter.with_label("rest", |fmt| fmt.write_str(&self.rest))
}
fn has_content(&self, _: &()) -> bool {
!self.prefix.is_empty() || !self.rest.is_empty()
}
}
fn build_shortest_id_prefix_method<'a>(
language: &CommitTemplateLanguage<'a, '_>,
self_property: impl TemplateProperty<Commit, Output = ShortestIdPrefix> + 'a,

View file

@ -12,14 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::cmp::max;
use std::io;
use itertools::Itertools;
use jujutsu_lib::backend::{ChangeId, CommitId, ObjectId, Signature, Timestamp};
use jujutsu_lib::commit::Commit;
use jujutsu_lib::hex_util::to_reverse_hex;
use jujutsu_lib::repo::Repo;
use jujutsu_lib::backend::{Signature, Timestamp};
use crate::formatter::{Formatter, PlainTextFormatter};
use crate::time_util;
@ -328,141 +323,6 @@ impl<C, T: Template<C>> TemplateProperty<C> for PlainTextFormattedProperty<T> {
}
}
pub struct WorkingCopiesProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for WorkingCopiesProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let wc_commit_ids = self.repo.view().wc_commit_ids();
if wc_commit_ids.len() <= 1 {
return "".to_string();
}
let mut names = vec![];
for (workspace_id, wc_commit_id) in wc_commit_ids.iter().sorted() {
if wc_commit_id == context.id() {
names.push(format!("{}@", workspace_id.as_str()));
}
}
names.join(" ")
}
}
pub struct BranchProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for BranchProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (branch_name, branch_target) in self.repo.view().branches() {
let local_target = branch_target.local_target.as_ref();
if let Some(local_target) = local_target {
if local_target.has_add(context.id()) {
if local_target.is_conflict() {
names.push(format!("{branch_name}??"));
} else if branch_target
.remote_targets
.values()
.any(|remote_target| remote_target != local_target)
{
names.push(format!("{branch_name}*"));
} else {
names.push(branch_name.clone());
}
}
}
for (remote_name, remote_target) in &branch_target.remote_targets {
if Some(remote_target) != local_target && remote_target.has_add(context.id()) {
if remote_target.is_conflict() {
names.push(format!("{branch_name}@{remote_name}?"));
} else {
names.push(format!("{branch_name}@{remote_name}"));
}
}
}
}
names.join(" ")
}
}
pub struct TagProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for TagProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (tag_name, target) in self.repo.view().tags() {
if target.has_add(context.id()) {
if target.is_conflict() {
names.push(format!("{tag_name}?"));
} else {
names.push(tag_name.clone());
}
}
}
names.join(" ")
}
}
pub struct GitRefsProperty<'a> {
pub repo: &'a dyn Repo,
}
impl TemplateProperty<Commit> for GitRefsProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
// TODO: We should keep a map from commit to ref names so we don't have to walk
// all refs here.
let mut names = vec![];
for (name, target) in self.repo.view().git_refs() {
if target.has_add(context.id()) {
if target.is_conflict() {
names.push(format!("{name}?"));
} else {
names.push(name.clone());
}
}
}
names.join(" ")
}
}
pub struct GitHeadProperty<'a> {
repo: &'a dyn Repo,
}
impl<'a> GitHeadProperty<'a> {
pub fn new(repo: &'a dyn Repo) -> Self {
Self { repo }
}
}
impl TemplateProperty<Commit> for GitHeadProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> String {
match self.repo.view().git_head() {
Some(ref_target) if ref_target.has_add(context.id()) => {
if ref_target.is_conflict() {
"HEAD@git?".to_string()
} else {
"HEAD@git".to_string()
}
}
_ => "".to_string(),
}
}
}
pub struct ConditionalTemplate<P, T, U> {
pub condition: P,
pub true_template: T,
@ -538,88 +398,3 @@ where
(self.function)(self.property.extract(context))
}
}
/// Type-erased `CommitId`/`ChangeId`.
#[derive(Clone)]
pub struct CommitOrChangeId<'a> {
repo: &'a dyn Repo,
id_bytes: Vec<u8>,
is_commit_id: bool,
}
impl<'a> CommitOrChangeId<'a> {
pub fn commit_id(repo: &'a dyn Repo, id: &CommitId) -> Self {
CommitOrChangeId {
repo,
id_bytes: id.to_bytes(),
is_commit_id: true,
}
}
pub fn change_id(repo: &'a dyn Repo, id: &ChangeId) -> Self {
CommitOrChangeId {
repo,
id_bytes: id.to_bytes(),
is_commit_id: false,
}
}
pub fn hex(&self) -> String {
if self.is_commit_id {
hex::encode(&self.id_bytes)
} else {
// TODO: We can avoid the unwrap() and make this more efficient by converting
// straight from bytes.
to_reverse_hex(&hex::encode(&self.id_bytes)).unwrap()
}
}
pub fn short(&self, total_len: usize) -> String {
let mut hex = self.hex();
hex.truncate(total_len);
hex
}
/// The length of the id printed will be the maximum of `total_len` and the
/// length of the shortest unique prefix
pub fn shortest(&self, total_len: usize) -> ShortestIdPrefix {
let mut hex = self.hex();
let prefix_len = if self.is_commit_id {
self.repo
.index()
.shortest_unique_commit_id_prefix_len(&CommitId::from_bytes(&self.id_bytes))
} else {
self.repo
.shortest_unique_change_id_prefix_len(&ChangeId::from_bytes(&self.id_bytes))
};
hex.truncate(max(prefix_len, total_len));
let rest = hex.split_off(prefix_len);
ShortestIdPrefix { prefix: hex, rest }
}
}
impl Template<()> for CommitOrChangeId<'_> {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(&self.hex())
}
fn has_content(&self, _: &()) -> bool {
!self.id_bytes.is_empty()
}
}
pub struct ShortestIdPrefix {
pub prefix: String,
pub rest: String,
}
impl Template<()> for ShortestIdPrefix {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.with_label("prefix", |fmt| fmt.write_str(&self.prefix))?;
formatter.with_label("rest", |fmt| fmt.write_str(&self.rest))
}
fn has_content(&self, _: &()) -> bool {
!self.prefix.is_empty() || !self.rest.is_empty()
}
}