1use std::any::Any;
18use std::cmp::Ordering;
19use std::cmp::max;
20use std::collections::HashMap;
21use std::fmt;
22use std::fmt::Display;
23use std::io;
24use std::rc::Rc;
25use std::sync::Arc;
26
27use bstr::BString;
28use futures::StreamExt as _;
29use futures::TryStreamExt as _;
30use futures::stream::BoxStream;
31use itertools::Itertools as _;
32use jj_lib::backend::BackendResult;
33use jj_lib::backend::ChangeId;
34use jj_lib::backend::CommitId;
35use jj_lib::backend::TreeValue;
36use jj_lib::commit::Commit;
37use jj_lib::conflicts;
38use jj_lib::conflicts::ConflictMarkerStyle;
39use jj_lib::copies::CopiesTreeDiffEntry;
40use jj_lib::copies::CopiesTreeDiffEntryPath;
41use jj_lib::copies::CopyRecords;
42use jj_lib::evolution::CommitEvolutionEntry;
43use jj_lib::extensions_map::ExtensionsMap;
44use jj_lib::fileset;
45use jj_lib::fileset::FilesetDiagnostics;
46use jj_lib::fileset::FilesetExpression;
47use jj_lib::id_prefix::IdPrefixContext;
48use jj_lib::id_prefix::IdPrefixIndex;
49use jj_lib::index::IndexResult;
50use jj_lib::matchers::Matcher;
51use jj_lib::merge::Diff;
52use jj_lib::merge::MergedTreeValue;
53use jj_lib::merged_tree::MergedTree;
54use jj_lib::object_id::ObjectId as _;
55use jj_lib::op_store::LocalRemoteRefTarget;
56use jj_lib::op_store::OperationId;
57use jj_lib::op_store::RefTarget;
58use jj_lib::op_store::RemoteRef;
59use jj_lib::ref_name::RefName;
60use jj_lib::ref_name::WorkspaceName;
61use jj_lib::ref_name::WorkspaceNameBuf;
62use jj_lib::repo::Repo;
63use jj_lib::repo::RepoLoader;
64use jj_lib::repo_path::RepoPathBuf;
65use jj_lib::repo_path::RepoPathUiConverter;
66use jj_lib::revset;
67use jj_lib::revset::Revset;
68use jj_lib::revset::RevsetContainingFn;
69use jj_lib::revset::RevsetDiagnostics;
70use jj_lib::revset::RevsetModifier;
71use jj_lib::revset::RevsetParseContext;
72use jj_lib::revset::UserRevsetExpression;
73use jj_lib::settings::UserSettings;
74use jj_lib::signing::SigStatus;
75use jj_lib::signing::SignError;
76use jj_lib::signing::SignResult;
77use jj_lib::signing::Verification;
78use jj_lib::store::Store;
79use jj_lib::trailer;
80use jj_lib::trailer::Trailer;
81use once_cell::unsync::OnceCell;
82use pollster::FutureExt as _;
83use serde::Serialize as _;
84
85use crate::diff_util;
86use crate::diff_util::DiffStatEntry;
87use crate::diff_util::DiffStats;
88use crate::formatter::Formatter;
89use crate::operation_templater;
90use crate::operation_templater::OperationTemplateBuildFnTable;
91use crate::operation_templater::OperationTemplateEnvironment;
92use crate::operation_templater::OperationTemplatePropertyKind;
93use crate::operation_templater::OperationTemplatePropertyVar;
94use crate::revset_util;
95use crate::template_builder;
96use crate::template_builder::BuildContext;
97use crate::template_builder::CoreTemplateBuildFnTable;
98use crate::template_builder::CoreTemplatePropertyKind;
99use crate::template_builder::CoreTemplatePropertyVar;
100use crate::template_builder::TemplateBuildMethodFnMap;
101use crate::template_builder::TemplateLanguage;
102use crate::template_builder::expect_stringify_expression;
103use crate::template_builder::merge_fn_map;
104use crate::template_parser;
105use crate::template_parser::ExpressionNode;
106use crate::template_parser::FunctionCallNode;
107use crate::template_parser::TemplateDiagnostics;
108use crate::template_parser::TemplateParseError;
109use crate::template_parser::TemplateParseResult;
110use crate::templater;
111use crate::templater::BoxedSerializeProperty;
112use crate::templater::BoxedTemplateProperty;
113use crate::templater::ListTemplate;
114use crate::templater::PlainTextFormattedProperty;
115use crate::templater::SizeHint;
116use crate::templater::Template;
117use crate::templater::TemplateFormatter;
118use crate::templater::TemplatePropertyError;
119use crate::templater::TemplatePropertyExt as _;
120
121pub trait CommitTemplateLanguageExtension {
122 fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>;
123
124 fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);
125}
126
127pub struct CommitTemplateLanguage<'repo> {
129 repo: &'repo dyn Repo,
130 path_converter: &'repo RepoPathUiConverter,
131 workspace_name: WorkspaceNameBuf,
132 revset_parse_context: RevsetParseContext<'repo>,
138 id_prefix_context: &'repo IdPrefixContext,
139 immutable_expression: Arc<UserRevsetExpression>,
140 conflict_marker_style: ConflictMarkerStyle,
141 build_fn_table: CommitTemplateBuildFnTable<'repo>,
142 keyword_cache: CommitKeywordCache<'repo>,
143 cache_extensions: ExtensionsMap,
144}
145
146impl<'repo> CommitTemplateLanguage<'repo> {
147 #[expect(clippy::too_many_arguments)]
150 pub fn new(
151 repo: &'repo dyn Repo,
152 path_converter: &'repo RepoPathUiConverter,
153 workspace_name: &WorkspaceName,
154 revset_parse_context: RevsetParseContext<'repo>,
155 id_prefix_context: &'repo IdPrefixContext,
156 immutable_expression: Arc<UserRevsetExpression>,
157 conflict_marker_style: ConflictMarkerStyle,
158 extensions: &[impl AsRef<dyn CommitTemplateLanguageExtension>],
159 ) -> Self {
160 let mut build_fn_table = CommitTemplateBuildFnTable::builtin();
161 let mut cache_extensions = ExtensionsMap::empty();
162
163 for extension in extensions {
164 build_fn_table.merge(extension.as_ref().build_fn_table());
165 extension
166 .as_ref()
167 .build_cache_extensions(&mut cache_extensions);
168 }
169
170 CommitTemplateLanguage {
171 repo,
172 path_converter,
173 workspace_name: workspace_name.to_owned(),
174 revset_parse_context,
175 id_prefix_context,
176 immutable_expression,
177 conflict_marker_style,
178 build_fn_table,
179 keyword_cache: CommitKeywordCache::default(),
180 cache_extensions,
181 }
182 }
183}
184
185impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
186 type Property = CommitTemplatePropertyKind<'repo>;
187
188 fn settings(&self) -> &UserSettings {
189 self.repo.base_repo().settings()
190 }
191
192 fn build_function(
193 &self,
194 diagnostics: &mut TemplateDiagnostics,
195 build_ctx: &BuildContext<Self::Property>,
196 function: &FunctionCallNode,
197 ) -> TemplateParseResult<Self::Property> {
198 let table = &self.build_fn_table.core;
199 table.build_function(self, diagnostics, build_ctx, function)
200 }
201
202 fn build_method(
203 &self,
204 diagnostics: &mut TemplateDiagnostics,
205 build_ctx: &BuildContext<Self::Property>,
206 property: Self::Property,
207 function: &FunctionCallNode,
208 ) -> TemplateParseResult<Self::Property> {
209 let type_name = property.type_name();
210 match property {
211 CommitTemplatePropertyKind::Core(property) => {
212 let table = &self.build_fn_table.core;
213 table.build_method(self, diagnostics, build_ctx, property, function)
214 }
215 CommitTemplatePropertyKind::Operation(property) => {
216 let table = &self.build_fn_table.operation;
217 table.build_method(self, diagnostics, build_ctx, property, function)
218 }
219 CommitTemplatePropertyKind::Commit(property) => {
220 let table = &self.build_fn_table.commit_methods;
221 let build = template_parser::lookup_method(type_name, table, function)?;
222 build(self, diagnostics, build_ctx, property, function)
223 }
224 CommitTemplatePropertyKind::CommitOpt(property) => {
225 let type_name = "Commit";
226 let table = &self.build_fn_table.commit_methods;
227 let build = template_parser::lookup_method(type_name, table, function)?;
228 let inner_property = property.try_unwrap(type_name).into_dyn();
229 build(self, diagnostics, build_ctx, inner_property, function)
230 }
231 CommitTemplatePropertyKind::CommitList(property) => {
232 let table = &self.build_fn_table.commit_list_methods;
233 let build = template_parser::lookup_method(type_name, table, function)?;
234 build(self, diagnostics, build_ctx, property, function)
235 }
236 CommitTemplatePropertyKind::CommitEvolutionEntry(property) => {
237 let table = &self.build_fn_table.commit_evolution_entry_methods;
238 let build = template_parser::lookup_method(type_name, table, function)?;
239 build(self, diagnostics, build_ctx, property, function)
240 }
241 CommitTemplatePropertyKind::CommitRef(property) => {
242 let table = &self.build_fn_table.commit_ref_methods;
243 let build = template_parser::lookup_method(type_name, table, function)?;
244 build(self, diagnostics, build_ctx, property, function)
245 }
246 CommitTemplatePropertyKind::CommitRefOpt(property) => {
247 let type_name = "CommitRef";
248 let table = &self.build_fn_table.commit_ref_methods;
249 let build = template_parser::lookup_method(type_name, table, function)?;
250 let inner_property = property.try_unwrap(type_name).into_dyn();
251 build(self, diagnostics, build_ctx, inner_property, function)
252 }
253 CommitTemplatePropertyKind::CommitRefList(property) => {
254 let table = &self.build_fn_table.commit_ref_list_methods;
255 let build = template_parser::lookup_method(type_name, table, function)?;
256 build(self, diagnostics, build_ctx, property, function)
257 }
258 CommitTemplatePropertyKind::WorkspaceRef(property) => {
259 let table = &self.build_fn_table.workspace_ref_methods;
260 let build = template_parser::lookup_method(type_name, table, function)?;
261 build(self, diagnostics, build_ctx, property, function)
262 }
263 CommitTemplatePropertyKind::WorkspaceRefOpt(property) => {
264 let type_name = "WorkspaceRef";
265 let table = &self.build_fn_table.workspace_ref_methods;
266 let build = template_parser::lookup_method(type_name, table, function)?;
267 let inner_property = property.try_unwrap(type_name).into_dyn();
268 build(self, diagnostics, build_ctx, inner_property, function)
269 }
270 CommitTemplatePropertyKind::WorkspaceRefList(property) => {
271 let table = &self.build_fn_table.workspace_ref_list_methods;
272 let build = template_parser::lookup_method(type_name, table, function)?;
273 build(self, diagnostics, build_ctx, property, function)
274 }
275 CommitTemplatePropertyKind::RefSymbol(property) => {
276 let table = &self.build_fn_table.core.string_methods;
277 let build = template_parser::lookup_method(type_name, table, function)?;
278 let inner_property = property.map(|RefSymbolBuf(s)| s).into_dyn();
279 build(self, diagnostics, build_ctx, inner_property, function)
280 }
281 CommitTemplatePropertyKind::RefSymbolOpt(property) => {
282 let type_name = "RefSymbol";
283 let table = &self.build_fn_table.core.string_methods;
284 let build = template_parser::lookup_method(type_name, table, function)?;
285 let inner_property = property
286 .try_unwrap(type_name)
287 .map(|RefSymbolBuf(s)| s)
288 .into_dyn();
289 build(self, diagnostics, build_ctx, inner_property, function)
290 }
291 CommitTemplatePropertyKind::RepoPath(property) => {
292 let table = &self.build_fn_table.repo_path_methods;
293 let build = template_parser::lookup_method(type_name, table, function)?;
294 build(self, diagnostics, build_ctx, property, function)
295 }
296 CommitTemplatePropertyKind::RepoPathOpt(property) => {
297 let type_name = "RepoPath";
298 let table = &self.build_fn_table.repo_path_methods;
299 let build = template_parser::lookup_method(type_name, table, function)?;
300 let inner_property = property.try_unwrap(type_name).into_dyn();
301 build(self, diagnostics, build_ctx, inner_property, function)
302 }
303 CommitTemplatePropertyKind::ChangeId(property) => {
304 let table = &self.build_fn_table.change_id_methods;
305 let build = template_parser::lookup_method(type_name, table, function)?;
306 build(self, diagnostics, build_ctx, property, function)
307 }
308 CommitTemplatePropertyKind::CommitId(property) => {
309 let table = &self.build_fn_table.commit_id_methods;
310 let build = template_parser::lookup_method(type_name, table, function)?;
311 build(self, diagnostics, build_ctx, property, function)
312 }
313 CommitTemplatePropertyKind::ShortestIdPrefix(property) => {
314 let table = &self.build_fn_table.shortest_id_prefix_methods;
315 let build = template_parser::lookup_method(type_name, table, function)?;
316 build(self, diagnostics, build_ctx, property, function)
317 }
318 CommitTemplatePropertyKind::TreeDiff(property) => {
319 let table = &self.build_fn_table.tree_diff_methods;
320 let build = template_parser::lookup_method(type_name, table, function)?;
321 build(self, diagnostics, build_ctx, property, function)
322 }
323 CommitTemplatePropertyKind::TreeDiffEntry(property) => {
324 let table = &self.build_fn_table.tree_diff_entry_methods;
325 let build = template_parser::lookup_method(type_name, table, function)?;
326 build(self, diagnostics, build_ctx, property, function)
327 }
328 CommitTemplatePropertyKind::TreeDiffEntryList(property) => {
329 let table = &self.build_fn_table.tree_diff_entry_list_methods;
330 let build = template_parser::lookup_method(type_name, table, function)?;
331 build(self, diagnostics, build_ctx, property, function)
332 }
333 CommitTemplatePropertyKind::TreeEntry(property) => {
334 let table = &self.build_fn_table.tree_entry_methods;
335 let build = template_parser::lookup_method(type_name, table, function)?;
336 build(self, diagnostics, build_ctx, property, function)
337 }
338 CommitTemplatePropertyKind::TreeEntryList(property) => {
339 let table = &self.build_fn_table.tree_entry_list_methods;
340 let build = template_parser::lookup_method(type_name, table, function)?;
341 build(self, diagnostics, build_ctx, property, function)
342 }
343 CommitTemplatePropertyKind::DiffStats(property) => {
344 let table = &self.build_fn_table.diff_stats_methods;
345 let build = template_parser::lookup_method(type_name, table, function)?;
346 let property = property.map(|formatted| formatted.stats).into_dyn();
349 build(self, diagnostics, build_ctx, property, function)
350 }
351 CommitTemplatePropertyKind::DiffStatEntry(property) => {
352 let table = &self.build_fn_table.diff_stat_entry_methods;
353 let build = template_parser::lookup_method(type_name, table, function)?;
354 build(self, diagnostics, build_ctx, property, function)
355 }
356 CommitTemplatePropertyKind::DiffStatEntryList(property) => {
357 let table = &self.build_fn_table.diff_stat_entry_list_methods;
358 let build = template_parser::lookup_method(type_name, table, function)?;
359 build(self, diagnostics, build_ctx, property, function)
360 }
361 CommitTemplatePropertyKind::CryptographicSignatureOpt(property) => {
362 let type_name = "CryptographicSignature";
363 let table = &self.build_fn_table.cryptographic_signature_methods;
364 let build = template_parser::lookup_method(type_name, table, function)?;
365 let inner_property = property.try_unwrap(type_name).into_dyn();
366 build(self, diagnostics, build_ctx, inner_property, function)
367 }
368 CommitTemplatePropertyKind::AnnotationLine(property) => {
369 let type_name = "AnnotationLine";
370 let table = &self.build_fn_table.annotation_line_methods;
371 let build = template_parser::lookup_method(type_name, table, function)?;
372 build(self, diagnostics, build_ctx, property, function)
373 }
374 CommitTemplatePropertyKind::Trailer(property) => {
375 let table = &self.build_fn_table.trailer_methods;
376 let build = template_parser::lookup_method(type_name, table, function)?;
377 build(self, diagnostics, build_ctx, property, function)
378 }
379 CommitTemplatePropertyKind::TrailerList(property) => {
380 let table = &self.build_fn_table.trailer_list_methods;
381 let build = template_parser::lookup_method(type_name, table, function)?;
382 build(self, diagnostics, build_ctx, property, function)
383 }
384 }
385 }
386}
387
388impl<'repo> CommitTemplateLanguage<'repo> {
391 pub fn repo(&self) -> &'repo dyn Repo {
392 self.repo
393 }
394
395 pub fn workspace_name(&self) -> &WorkspaceName {
396 &self.workspace_name
397 }
398
399 pub fn keyword_cache(&self) -> &CommitKeywordCache<'repo> {
400 &self.keyword_cache
401 }
402
403 pub fn cache_extension<T: Any>(&self) -> Option<&T> {
404 self.cache_extensions.get::<T>()
405 }
406}
407
408impl OperationTemplateEnvironment for CommitTemplateLanguage<'_> {
409 fn repo_loader(&self) -> &RepoLoader {
410 self.repo.base_repo().loader()
411 }
412
413 fn current_op_id(&self) -> Option<&OperationId> {
414 Some(self.repo.base_repo().op_id())
416 }
417}
418
419pub enum CommitTemplatePropertyKind<'repo> {
420 Core(CoreTemplatePropertyKind<'repo>),
421 Operation(OperationTemplatePropertyKind<'repo>),
422 Commit(BoxedTemplateProperty<'repo, Commit>),
423 CommitOpt(BoxedTemplateProperty<'repo, Option<Commit>>),
424 CommitList(BoxedTemplateProperty<'repo, Vec<Commit>>),
425 CommitEvolutionEntry(BoxedTemplateProperty<'repo, CommitEvolutionEntry>),
426 CommitRef(BoxedTemplateProperty<'repo, Rc<CommitRef>>),
427 CommitRefOpt(BoxedTemplateProperty<'repo, Option<Rc<CommitRef>>>),
428 CommitRefList(BoxedTemplateProperty<'repo, Vec<Rc<CommitRef>>>),
429 WorkspaceRef(BoxedTemplateProperty<'repo, WorkspaceRef>),
430 WorkspaceRefOpt(BoxedTemplateProperty<'repo, Option<WorkspaceRef>>),
431 WorkspaceRefList(BoxedTemplateProperty<'repo, Vec<WorkspaceRef>>),
432 RefSymbol(BoxedTemplateProperty<'repo, RefSymbolBuf>),
433 RefSymbolOpt(BoxedTemplateProperty<'repo, Option<RefSymbolBuf>>),
434 RepoPath(BoxedTemplateProperty<'repo, RepoPathBuf>),
435 RepoPathOpt(BoxedTemplateProperty<'repo, Option<RepoPathBuf>>),
436 ChangeId(BoxedTemplateProperty<'repo, ChangeId>),
437 CommitId(BoxedTemplateProperty<'repo, CommitId>),
438 ShortestIdPrefix(BoxedTemplateProperty<'repo, ShortestIdPrefix>),
439 TreeDiff(BoxedTemplateProperty<'repo, TreeDiff>),
440 TreeDiffEntry(BoxedTemplateProperty<'repo, TreeDiffEntry>),
441 TreeDiffEntryList(BoxedTemplateProperty<'repo, Vec<TreeDiffEntry>>),
442 TreeEntry(BoxedTemplateProperty<'repo, TreeEntry>),
443 TreeEntryList(BoxedTemplateProperty<'repo, Vec<TreeEntry>>),
444 DiffStats(BoxedTemplateProperty<'repo, DiffStatsFormatted<'repo>>),
445 DiffStatEntry(BoxedTemplateProperty<'repo, DiffStatEntry>),
446 DiffStatEntryList(BoxedTemplateProperty<'repo, Vec<DiffStatEntry>>),
447 CryptographicSignatureOpt(BoxedTemplateProperty<'repo, Option<CryptographicSignature>>),
448 AnnotationLine(BoxedTemplateProperty<'repo, AnnotationLine>),
449 Trailer(BoxedTemplateProperty<'repo, Trailer>),
450 TrailerList(BoxedTemplateProperty<'repo, Vec<Trailer>>),
451}
452
453template_builder::impl_core_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Core);
454operation_templater::impl_operation_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Operation);
455template_builder::impl_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> {
456 Commit(Commit),
457 CommitOpt(Option<Commit>),
458 CommitList(Vec<Commit>),
459 CommitEvolutionEntry(CommitEvolutionEntry),
460 CommitRef(Rc<CommitRef>),
461 CommitRefOpt(Option<Rc<CommitRef>>),
462 CommitRefList(Vec<Rc<CommitRef>>),
463 WorkspaceRef(WorkspaceRef),
464 WorkspaceRefOpt(Option<WorkspaceRef>),
465 WorkspaceRefList(Vec<WorkspaceRef>),
466 RefSymbol(RefSymbolBuf),
467 RefSymbolOpt(Option<RefSymbolBuf>),
468 RepoPath(RepoPathBuf),
469 RepoPathOpt(Option<RepoPathBuf>),
470 ChangeId(ChangeId),
471 CommitId(CommitId),
472 ShortestIdPrefix(ShortestIdPrefix),
473 TreeDiff(TreeDiff),
474 TreeDiffEntry(TreeDiffEntry),
475 TreeDiffEntryList(Vec<TreeDiffEntry>),
476 TreeEntry(TreeEntry),
477 TreeEntryList(Vec<TreeEntry>),
478 DiffStats(DiffStatsFormatted<'repo>),
479 DiffStatEntry(DiffStatEntry),
480 DiffStatEntryList(Vec<DiffStatEntry>),
481 CryptographicSignatureOpt(Option<CryptographicSignature>),
482 AnnotationLine(AnnotationLine),
483 Trailer(Trailer),
484 TrailerList(Vec<Trailer>),
485});
486
487impl<'repo> CoreTemplatePropertyVar<'repo> for CommitTemplatePropertyKind<'repo> {
488 fn wrap_template(template: Box<dyn Template + 'repo>) -> Self {
489 Self::Core(CoreTemplatePropertyKind::wrap_template(template))
490 }
491
492 fn wrap_list_template(template: Box<dyn ListTemplate + 'repo>) -> Self {
493 Self::Core(CoreTemplatePropertyKind::wrap_list_template(template))
494 }
495
496 fn type_name(&self) -> &'static str {
497 match self {
498 Self::Core(property) => property.type_name(),
499 Self::Operation(property) => property.type_name(),
500 Self::Commit(_) => "Commit",
501 Self::CommitOpt(_) => "Option<Commit>",
502 Self::CommitList(_) => "List<Commit>",
503 Self::CommitEvolutionEntry(_) => "CommitEvolutionEntry",
504 Self::CommitRef(_) => "CommitRef",
505 Self::CommitRefOpt(_) => "Option<CommitRef>",
506 Self::CommitRefList(_) => "List<CommitRef>",
507 Self::WorkspaceRef(_) => "WorkspaceRef",
508 Self::WorkspaceRefOpt(_) => "Option<WorkspaceRef>",
509 Self::WorkspaceRefList(_) => "List<WorkspaceRef>",
510 Self::RefSymbol(_) => "RefSymbol",
511 Self::RefSymbolOpt(_) => "Option<RefSymbol>",
512 Self::RepoPath(_) => "RepoPath",
513 Self::RepoPathOpt(_) => "Option<RepoPath>",
514 Self::ChangeId(_) => "ChangeId",
515 Self::CommitId(_) => "CommitId",
516 Self::ShortestIdPrefix(_) => "ShortestIdPrefix",
517 Self::TreeDiff(_) => "TreeDiff",
518 Self::TreeDiffEntry(_) => "TreeDiffEntry",
519 Self::TreeDiffEntryList(_) => "List<TreeDiffEntry>",
520 Self::TreeEntry(_) => "TreeEntry",
521 Self::TreeEntryList(_) => "List<TreeEntry>",
522 Self::DiffStats(_) => "DiffStats",
523 Self::DiffStatEntry(_) => "DiffStatEntry",
524 Self::DiffStatEntryList(_) => "List<DiffStatEntry>",
525 Self::CryptographicSignatureOpt(_) => "Option<CryptographicSignature>",
526 Self::AnnotationLine(_) => "AnnotationLine",
527 Self::Trailer(_) => "Trailer",
528 Self::TrailerList(_) => "List<Trailer>",
529 }
530 }
531
532 fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'repo, bool>> {
533 match self {
534 Self::Core(property) => property.try_into_boolean(),
535 Self::Operation(property) => property.try_into_boolean(),
536 Self::Commit(_) => None,
537 Self::CommitOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
538 Self::CommitList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
539 Self::CommitEvolutionEntry(_) => None,
540 Self::CommitRef(_) => None,
541 Self::CommitRefOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
542 Self::CommitRefList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
543 Self::WorkspaceRef(_) => None,
544 Self::WorkspaceRefOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
545 Self::WorkspaceRefList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
546 Self::RefSymbol(_) => None,
547 Self::RefSymbolOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
548 Self::RepoPath(_) => None,
549 Self::RepoPathOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()),
550 Self::ChangeId(_) => None,
551 Self::CommitId(_) => None,
552 Self::ShortestIdPrefix(_) => None,
553 Self::TreeDiff(_) => None,
556 Self::TreeDiffEntry(_) => None,
557 Self::TreeDiffEntryList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
558 Self::TreeEntry(_) => None,
559 Self::TreeEntryList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
560 Self::DiffStats(_) => None,
561 Self::DiffStatEntry(_) => None,
562 Self::DiffStatEntryList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
563 Self::CryptographicSignatureOpt(property) => {
564 Some(property.map(|sig| sig.is_some()).into_dyn())
565 }
566 Self::AnnotationLine(_) => None,
567 Self::Trailer(_) => None,
568 Self::TrailerList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()),
569 }
570 }
571
572 fn try_into_integer(self) -> Option<BoxedTemplateProperty<'repo, i64>> {
573 match self {
574 Self::Core(property) => property.try_into_integer(),
575 Self::Operation(property) => property.try_into_integer(),
576 _ => None,
577 }
578 }
579
580 fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'repo, String>> {
581 match self {
582 Self::Core(property) => property.try_into_stringify(),
583 Self::Operation(property) => property.try_into_stringify(),
584 Self::RefSymbol(property) => Some(property.map(|RefSymbolBuf(s)| s).into_dyn()),
585 Self::RefSymbolOpt(property) => Some(
586 property
587 .map(|opt| opt.map_or_else(String::new, |RefSymbolBuf(s)| s))
588 .into_dyn(),
589 ),
590 _ => {
591 let template = self.try_into_template()?;
592 Some(PlainTextFormattedProperty::new(template).into_dyn())
593 }
594 }
595 }
596
597 fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'repo>> {
598 match self {
599 Self::Core(property) => property.try_into_serialize(),
600 Self::Operation(property) => property.try_into_serialize(),
601 Self::Commit(property) => Some(property.into_serialize()),
602 Self::CommitOpt(property) => Some(property.into_serialize()),
603 Self::CommitList(property) => Some(property.into_serialize()),
604 Self::CommitEvolutionEntry(property) => Some(property.into_serialize()),
605 Self::CommitRef(property) => Some(property.into_serialize()),
606 Self::CommitRefOpt(property) => Some(property.into_serialize()),
607 Self::CommitRefList(property) => Some(property.into_serialize()),
608 Self::WorkspaceRef(property) => Some(property.into_serialize()),
609 Self::WorkspaceRefOpt(property) => Some(property.into_serialize()),
610 Self::WorkspaceRefList(property) => Some(property.into_serialize()),
611 Self::RefSymbol(property) => Some(property.into_serialize()),
612 Self::RefSymbolOpt(property) => Some(property.into_serialize()),
613 Self::RepoPath(property) => Some(property.into_serialize()),
614 Self::RepoPathOpt(property) => Some(property.into_serialize()),
615 Self::ChangeId(property) => Some(property.into_serialize()),
616 Self::CommitId(property) => Some(property.into_serialize()),
617 Self::ShortestIdPrefix(property) => Some(property.into_serialize()),
618 Self::TreeDiff(_) => None,
619 Self::TreeDiffEntry(_) => None,
620 Self::TreeDiffEntryList(_) => None,
621 Self::TreeEntry(_) => None,
622 Self::TreeEntryList(_) => None,
623 Self::DiffStats(_) => None,
624 Self::DiffStatEntry(_) => None,
625 Self::DiffStatEntryList(_) => None,
626 Self::CryptographicSignatureOpt(_) => None,
627 Self::AnnotationLine(_) => None,
628 Self::Trailer(_) => None,
629 Self::TrailerList(_) => None,
630 }
631 }
632
633 fn try_into_template(self) -> Option<Box<dyn Template + 'repo>> {
634 match self {
635 Self::Core(property) => property.try_into_template(),
636 Self::Operation(property) => property.try_into_template(),
637 Self::Commit(_) => None,
638 Self::CommitOpt(_) => None,
639 Self::CommitList(_) => None,
640 Self::CommitEvolutionEntry(_) => None,
641 Self::CommitRef(property) => Some(property.into_template()),
642 Self::CommitRefOpt(property) => Some(property.into_template()),
643 Self::CommitRefList(property) => Some(property.into_template()),
644 Self::WorkspaceRef(property) => Some(property.into_template()),
645 Self::WorkspaceRefOpt(property) => Some(property.into_template()),
646 Self::WorkspaceRefList(property) => Some(property.into_template()),
647 Self::RefSymbol(property) => Some(property.into_template()),
648 Self::RefSymbolOpt(property) => Some(property.into_template()),
649 Self::RepoPath(property) => Some(property.into_template()),
650 Self::RepoPathOpt(property) => Some(property.into_template()),
651 Self::ChangeId(property) => Some(property.into_template()),
652 Self::CommitId(property) => Some(property.into_template()),
653 Self::ShortestIdPrefix(property) => Some(property.into_template()),
654 Self::TreeDiff(_) => None,
655 Self::TreeDiffEntry(_) => None,
656 Self::TreeDiffEntryList(_) => None,
657 Self::TreeEntry(_) => None,
658 Self::TreeEntryList(_) => None,
659 Self::DiffStats(property) => Some(property.into_template()),
660 Self::DiffStatEntry(_) => None,
661 Self::DiffStatEntryList(_) => None,
662 Self::CryptographicSignatureOpt(_) => None,
663 Self::AnnotationLine(_) => None,
664 Self::Trailer(property) => Some(property.into_template()),
665 Self::TrailerList(property) => Some(property.into_template()),
666 }
667 }
668
669 fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'repo, bool>> {
670 type Core<'repo> = CoreTemplatePropertyKind<'repo>;
671 match (self, other) {
672 (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
673 (Self::Core(lhs), Self::Operation(rhs)) => rhs.try_into_eq_core(lhs),
674 (Self::Core(Core::String(lhs)), Self::RefSymbol(rhs)) => {
675 Some((lhs, rhs).map(|(l, r)| RefSymbolBuf(l) == r).into_dyn())
676 }
677 (Self::Core(Core::String(lhs)), Self::RefSymbolOpt(rhs)) => Some(
678 (lhs, rhs)
679 .map(|(l, r)| Some(RefSymbolBuf(l)) == r)
680 .into_dyn(),
681 ),
682 (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_eq_core(rhs),
683 (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_eq(rhs),
684 (Self::RefSymbol(lhs), Self::Core(Core::String(rhs))) => {
685 Some((lhs, rhs).map(|(l, r)| l == RefSymbolBuf(r)).into_dyn())
686 }
687 (Self::RefSymbol(lhs), Self::RefSymbol(rhs)) => {
688 Some((lhs, rhs).map(|(l, r)| l == r).into_dyn())
689 }
690 (Self::RefSymbol(lhs), Self::RefSymbolOpt(rhs)) => {
691 Some((lhs, rhs).map(|(l, r)| Some(l) == r).into_dyn())
692 }
693 (Self::RefSymbolOpt(lhs), Self::Core(Core::String(rhs))) => Some(
694 (lhs, rhs)
695 .map(|(l, r)| l == Some(RefSymbolBuf(r)))
696 .into_dyn(),
697 ),
698 (Self::RefSymbolOpt(lhs), Self::RefSymbol(rhs)) => {
699 Some((lhs, rhs).map(|(l, r)| l == Some(r)).into_dyn())
700 }
701 (Self::RefSymbolOpt(lhs), Self::RefSymbolOpt(rhs)) => {
702 Some((lhs, rhs).map(|(l, r)| l == r).into_dyn())
703 }
704 (Self::Core(_), _) => None,
705 (Self::Operation(_), _) => None,
706 (Self::Commit(_), _) => None,
707 (Self::CommitOpt(_), _) => None,
708 (Self::CommitList(_), _) => None,
709 (Self::CommitEvolutionEntry(_), _) => None,
710 (Self::CommitRef(_), _) => None,
711 (Self::CommitRefOpt(_), _) => None,
712 (Self::CommitRefList(_), _) => None,
713 (Self::WorkspaceRef(_), _) => None,
714 (Self::WorkspaceRefOpt(_), _) => None,
715 (Self::WorkspaceRefList(_), _) => None,
716 (Self::RefSymbol(_), _) => None,
717 (Self::RefSymbolOpt(_), _) => None,
718 (Self::RepoPath(_), _) => None,
719 (Self::RepoPathOpt(_), _) => None,
720 (Self::ChangeId(_), _) => None,
721 (Self::CommitId(_), _) => None,
722 (Self::ShortestIdPrefix(_), _) => None,
723 (Self::TreeDiff(_), _) => None,
724 (Self::TreeDiffEntry(_), _) => None,
725 (Self::TreeDiffEntryList(_), _) => None,
726 (Self::TreeEntry(_), _) => None,
727 (Self::TreeEntryList(_), _) => None,
728 (Self::DiffStats(_), _) => None,
729 (Self::DiffStatEntry(_), _) => None,
730 (Self::DiffStatEntryList(_), _) => None,
731 (Self::CryptographicSignatureOpt(_), _) => None,
732 (Self::AnnotationLine(_), _) => None,
733 (Self::Trailer(_), _) => None,
734 (Self::TrailerList(_), _) => None,
735 }
736 }
737
738 fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'repo, Ordering>> {
739 match (self, other) {
740 (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
741 (Self::Core(lhs), Self::Operation(rhs)) => rhs
742 .try_into_cmp_core(lhs)
743 .map(|property| property.map(Ordering::reverse).into_dyn()),
744 (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_cmp_core(rhs),
745 (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_cmp(rhs),
746 (Self::Core(_), _) => None,
747 (Self::Operation(_), _) => None,
748 (Self::Commit(_), _) => None,
749 (Self::CommitOpt(_), _) => None,
750 (Self::CommitList(_), _) => None,
751 (Self::CommitEvolutionEntry(_), _) => None,
752 (Self::CommitRef(_), _) => None,
753 (Self::CommitRefOpt(_), _) => None,
754 (Self::CommitRefList(_), _) => None,
755 (Self::WorkspaceRef(_), _) => None,
756 (Self::WorkspaceRefOpt(_), _) => None,
757 (Self::WorkspaceRefList(_), _) => None,
758 (Self::RefSymbol(_), _) => None,
759 (Self::RefSymbolOpt(_), _) => None,
760 (Self::RepoPath(_), _) => None,
761 (Self::RepoPathOpt(_), _) => None,
762 (Self::ChangeId(_), _) => None,
763 (Self::CommitId(_), _) => None,
764 (Self::ShortestIdPrefix(_), _) => None,
765 (Self::TreeDiff(_), _) => None,
766 (Self::TreeDiffEntry(_), _) => None,
767 (Self::TreeDiffEntryList(_), _) => None,
768 (Self::TreeEntry(_), _) => None,
769 (Self::TreeEntryList(_), _) => None,
770 (Self::DiffStats(_), _) => None,
771 (Self::DiffStatEntry(_), _) => None,
772 (Self::DiffStatEntryList(_), _) => None,
773 (Self::CryptographicSignatureOpt(_), _) => None,
774 (Self::AnnotationLine(_), _) => None,
775 (Self::Trailer(_), _) => None,
776 (Self::TrailerList(_), _) => None,
777 }
778 }
779}
780
781impl<'repo> OperationTemplatePropertyVar<'repo> for CommitTemplatePropertyKind<'repo> {}
782
783pub type CommitTemplateBuildMethodFnMap<'repo, T> =
785 TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>;
786
787pub struct CommitTemplateBuildFnTable<'repo> {
789 pub core: CoreTemplateBuildFnTable<'repo, CommitTemplateLanguage<'repo>>,
790 pub operation: OperationTemplateBuildFnTable<'repo, CommitTemplateLanguage<'repo>>,
791 pub commit_methods: CommitTemplateBuildMethodFnMap<'repo, Commit>,
792 pub commit_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<Commit>>,
793 pub commit_evolution_entry_methods: CommitTemplateBuildMethodFnMap<'repo, CommitEvolutionEntry>,
794 pub commit_ref_methods: CommitTemplateBuildMethodFnMap<'repo, Rc<CommitRef>>,
795 pub commit_ref_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<Rc<CommitRef>>>,
796 pub workspace_ref_methods: CommitTemplateBuildMethodFnMap<'repo, WorkspaceRef>,
797 pub workspace_ref_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<WorkspaceRef>>,
798 pub repo_path_methods: CommitTemplateBuildMethodFnMap<'repo, RepoPathBuf>,
799 pub change_id_methods: CommitTemplateBuildMethodFnMap<'repo, ChangeId>,
800 pub commit_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitId>,
801 pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,
802 pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>,
803 pub tree_diff_entry_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiffEntry>,
804 pub tree_diff_entry_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<TreeDiffEntry>>,
805 pub tree_entry_methods: CommitTemplateBuildMethodFnMap<'repo, TreeEntry>,
806 pub tree_entry_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<TreeEntry>>,
807 pub diff_stats_methods: CommitTemplateBuildMethodFnMap<'repo, DiffStats>,
808 pub diff_stat_entry_methods: CommitTemplateBuildMethodFnMap<'repo, DiffStatEntry>,
809 pub diff_stat_entry_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<DiffStatEntry>>,
810 pub cryptographic_signature_methods:
811 CommitTemplateBuildMethodFnMap<'repo, CryptographicSignature>,
812 pub annotation_line_methods: CommitTemplateBuildMethodFnMap<'repo, AnnotationLine>,
813 pub trailer_methods: CommitTemplateBuildMethodFnMap<'repo, Trailer>,
814 pub trailer_list_methods: CommitTemplateBuildMethodFnMap<'repo, Vec<Trailer>>,
815}
816
817impl CommitTemplateBuildFnTable<'_> {
818 pub fn empty() -> Self {
819 Self {
820 core: CoreTemplateBuildFnTable::empty(),
821 operation: OperationTemplateBuildFnTable::empty(),
822 commit_methods: HashMap::new(),
823 commit_list_methods: HashMap::new(),
824 commit_evolution_entry_methods: HashMap::new(),
825 commit_ref_methods: HashMap::new(),
826 commit_ref_list_methods: HashMap::new(),
827 workspace_ref_methods: HashMap::new(),
828 workspace_ref_list_methods: HashMap::new(),
829 repo_path_methods: HashMap::new(),
830 change_id_methods: HashMap::new(),
831 commit_id_methods: HashMap::new(),
832 shortest_id_prefix_methods: HashMap::new(),
833 tree_diff_methods: HashMap::new(),
834 tree_diff_entry_methods: HashMap::new(),
835 tree_diff_entry_list_methods: HashMap::new(),
836 tree_entry_methods: HashMap::new(),
837 tree_entry_list_methods: HashMap::new(),
838 diff_stats_methods: HashMap::new(),
839 diff_stat_entry_methods: HashMap::new(),
840 diff_stat_entry_list_methods: HashMap::new(),
841 cryptographic_signature_methods: HashMap::new(),
842 annotation_line_methods: HashMap::new(),
843 trailer_methods: HashMap::new(),
844 trailer_list_methods: HashMap::new(),
845 }
846 }
847
848 fn merge(&mut self, other: Self) {
849 let Self {
850 core,
851 operation,
852 commit_methods,
853 commit_list_methods,
854 commit_evolution_entry_methods,
855 commit_ref_methods,
856 commit_ref_list_methods,
857 workspace_ref_methods,
858 workspace_ref_list_methods,
859 repo_path_methods,
860 change_id_methods,
861 commit_id_methods,
862 shortest_id_prefix_methods,
863 tree_diff_methods,
864 tree_diff_entry_methods,
865 tree_diff_entry_list_methods,
866 tree_entry_methods,
867 tree_entry_list_methods,
868 diff_stats_methods,
869 diff_stat_entry_methods,
870 diff_stat_entry_list_methods,
871 cryptographic_signature_methods,
872 annotation_line_methods,
873 trailer_methods,
874 trailer_list_methods,
875 } = other;
876
877 self.core.merge(core);
878 self.operation.merge(operation);
879 merge_fn_map(&mut self.commit_methods, commit_methods);
880 merge_fn_map(&mut self.commit_list_methods, commit_list_methods);
881 merge_fn_map(
882 &mut self.commit_evolution_entry_methods,
883 commit_evolution_entry_methods,
884 );
885 merge_fn_map(&mut self.commit_ref_methods, commit_ref_methods);
886 merge_fn_map(&mut self.commit_ref_list_methods, commit_ref_list_methods);
887 merge_fn_map(&mut self.workspace_ref_methods, workspace_ref_methods);
888 merge_fn_map(
889 &mut self.workspace_ref_list_methods,
890 workspace_ref_list_methods,
891 );
892 merge_fn_map(&mut self.repo_path_methods, repo_path_methods);
893 merge_fn_map(&mut self.change_id_methods, change_id_methods);
894 merge_fn_map(&mut self.commit_id_methods, commit_id_methods);
895 merge_fn_map(
896 &mut self.shortest_id_prefix_methods,
897 shortest_id_prefix_methods,
898 );
899 merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods);
900 merge_fn_map(&mut self.tree_diff_entry_methods, tree_diff_entry_methods);
901 merge_fn_map(
902 &mut self.tree_diff_entry_list_methods,
903 tree_diff_entry_list_methods,
904 );
905 merge_fn_map(&mut self.tree_entry_methods, tree_entry_methods);
906 merge_fn_map(&mut self.tree_entry_list_methods, tree_entry_list_methods);
907 merge_fn_map(&mut self.diff_stats_methods, diff_stats_methods);
908 merge_fn_map(&mut self.diff_stat_entry_methods, diff_stat_entry_methods);
909 merge_fn_map(
910 &mut self.diff_stat_entry_list_methods,
911 diff_stat_entry_list_methods,
912 );
913 merge_fn_map(
914 &mut self.cryptographic_signature_methods,
915 cryptographic_signature_methods,
916 );
917 merge_fn_map(&mut self.annotation_line_methods, annotation_line_methods);
918 merge_fn_map(&mut self.trailer_methods, trailer_methods);
919 merge_fn_map(&mut self.trailer_list_methods, trailer_list_methods);
920 }
921
922 fn builtin() -> Self {
924 Self {
925 core: CoreTemplateBuildFnTable::builtin(),
926 operation: OperationTemplateBuildFnTable::builtin(),
927 commit_methods: builtin_commit_methods(),
928 commit_list_methods: template_builder::builtin_unformattable_list_methods(),
929 commit_evolution_entry_methods: builtin_commit_evolution_entry_methods(),
930 commit_ref_methods: builtin_commit_ref_methods(),
931 commit_ref_list_methods: template_builder::builtin_formattable_list_methods(),
932 workspace_ref_methods: builtin_workspace_ref_methods(),
933 workspace_ref_list_methods: template_builder::builtin_formattable_list_methods(),
934 repo_path_methods: builtin_repo_path_methods(),
935 change_id_methods: builtin_change_id_methods(),
936 commit_id_methods: builtin_commit_id_methods(),
937 shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(),
938 tree_diff_methods: builtin_tree_diff_methods(),
939 tree_diff_entry_methods: builtin_tree_diff_entry_methods(),
940 tree_diff_entry_list_methods: template_builder::builtin_unformattable_list_methods(),
941 tree_entry_methods: builtin_tree_entry_methods(),
942 tree_entry_list_methods: template_builder::builtin_unformattable_list_methods(),
943 diff_stats_methods: builtin_diff_stats_methods(),
944 diff_stat_entry_methods: builtin_diff_stat_entry_methods(),
945 diff_stat_entry_list_methods: template_builder::builtin_unformattable_list_methods(),
946 cryptographic_signature_methods: builtin_cryptographic_signature_methods(),
947 annotation_line_methods: builtin_annotation_line_methods(),
948 trailer_methods: builtin_trailer_methods(),
949 trailer_list_methods: builtin_trailer_list_methods(),
950 }
951 }
952}
953
954#[derive(Default)]
955pub struct CommitKeywordCache<'repo> {
956 bookmarks_index: OnceCell<Rc<CommitRefsIndex>>,
958 tags_index: OnceCell<Rc<CommitRefsIndex>>,
959 git_refs_index: OnceCell<Rc<CommitRefsIndex>>,
960 is_immutable_fn: OnceCell<Rc<RevsetContainingFn<'repo>>>,
961}
962
963impl<'repo> CommitKeywordCache<'repo> {
964 pub fn bookmarks_index(&self, repo: &dyn Repo) -> &Rc<CommitRefsIndex> {
965 self.bookmarks_index
966 .get_or_init(|| Rc::new(build_local_remote_refs_index(repo.view().bookmarks())))
967 }
968
969 pub fn tags_index(&self, repo: &dyn Repo) -> &Rc<CommitRefsIndex> {
970 self.tags_index
971 .get_or_init(|| Rc::new(build_local_remote_refs_index(repo.view().tags())))
972 }
973
974 pub fn git_refs_index(&self, repo: &dyn Repo) -> &Rc<CommitRefsIndex> {
975 self.git_refs_index
976 .get_or_init(|| Rc::new(build_commit_refs_index(repo.view().git_refs())))
977 }
978
979 pub fn is_immutable_fn(
980 &self,
981 language: &CommitTemplateLanguage<'repo>,
982 span: pest::Span<'_>,
983 ) -> TemplateParseResult<&Rc<RevsetContainingFn<'repo>>> {
984 self.is_immutable_fn.get_or_try_init(|| {
988 let expression = &language.immutable_expression;
989 let revset = evaluate_revset_expression(language, span, expression)?;
990 Ok(revset.containing_fn().into())
991 })
992 }
993}
994
995fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> {
996 let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new();
999 map.insert(
1000 "description",
1001 |_language, _diagnostics, _build_ctx, self_property, function| {
1002 function.expect_no_arguments()?;
1003 let out_property = self_property.map(|commit| commit.description().to_owned());
1004 Ok(out_property.into_dyn_wrapped())
1005 },
1006 );
1007 map.insert(
1008 "trailers",
1009 |_language, _diagnostics, _build_ctx, self_property, function| {
1010 function.expect_no_arguments()?;
1011 let out_property = self_property
1012 .map(|commit| trailer::parse_description_trailers(commit.description()));
1013 Ok(out_property.into_dyn_wrapped())
1014 },
1015 );
1016 map.insert(
1017 "change_id",
1018 |_language, _diagnostics, _build_ctx, self_property, function| {
1019 function.expect_no_arguments()?;
1020 let out_property = self_property.map(|commit| commit.change_id().to_owned());
1021 Ok(out_property.into_dyn_wrapped())
1022 },
1023 );
1024 map.insert(
1025 "commit_id",
1026 |_language, _diagnostics, _build_ctx, self_property, function| {
1027 function.expect_no_arguments()?;
1028 let out_property = self_property.map(|commit| commit.id().to_owned());
1029 Ok(out_property.into_dyn_wrapped())
1030 },
1031 );
1032 map.insert(
1033 "parents",
1034 |_language, _diagnostics, _build_ctx, self_property, function| {
1035 function.expect_no_arguments()?;
1036 let out_property = self_property.and_then(|commit| {
1037 let commits: Vec<_> = commit.parents().try_collect()?;
1038 Ok(commits)
1039 });
1040 Ok(out_property.into_dyn_wrapped())
1041 },
1042 );
1043 map.insert(
1044 "author",
1045 |_language, _diagnostics, _build_ctx, self_property, function| {
1046 function.expect_no_arguments()?;
1047 let out_property = self_property.map(|commit| commit.author().clone());
1048 Ok(out_property.into_dyn_wrapped())
1049 },
1050 );
1051 map.insert(
1052 "committer",
1053 |_language, _diagnostics, _build_ctx, self_property, function| {
1054 function.expect_no_arguments()?;
1055 let out_property = self_property.map(|commit| commit.committer().clone());
1056 Ok(out_property.into_dyn_wrapped())
1057 },
1058 );
1059 map.insert(
1060 "mine",
1061 |language, _diagnostics, _build_ctx, self_property, function| {
1062 function.expect_no_arguments()?;
1063 let user_email = language.revset_parse_context.user_email.to_owned();
1064 let out_property = self_property.map(move |commit| commit.author().email == user_email);
1065 Ok(out_property.into_dyn_wrapped())
1066 },
1067 );
1068 map.insert(
1069 "signature",
1070 |_language, _diagnostics, _build_ctx, self_property, function| {
1071 function.expect_no_arguments()?;
1072 let out_property = self_property.map(CryptographicSignature::new);
1073 Ok(out_property.into_dyn_wrapped())
1074 },
1075 );
1076 map.insert(
1077 "working_copies",
1078 |language, _diagnostics, _build_ctx, self_property, function| {
1079 function.expect_no_arguments()?;
1080 let repo = language.repo;
1081 let out_property = self_property.map(|commit| extract_working_copies(repo, &commit));
1082 Ok(out_property.into_dyn_wrapped())
1083 },
1084 );
1085 map.insert(
1086 "current_working_copy",
1087 |language, _diagnostics, _build_ctx, self_property, function| {
1088 function.expect_no_arguments()?;
1089 let repo = language.repo;
1090 let name = language.workspace_name.clone();
1091 let out_property = self_property
1092 .map(move |commit| Some(commit.id()) == repo.view().get_wc_commit_id(&name));
1093 Ok(out_property.into_dyn_wrapped())
1094 },
1095 );
1096 map.insert(
1097 "bookmarks",
1098 |language, _diagnostics, _build_ctx, self_property, function| {
1099 function.expect_no_arguments()?;
1100 let index = language
1101 .keyword_cache
1102 .bookmarks_index(language.repo)
1103 .clone();
1104 let out_property =
1105 self_property.map(move |commit| collect_distinct_refs(index.get(commit.id())));
1106 Ok(out_property.into_dyn_wrapped())
1107 },
1108 );
1109 map.insert(
1110 "local_bookmarks",
1111 |language, _diagnostics, _build_ctx, self_property, function| {
1112 function.expect_no_arguments()?;
1113 let index = language
1114 .keyword_cache
1115 .bookmarks_index(language.repo)
1116 .clone();
1117 let out_property =
1118 self_property.map(move |commit| collect_local_refs(index.get(commit.id())));
1119 Ok(out_property.into_dyn_wrapped())
1120 },
1121 );
1122 map.insert(
1123 "remote_bookmarks",
1124 |language, _diagnostics, _build_ctx, self_property, function| {
1125 function.expect_no_arguments()?;
1126 let index = language
1127 .keyword_cache
1128 .bookmarks_index(language.repo)
1129 .clone();
1130 let out_property =
1131 self_property.map(move |commit| collect_remote_refs(index.get(commit.id())));
1132 Ok(out_property.into_dyn_wrapped())
1133 },
1134 );
1135 map.insert(
1136 "tags",
1137 |language, _diagnostics, _build_ctx, self_property, function| {
1138 function.expect_no_arguments()?;
1139 let index = language.keyword_cache.tags_index(language.repo).clone();
1140 let out_property =
1141 self_property.map(move |commit| collect_distinct_refs(index.get(commit.id())));
1142 Ok(out_property.into_dyn_wrapped())
1143 },
1144 );
1145 map.insert(
1146 "local_tags",
1147 |language, _diagnostics, _build_ctx, self_property, function| {
1148 function.expect_no_arguments()?;
1149 let index = language.keyword_cache.tags_index(language.repo).clone();
1150 let out_property =
1151 self_property.map(move |commit| collect_local_refs(index.get(commit.id())));
1152 Ok(out_property.into_dyn_wrapped())
1153 },
1154 );
1155 map.insert(
1156 "remote_tags",
1157 |language, _diagnostics, _build_ctx, self_property, function| {
1158 function.expect_no_arguments()?;
1159 let index = language.keyword_cache.tags_index(language.repo).clone();
1160 let out_property =
1161 self_property.map(move |commit| collect_remote_refs(index.get(commit.id())));
1162 Ok(out_property.into_dyn_wrapped())
1163 },
1164 );
1165 map.insert(
1166 "git_refs",
1167 |language, _diagnostics, _build_ctx, self_property, function| {
1168 function.expect_no_arguments()?;
1169 let index = language.keyword_cache.git_refs_index(language.repo).clone();
1170 let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
1171 Ok(out_property.into_dyn_wrapped())
1172 },
1173 );
1174 map.insert(
1175 "git_head",
1176 |language, _diagnostics, _build_ctx, self_property, function| {
1177 function.expect_no_arguments()?;
1178 let repo = language.repo;
1179 let out_property = self_property.map(|commit| {
1180 let target = repo.view().git_head();
1181 target.added_ids().contains(commit.id())
1182 });
1183 Ok(out_property.into_dyn_wrapped())
1184 },
1185 );
1186 map.insert(
1187 "divergent",
1188 |language, _diagnostics, _build_ctx, self_property, function| {
1189 function.expect_no_arguments()?;
1190 let repo = language.repo;
1191 let out_property = self_property.and_then(|commit| {
1192 let maybe_entries = repo.resolve_change_id(commit.change_id())?;
1194 let divergent = maybe_entries.map_or(0, |entries| entries.len()) > 1;
1195 Ok(divergent)
1196 });
1197 Ok(out_property.into_dyn_wrapped())
1198 },
1199 );
1200 map.insert(
1201 "hidden",
1202 |language, _diagnostics, _build_ctx, self_property, function| {
1203 function.expect_no_arguments()?;
1204 let repo = language.repo;
1205 let out_property = self_property.and_then(|commit| Ok(commit.is_hidden(repo)?));
1206 Ok(out_property.into_dyn_wrapped())
1207 },
1208 );
1209 map.insert(
1210 "immutable",
1211 |language, _diagnostics, _build_ctx, self_property, function| {
1212 function.expect_no_arguments()?;
1213 let is_immutable = language
1214 .keyword_cache
1215 .is_immutable_fn(language, function.name_span)?
1216 .clone();
1217 let out_property = self_property.and_then(move |commit| Ok(is_immutable(commit.id())?));
1218 Ok(out_property.into_dyn_wrapped())
1219 },
1220 );
1221 map.insert(
1222 "contained_in",
1223 |language, diagnostics, _build_ctx, self_property, function| {
1224 let [revset_node] = function.expect_exact_arguments()?;
1225
1226 let is_contained =
1227 template_parser::catch_aliases(diagnostics, revset_node, |diagnostics, node| {
1228 let text = template_parser::expect_string_literal(node)?;
1229 let revset = evaluate_user_revset(language, diagnostics, node.span, text)?;
1230 Ok(revset.containing_fn())
1231 })?;
1232
1233 let out_property = self_property.and_then(move |commit| Ok(is_contained(commit.id())?));
1234 Ok(out_property.into_dyn_wrapped())
1235 },
1236 );
1237 map.insert(
1238 "conflict",
1239 |_language, _diagnostics, _build_ctx, self_property, function| {
1240 function.expect_no_arguments()?;
1241 let out_property = self_property.map(|commit| commit.has_conflict());
1242 Ok(out_property.into_dyn_wrapped())
1243 },
1244 );
1245 map.insert(
1246 "empty",
1247 |language, _diagnostics, _build_ctx, self_property, function| {
1248 function.expect_no_arguments()?;
1249 let repo = language.repo;
1250 let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?));
1251 Ok(out_property.into_dyn_wrapped())
1252 },
1253 );
1254 map.insert(
1255 "diff",
1256 |language, diagnostics, _build_ctx, self_property, function| {
1257 let ([], [files_node]) = function.expect_arguments()?;
1258 let files = if let Some(node) = files_node {
1259 expect_fileset_literal(diagnostics, node, language.path_converter)?
1260 } else {
1261 FilesetExpression::all()
1264 };
1265 let repo = language.repo;
1266 let matcher: Rc<dyn Matcher> = files.to_matcher().into();
1267 let out_property = self_property
1268 .and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?));
1269 Ok(out_property.into_dyn_wrapped())
1270 },
1271 );
1272 map.insert(
1273 "files",
1274 |language, diagnostics, _build_ctx, self_property, function| {
1275 let ([], [files_node]) = function.expect_arguments()?;
1276 let files = if let Some(node) = files_node {
1277 expect_fileset_literal(diagnostics, node, language.path_converter)?
1278 } else {
1279 FilesetExpression::all()
1282 };
1283 let matcher = files.to_matcher();
1284 let out_property = self_property.and_then(move |commit| {
1285 let tree = commit.tree();
1286 let entries: Vec<_> = tree
1287 .entries_matching(&*matcher)
1288 .map(|(path, value)| value.map(|value| (path, value)))
1289 .map_ok(|(path, value)| TreeEntry { path, value })
1290 .try_collect()?;
1291 Ok(entries)
1292 });
1293 Ok(out_property.into_dyn_wrapped())
1294 },
1295 );
1296 map.insert(
1297 "root",
1298 |language, _diagnostics, _build_ctx, self_property, function| {
1299 function.expect_no_arguments()?;
1300 let repo = language.repo;
1301 let out_property =
1302 self_property.map(|commit| commit.id() == repo.store().root_commit_id());
1303 Ok(out_property.into_dyn_wrapped())
1304 },
1305 );
1306 map
1307}
1308
1309fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> Vec<WorkspaceRef> {
1310 if repo.view().wc_commit_ids().len() <= 1 {
1311 return vec![];
1313 }
1314
1315 repo.view()
1316 .wc_commit_ids()
1317 .iter()
1318 .filter(|(_, wc_commit_id)| *wc_commit_id == commit.id())
1319 .map(|(name, _)| WorkspaceRef::new(name.to_owned(), commit.to_owned()))
1320 .collect()
1321}
1322
1323fn expect_fileset_literal(
1324 diagnostics: &mut TemplateDiagnostics,
1325 node: &ExpressionNode,
1326 path_converter: &RepoPathUiConverter,
1327) -> Result<FilesetExpression, TemplateParseError> {
1328 template_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1329 let text = template_parser::expect_string_literal(node)?;
1330 let mut inner_diagnostics = FilesetDiagnostics::new();
1331 let expression =
1332 fileset::parse(&mut inner_diagnostics, text, path_converter).map_err(|err| {
1333 TemplateParseError::expression("In fileset expression", node.span).with_source(err)
1334 })?;
1335 diagnostics.extend_with(inner_diagnostics, |diag| {
1336 TemplateParseError::expression("In fileset expression", node.span).with_source(diag)
1337 });
1338 Ok(expression)
1339 })
1340}
1341
1342fn evaluate_revset_expression<'repo>(
1343 language: &CommitTemplateLanguage<'repo>,
1344 span: pest::Span<'_>,
1345 expression: &UserRevsetExpression,
1346) -> Result<Box<dyn Revset + 'repo>, TemplateParseError> {
1347 let make_error = || TemplateParseError::expression("Failed to evaluate revset", span);
1348 let repo = language.repo;
1349 let symbol_resolver = revset_util::default_symbol_resolver(
1350 repo,
1351 language.revset_parse_context.extensions.symbol_resolvers(),
1352 language.id_prefix_context,
1353 );
1354 let revset = expression
1355 .resolve_user_expression(repo, &symbol_resolver)
1356 .map_err(|err| make_error().with_source(err))?
1357 .evaluate(repo)
1358 .map_err(|err| make_error().with_source(err))?;
1359 Ok(revset)
1360}
1361
1362fn evaluate_user_revset<'repo>(
1363 language: &CommitTemplateLanguage<'repo>,
1364 diagnostics: &mut TemplateDiagnostics,
1365 span: pest::Span<'_>,
1366 revset: &str,
1367) -> Result<Box<dyn Revset + 'repo>, TemplateParseError> {
1368 let mut inner_diagnostics = RevsetDiagnostics::new();
1369 let (expression, modifier) = revset::parse_with_modifier(
1370 &mut inner_diagnostics,
1371 revset,
1372 &language.revset_parse_context,
1373 )
1374 .map_err(|err| TemplateParseError::expression("In revset expression", span).with_source(err))?;
1375 diagnostics.extend_with(inner_diagnostics, |diag| {
1376 TemplateParseError::expression("In revset expression", span).with_source(diag)
1377 });
1378 let (None | Some(RevsetModifier::All)) = modifier;
1379
1380 evaluate_revset_expression(language, span, &expression)
1381}
1382
1383fn builtin_commit_evolution_entry_methods<'repo>()
1384-> CommitTemplateBuildMethodFnMap<'repo, CommitEvolutionEntry> {
1385 let mut map = CommitTemplateBuildMethodFnMap::<CommitEvolutionEntry>::new();
1388 map.insert(
1389 "commit",
1390 |_language, _diagnostics, _build_ctx, self_property, function| {
1391 function.expect_no_arguments()?;
1392 let out_property = self_property.map(|entry| entry.commit);
1393 Ok(out_property.into_dyn_wrapped())
1394 },
1395 );
1396 map.insert(
1397 "operation",
1398 |_language, _diagnostics, _build_ctx, self_property, function| {
1399 function.expect_no_arguments()?;
1400 let out_property = self_property.map(|entry| entry.operation);
1401 Ok(out_property.into_dyn_wrapped())
1402 },
1403 );
1404 map
1406}
1407
1408#[derive(Debug, serde::Serialize)]
1410pub struct CommitRef {
1411 name: RefSymbolBuf,
1415 #[serde(skip_serializing_if = "Option::is_none")] remote: Option<RefSymbolBuf>,
1418 target: RefTarget,
1420 #[serde(rename = "tracking_target")]
1422 #[serde(skip_serializing_if = "Option::is_none")] #[serde(serialize_with = "serialize_tracking_target")]
1424 tracking_ref: Option<TrackingRef>,
1425 #[serde(skip)] synced: bool,
1429}
1430
1431#[derive(Debug)]
1432struct TrackingRef {
1433 target: RefTarget,
1435 ahead_count: OnceCell<SizeHint>,
1437 behind_count: OnceCell<SizeHint>,
1439}
1440
1441impl CommitRef {
1442 pub fn local<'a>(
1448 name: impl Into<String>,
1449 target: RefTarget,
1450 remote_refs: impl IntoIterator<Item = &'a RemoteRef>,
1451 ) -> Rc<Self> {
1452 let synced = remote_refs
1453 .into_iter()
1454 .all(|remote_ref| !remote_ref.is_tracked() || remote_ref.target == target);
1455 Rc::new(Self {
1456 name: RefSymbolBuf(name.into()),
1457 remote: None,
1458 target,
1459 tracking_ref: None,
1460 synced,
1461 })
1462 }
1463
1464 pub fn local_only(name: impl Into<String>, target: RefTarget) -> Rc<Self> {
1466 Self::local(name, target, [])
1467 }
1468
1469 pub fn remote(
1472 name: impl Into<String>,
1473 remote_name: impl Into<String>,
1474 remote_ref: RemoteRef,
1475 local_target: &RefTarget,
1476 ) -> Rc<Self> {
1477 let synced = remote_ref.is_tracked() && remote_ref.target == *local_target;
1478 let tracking_ref = remote_ref.is_tracked().then(|| {
1479 let count = if synced {
1480 OnceCell::from((0, Some(0))) } else {
1482 OnceCell::new()
1483 };
1484 TrackingRef {
1485 target: local_target.clone(),
1486 ahead_count: count.clone(),
1487 behind_count: count,
1488 }
1489 });
1490 Rc::new(Self {
1491 name: RefSymbolBuf(name.into()),
1492 remote: Some(RefSymbolBuf(remote_name.into())),
1493 target: remote_ref.target,
1494 tracking_ref,
1495 synced,
1496 })
1497 }
1498
1499 pub fn remote_only(
1501 name: impl Into<String>,
1502 remote_name: impl Into<String>,
1503 target: RefTarget,
1504 ) -> Rc<Self> {
1505 Rc::new(Self {
1506 name: RefSymbolBuf(name.into()),
1507 remote: Some(RefSymbolBuf(remote_name.into())),
1508 target,
1509 tracking_ref: None,
1510 synced: false, })
1512 }
1513
1514 pub fn name(&self) -> &str {
1516 self.name.as_ref()
1517 }
1518
1519 pub fn remote_name(&self) -> Option<&str> {
1521 self.remote.as_ref().map(AsRef::as_ref)
1522 }
1523
1524 pub fn target(&self) -> &RefTarget {
1526 &self.target
1527 }
1528
1529 pub fn is_local(&self) -> bool {
1531 self.remote.is_none()
1532 }
1533
1534 pub fn is_remote(&self) -> bool {
1536 self.remote.is_some()
1537 }
1538
1539 pub fn is_absent(&self) -> bool {
1541 self.target.is_absent()
1542 }
1543
1544 pub fn is_present(&self) -> bool {
1546 self.target.is_present()
1547 }
1548
1549 pub fn has_conflict(&self) -> bool {
1551 self.target.has_conflict()
1552 }
1553
1554 pub fn is_tracked(&self) -> bool {
1557 self.tracking_ref.is_some()
1558 }
1559
1560 pub fn is_tracking_present(&self) -> bool {
1563 self.tracking_ref
1564 .as_ref()
1565 .is_some_and(|tracking| tracking.target.is_present())
1566 }
1567
1568 fn tracking_ahead_count(&self, repo: &dyn Repo) -> Result<SizeHint, TemplatePropertyError> {
1570 let Some(tracking) = &self.tracking_ref else {
1571 return Err(TemplatePropertyError("Not a tracked remote ref".into()));
1572 };
1573 tracking
1574 .ahead_count
1575 .get_or_try_init(|| {
1576 let self_ids = self.target.added_ids().cloned().collect_vec();
1577 let other_ids = tracking.target.added_ids().cloned().collect_vec();
1578 Ok(revset::walk_revs(repo, &self_ids, &other_ids)?.count_estimate()?)
1579 })
1580 .copied()
1581 }
1582
1583 fn tracking_behind_count(&self, repo: &dyn Repo) -> Result<SizeHint, TemplatePropertyError> {
1585 let Some(tracking) = &self.tracking_ref else {
1586 return Err(TemplatePropertyError("Not a tracked remote ref".into()));
1587 };
1588 tracking
1589 .behind_count
1590 .get_or_try_init(|| {
1591 let self_ids = self.target.added_ids().cloned().collect_vec();
1592 let other_ids = tracking.target.added_ids().cloned().collect_vec();
1593 Ok(revset::walk_revs(repo, &other_ids, &self_ids)?.count_estimate()?)
1594 })
1595 .copied()
1596 }
1597}
1598
1599impl Template for Rc<CommitRef> {
1601 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1602 write!(formatter.labeled("name"), "{}", self.name)?;
1603 if let Some(remote) = &self.remote {
1604 write!(formatter, "@")?;
1605 write!(formatter.labeled("remote"), "{remote}")?;
1606 }
1607 if self.has_conflict() {
1610 write!(formatter, "??")?;
1611 } else if self.is_local() && !self.synced {
1612 write!(formatter, "*")?;
1613 }
1614 Ok(())
1615 }
1616}
1617
1618impl Template for Vec<Rc<CommitRef>> {
1619 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1620 templater::format_joined(formatter, self, " ")
1621 }
1622}
1623
1624#[derive(Debug, Clone, serde::Serialize)]
1626pub struct WorkspaceRef {
1627 name: WorkspaceNameBuf,
1629 target: Commit,
1631}
1632
1633impl WorkspaceRef {
1634 pub fn new(name: WorkspaceNameBuf, target: Commit) -> Self {
1636 Self { name, target }
1637 }
1638
1639 pub fn name(&self) -> &WorkspaceName {
1641 self.name.as_ref()
1642 }
1643
1644 pub fn target(&self) -> &Commit {
1646 &self.target
1647 }
1648}
1649
1650impl Template for WorkspaceRef {
1651 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1652 write!(formatter, "{}@", self.name.as_symbol())
1653 }
1654}
1655
1656impl Template for Vec<WorkspaceRef> {
1657 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1658 templater::format_joined(formatter, self, " ")
1659 }
1660}
1661
1662fn builtin_workspace_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, WorkspaceRef> {
1663 let mut map = CommitTemplateBuildMethodFnMap::<WorkspaceRef>::new();
1664 map.insert(
1665 "name",
1666 |_language, _diagnostics, _build_ctx, self_property, function| {
1667 function.expect_no_arguments()?;
1668 let out_property = self_property.map(|ws_ref| RefSymbolBuf(ws_ref.name.into()));
1669 Ok(out_property.into_dyn_wrapped())
1670 },
1671 );
1672 map.insert(
1673 "target",
1674 |_language, _diagnostics, _build_ctx, self_property, function| {
1675 function.expect_no_arguments()?;
1676 let out_property = self_property.map(|ws_ref| ws_ref.target);
1677 Ok(out_property.into_dyn_wrapped())
1678 },
1679 );
1680 map
1681}
1682
1683fn serialize_tracking_target<S>(
1684 tracking_ref: &Option<TrackingRef>,
1685 serializer: S,
1686) -> Result<S::Ok, S::Error>
1687where
1688 S: serde::Serializer,
1689{
1690 let target = tracking_ref.as_ref().map(|tracking| &tracking.target);
1691 target.serialize(serializer)
1692}
1693
1694fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc<CommitRef>> {
1695 let mut map = CommitTemplateBuildMethodFnMap::<Rc<CommitRef>>::new();
1698 map.insert(
1699 "name",
1700 |_language, _diagnostics, _build_ctx, self_property, function| {
1701 function.expect_no_arguments()?;
1702 let out_property = self_property.map(|commit_ref| commit_ref.name.clone());
1703 Ok(out_property.into_dyn_wrapped())
1704 },
1705 );
1706 map.insert(
1707 "remote",
1708 |_language, _diagnostics, _build_ctx, self_property, function| {
1709 function.expect_no_arguments()?;
1710 let out_property = self_property.map(|commit_ref| commit_ref.remote.clone());
1711 Ok(out_property.into_dyn_wrapped())
1712 },
1713 );
1714 map.insert(
1715 "present",
1716 |_language, _diagnostics, _build_ctx, self_property, function| {
1717 function.expect_no_arguments()?;
1718 let out_property = self_property.map(|commit_ref| commit_ref.is_present());
1719 Ok(out_property.into_dyn_wrapped())
1720 },
1721 );
1722 map.insert(
1723 "conflict",
1724 |_language, _diagnostics, _build_ctx, self_property, function| {
1725 function.expect_no_arguments()?;
1726 let out_property = self_property.map(|commit_ref| commit_ref.has_conflict());
1727 Ok(out_property.into_dyn_wrapped())
1728 },
1729 );
1730 map.insert(
1731 "normal_target",
1732 |language, _diagnostics, _build_ctx, self_property, function| {
1733 function.expect_no_arguments()?;
1734 let repo = language.repo;
1735 let out_property = self_property.and_then(|commit_ref| {
1736 let maybe_id = commit_ref.target.as_normal();
1737 Ok(maybe_id.map(|id| repo.store().get_commit(id)).transpose()?)
1738 });
1739 Ok(out_property.into_dyn_wrapped())
1740 },
1741 );
1742 map.insert(
1743 "removed_targets",
1744 |language, _diagnostics, _build_ctx, self_property, function| {
1745 function.expect_no_arguments()?;
1746 let repo = language.repo;
1747 let out_property = self_property.and_then(|commit_ref| {
1748 let ids = commit_ref.target.removed_ids();
1749 let commits: Vec<_> = ids.map(|id| repo.store().get_commit(id)).try_collect()?;
1750 Ok(commits)
1751 });
1752 Ok(out_property.into_dyn_wrapped())
1753 },
1754 );
1755 map.insert(
1756 "added_targets",
1757 |language, _diagnostics, _build_ctx, self_property, function| {
1758 function.expect_no_arguments()?;
1759 let repo = language.repo;
1760 let out_property = self_property.and_then(|commit_ref| {
1761 let ids = commit_ref.target.added_ids();
1762 let commits: Vec<_> = ids.map(|id| repo.store().get_commit(id)).try_collect()?;
1763 Ok(commits)
1764 });
1765 Ok(out_property.into_dyn_wrapped())
1766 },
1767 );
1768 map.insert(
1769 "tracked",
1770 |_language, _diagnostics, _build_ctx, self_property, function| {
1771 function.expect_no_arguments()?;
1772 let out_property = self_property.map(|commit_ref| commit_ref.is_tracked());
1773 Ok(out_property.into_dyn_wrapped())
1774 },
1775 );
1776 map.insert(
1777 "tracking_present",
1778 |_language, _diagnostics, _build_ctx, self_property, function| {
1779 function.expect_no_arguments()?;
1780 let out_property = self_property.map(|commit_ref| commit_ref.is_tracking_present());
1781 Ok(out_property.into_dyn_wrapped())
1782 },
1783 );
1784 map.insert(
1785 "tracking_ahead_count",
1786 |language, _diagnostics, _build_ctx, self_property, function| {
1787 function.expect_no_arguments()?;
1788 let repo = language.repo;
1789 let out_property =
1790 self_property.and_then(|commit_ref| commit_ref.tracking_ahead_count(repo));
1791 Ok(out_property.into_dyn_wrapped())
1792 },
1793 );
1794 map.insert(
1795 "tracking_behind_count",
1796 |language, _diagnostics, _build_ctx, self_property, function| {
1797 function.expect_no_arguments()?;
1798 let repo = language.repo;
1799 let out_property =
1800 self_property.and_then(|commit_ref| commit_ref.tracking_behind_count(repo));
1801 Ok(out_property.into_dyn_wrapped())
1802 },
1803 );
1804 map.insert(
1805 "synced",
1806 |_language, _diagnostics, _build_ctx, self_property, function| {
1807 function.expect_no_arguments()?;
1808 let out_property = self_property.map(|commit_ref| commit_ref.synced);
1809 Ok(out_property.into_dyn_wrapped())
1810 },
1811 );
1812 map
1813}
1814
1815#[derive(Clone, Debug, Default)]
1817pub struct CommitRefsIndex {
1818 index: HashMap<CommitId, Vec<Rc<CommitRef>>>,
1819}
1820
1821impl CommitRefsIndex {
1822 fn insert<'a>(&mut self, ids: impl IntoIterator<Item = &'a CommitId>, name: Rc<CommitRef>) {
1823 for id in ids {
1824 let commit_refs = self.index.entry(id.clone()).or_default();
1825 commit_refs.push(name.clone());
1826 }
1827 }
1828
1829 pub fn get(&self, id: &CommitId) -> &[Rc<CommitRef>] {
1830 self.index.get(id).map_or(&[], |refs: &Vec<_>| refs)
1831 }
1832}
1833
1834fn build_local_remote_refs_index<'a>(
1835 local_remote_refs: impl IntoIterator<Item = (&'a RefName, LocalRemoteRefTarget<'a>)>,
1836) -> CommitRefsIndex {
1837 let mut index = CommitRefsIndex::default();
1838 for (name, target) in local_remote_refs {
1839 let local_target = target.local_target;
1840 let remote_refs = target.remote_refs;
1841 if local_target.is_present() {
1842 let commit_ref = CommitRef::local(
1843 name,
1844 local_target.clone(),
1845 remote_refs.iter().map(|&(_, remote_ref)| remote_ref),
1846 );
1847 index.insert(local_target.added_ids(), commit_ref);
1848 }
1849 for &(remote_name, remote_ref) in &remote_refs {
1850 let commit_ref = CommitRef::remote(name, remote_name, remote_ref.clone(), local_target);
1851 index.insert(remote_ref.target.added_ids(), commit_ref);
1852 }
1853 }
1854 index
1855}
1856
1857fn build_commit_refs_index<'a, K: Into<String>>(
1858 ref_pairs: impl IntoIterator<Item = (K, &'a RefTarget)>,
1859) -> CommitRefsIndex {
1860 let mut index = CommitRefsIndex::default();
1861 for (name, target) in ref_pairs {
1862 let commit_ref = CommitRef::local_only(name, target.clone());
1863 index.insert(target.added_ids(), commit_ref);
1864 }
1865 index
1866}
1867
1868fn collect_distinct_refs(commit_refs: &[Rc<CommitRef>]) -> Vec<Rc<CommitRef>> {
1869 commit_refs
1870 .iter()
1871 .filter(|commit_ref| commit_ref.is_local() || !commit_ref.synced)
1872 .cloned()
1873 .collect()
1874}
1875
1876fn collect_local_refs(commit_refs: &[Rc<CommitRef>]) -> Vec<Rc<CommitRef>> {
1877 commit_refs
1878 .iter()
1879 .filter(|commit_ref| commit_ref.is_local())
1880 .cloned()
1881 .collect()
1882}
1883
1884fn collect_remote_refs(commit_refs: &[Rc<CommitRef>]) -> Vec<Rc<CommitRef>> {
1885 commit_refs
1886 .iter()
1887 .filter(|commit_ref| commit_ref.is_remote())
1888 .cloned()
1889 .collect()
1890}
1891
1892#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
1894#[serde(transparent)]
1895pub struct RefSymbolBuf(String);
1896
1897impl AsRef<str> for RefSymbolBuf {
1898 fn as_ref(&self) -> &str {
1899 &self.0
1900 }
1901}
1902
1903impl Display for RefSymbolBuf {
1904 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1905 f.pad(&revset::format_symbol(&self.0))
1906 }
1907}
1908
1909impl Template for RefSymbolBuf {
1910 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1911 write!(formatter, "{self}")
1912 }
1913}
1914
1915impl Template for RepoPathBuf {
1916 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1917 write!(formatter, "{}", self.as_internal_file_string())
1918 }
1919}
1920
1921fn builtin_repo_path_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, RepoPathBuf> {
1922 let mut map = CommitTemplateBuildMethodFnMap::<RepoPathBuf>::new();
1925 map.insert(
1926 "absolute",
1927 |language, _diagnostics, _build_ctx, self_property, function| {
1928 function.expect_no_arguments()?;
1929 let path_converter = language.path_converter;
1930 let out_property = self_property.and_then(move |path| match path_converter {
1934 RepoPathUiConverter::Fs { cwd: _, base } => path
1935 .to_fs_path(base)?
1936 .into_os_string()
1937 .into_string()
1938 .map_err(|_| TemplatePropertyError("Invalid UTF-8 sequence in path".into())),
1939 });
1940 Ok(out_property.into_dyn_wrapped())
1941 },
1942 );
1943 map.insert(
1944 "display",
1945 |language, _diagnostics, _build_ctx, self_property, function| {
1946 function.expect_no_arguments()?;
1947 let path_converter = language.path_converter;
1948 let out_property = self_property.map(|path| path_converter.format_file_path(&path));
1949 Ok(out_property.into_dyn_wrapped())
1950 },
1951 );
1952 map.insert(
1953 "parent",
1954 |_language, _diagnostics, _build_ctx, self_property, function| {
1955 function.expect_no_arguments()?;
1956 let out_property = self_property.map(|path| Some(path.parent()?.to_owned()));
1957 Ok(out_property.into_dyn_wrapped())
1958 },
1959 );
1960 map
1961}
1962
1963trait ShortestIdPrefixLen {
1964 fn shortest_prefix_len(&self, repo: &dyn Repo, index: &IdPrefixIndex) -> IndexResult<usize>;
1965}
1966
1967impl ShortestIdPrefixLen for ChangeId {
1968 fn shortest_prefix_len(&self, repo: &dyn Repo, index: &IdPrefixIndex) -> IndexResult<usize> {
1969 index.shortest_change_prefix_len(repo, self)
1970 }
1971}
1972
1973impl Template for ChangeId {
1974 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
1975 write!(formatter, "{self}")
1976 }
1977}
1978
1979fn builtin_change_id_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, ChangeId> {
1980 let mut map = builtin_commit_or_change_id_methods::<ChangeId>();
1981 map.insert(
1982 "normal_hex",
1983 |_language, _diagnostics, _build_ctx, self_property, function| {
1984 function.expect_no_arguments()?;
1985 let out_property = self_property.map(|id| id.hex());
1989 Ok(out_property.into_dyn_wrapped())
1990 },
1991 );
1992 map
1993}
1994
1995impl ShortestIdPrefixLen for CommitId {
1996 fn shortest_prefix_len(&self, repo: &dyn Repo, index: &IdPrefixIndex) -> IndexResult<usize> {
1997 index.shortest_commit_prefix_len(repo, self)
1998 }
1999}
2000
2001impl Template for CommitId {
2002 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2003 write!(formatter, "{self}")
2004 }
2005}
2006
2007fn builtin_commit_id_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, CommitId> {
2008 let mut map = builtin_commit_or_change_id_methods::<CommitId>();
2009 map.insert(
2011 "normal_hex",
2012 |_language, diagnostics, _build_ctx, self_property, function| {
2013 diagnostics.add_warning(TemplateParseError::expression(
2014 "commit_id.normal_hex() is deprecated; use stringify(commit_id) instead",
2015 function.name_span,
2016 ));
2017 function.expect_no_arguments()?;
2018 let out_property = self_property.map(|id| id.hex());
2019 Ok(out_property.into_dyn_wrapped())
2020 },
2021 );
2022 map
2023}
2024
2025fn builtin_commit_or_change_id_methods<'repo, O>() -> CommitTemplateBuildMethodFnMap<'repo, O>
2026where
2027 O: Display + ShortestIdPrefixLen + 'repo,
2028{
2029 let mut map = CommitTemplateBuildMethodFnMap::<O>::new();
2032 map.insert(
2033 "short",
2034 |language, diagnostics, build_ctx, self_property, function| {
2035 let ([], [len_node]) = function.expect_arguments()?;
2036 let len_property = len_node
2037 .map(|node| {
2038 template_builder::expect_usize_expression(
2039 language,
2040 diagnostics,
2041 build_ctx,
2042 node,
2043 )
2044 })
2045 .transpose()?;
2046 let out_property = (self_property, len_property)
2047 .map(|(id, len)| format!("{id:.len$}", len = len.unwrap_or(12)));
2048 Ok(out_property.into_dyn_wrapped())
2049 },
2050 );
2051 map.insert(
2052 "shortest",
2053 |language, diagnostics, build_ctx, self_property, function| {
2054 let ([], [len_node]) = function.expect_arguments()?;
2055 let len_property = len_node
2056 .map(|node| {
2057 template_builder::expect_usize_expression(
2058 language,
2059 diagnostics,
2060 build_ctx,
2061 node,
2062 )
2063 })
2064 .transpose()?;
2065 let repo = language.repo;
2066 let index = match language.id_prefix_context.populate(repo) {
2067 Ok(index) => index,
2068 Err(err) => {
2069 diagnostics.add_warning(
2072 TemplateParseError::expression(
2073 "Failed to load short-prefixes index",
2074 function.name_span,
2075 )
2076 .with_source(err),
2077 );
2078 IdPrefixIndex::empty()
2079 }
2080 };
2081 let out_property = (self_property, len_property).and_then(move |(id, len)| {
2084 let prefix_len = id.shortest_prefix_len(repo, &index)?;
2085 let mut hex = format!("{id:.len$}", len = max(prefix_len, len.unwrap_or(0)));
2086 let rest = hex.split_off(prefix_len);
2087 Ok(ShortestIdPrefix { prefix: hex, rest })
2088 });
2089 Ok(out_property.into_dyn_wrapped())
2090 },
2091 );
2092 map
2093}
2094
2095#[derive(Clone, Debug, serde::Serialize)]
2096pub struct ShortestIdPrefix {
2097 pub prefix: String,
2098 pub rest: String,
2099}
2100
2101impl Template for ShortestIdPrefix {
2102 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2103 write!(formatter.labeled("prefix"), "{}", self.prefix)?;
2104 write!(formatter.labeled("rest"), "{}", self.rest)?;
2105 Ok(())
2106 }
2107}
2108
2109impl ShortestIdPrefix {
2110 fn to_upper(&self) -> Self {
2111 Self {
2112 prefix: self.prefix.to_ascii_uppercase(),
2113 rest: self.rest.to_ascii_uppercase(),
2114 }
2115 }
2116 fn to_lower(&self) -> Self {
2117 Self {
2118 prefix: self.prefix.to_ascii_lowercase(),
2119 rest: self.rest.to_ascii_lowercase(),
2120 }
2121 }
2122}
2123
2124fn builtin_shortest_id_prefix_methods<'repo>()
2125-> CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix> {
2126 let mut map = CommitTemplateBuildMethodFnMap::<ShortestIdPrefix>::new();
2129 map.insert(
2130 "prefix",
2131 |_language, _diagnostics, _build_ctx, self_property, function| {
2132 function.expect_no_arguments()?;
2133 let out_property = self_property.map(|id| id.prefix);
2134 Ok(out_property.into_dyn_wrapped())
2135 },
2136 );
2137 map.insert(
2138 "rest",
2139 |_language, _diagnostics, _build_ctx, self_property, function| {
2140 function.expect_no_arguments()?;
2141 let out_property = self_property.map(|id| id.rest);
2142 Ok(out_property.into_dyn_wrapped())
2143 },
2144 );
2145 map.insert(
2146 "upper",
2147 |_language, _diagnostics, _build_ctx, self_property, function| {
2148 function.expect_no_arguments()?;
2149 let out_property = self_property.map(|id| id.to_upper());
2150 Ok(out_property.into_dyn_wrapped())
2151 },
2152 );
2153 map.insert(
2154 "lower",
2155 |_language, _diagnostics, _build_ctx, self_property, function| {
2156 function.expect_no_arguments()?;
2157 let out_property = self_property.map(|id| id.to_lower());
2158 Ok(out_property.into_dyn_wrapped())
2159 },
2160 );
2161 map
2162}
2163
2164#[derive(Debug)]
2166pub struct TreeDiff {
2167 from_tree: MergedTree,
2168 to_tree: MergedTree,
2169 matcher: Rc<dyn Matcher>,
2170 copy_records: CopyRecords,
2171}
2172
2173impl TreeDiff {
2174 fn from_commit(
2175 repo: &dyn Repo,
2176 commit: &Commit,
2177 matcher: Rc<dyn Matcher>,
2178 ) -> BackendResult<Self> {
2179 let mut copy_records = CopyRecords::default();
2180 for parent in commit.parent_ids() {
2181 let records =
2182 diff_util::get_copy_records(repo.store(), parent, commit.id(), &*matcher)?;
2183 copy_records.add_records(records)?;
2184 }
2185 Ok(Self {
2186 from_tree: commit.parent_tree(repo)?,
2187 to_tree: commit.tree(),
2188 matcher,
2189 copy_records,
2190 })
2191 }
2192
2193 fn diff_stream(&self) -> BoxStream<'_, CopiesTreeDiffEntry> {
2194 self.from_tree
2195 .diff_stream_with_copies(&self.to_tree, &*self.matcher, &self.copy_records)
2196 }
2197
2198 async fn collect_entries(&self) -> BackendResult<Vec<TreeDiffEntry>> {
2199 self.diff_stream()
2200 .map(TreeDiffEntry::from_backend_entry_with_copies)
2201 .try_collect()
2202 .await
2203 }
2204
2205 fn into_formatted<F, E>(self, show: F) -> TreeDiffFormatted<F>
2206 where
2207 F: Fn(&mut dyn Formatter, &Store, BoxStream<CopiesTreeDiffEntry>) -> Result<(), E>,
2208 E: Into<TemplatePropertyError>,
2209 {
2210 TreeDiffFormatted { diff: self, show }
2211 }
2212}
2213
2214struct TreeDiffFormatted<F> {
2216 diff: TreeDiff,
2217 show: F,
2218}
2219
2220impl<F, E> Template for TreeDiffFormatted<F>
2221where
2222 F: Fn(&mut dyn Formatter, &Store, BoxStream<CopiesTreeDiffEntry>) -> Result<(), E>,
2223 E: Into<TemplatePropertyError>,
2224{
2225 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2226 let show = &self.show;
2227 let store = self.diff.from_tree.store();
2228 let tree_diff = self.diff.diff_stream();
2229 show(formatter.as_mut(), store, tree_diff).or_else(|err| formatter.handle_error(err.into()))
2230 }
2231}
2232
2233fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, TreeDiff> {
2234 type P<'repo> = CommitTemplatePropertyKind<'repo>;
2235 let mut map = CommitTemplateBuildMethodFnMap::<TreeDiff>::new();
2238 map.insert(
2239 "files",
2240 |_language, _diagnostics, _build_ctx, self_property, function| {
2241 function.expect_no_arguments()?;
2242 let out_property =
2244 self_property.and_then(|diff| Ok(diff.collect_entries().block_on()?));
2245 Ok(out_property.into_dyn_wrapped())
2246 },
2247 );
2248 map.insert(
2249 "color_words",
2250 |language, diagnostics, build_ctx, self_property, function| {
2251 let ([], [context_node]) = function.expect_arguments()?;
2252 let context_property = context_node
2253 .map(|node| {
2254 template_builder::expect_usize_expression(
2255 language,
2256 diagnostics,
2257 build_ctx,
2258 node,
2259 )
2260 })
2261 .transpose()?;
2262 let path_converter = language.path_converter;
2263 let options = diff_util::ColorWordsDiffOptions::from_settings(language.settings())
2264 .map_err(|err| {
2265 let message = "Failed to load diff settings";
2266 TemplateParseError::expression(message, function.name_span).with_source(err)
2267 })?;
2268 let conflict_marker_style = language.conflict_marker_style;
2269 let template = (self_property, context_property)
2270 .map(move |(diff, context)| {
2271 let mut options = options.clone();
2272 if let Some(context) = context {
2273 options.context = context;
2274 }
2275 diff.into_formatted(move |formatter, store, tree_diff| {
2276 diff_util::show_color_words_diff(
2277 formatter,
2278 store,
2279 tree_diff,
2280 path_converter,
2281 &options,
2282 conflict_marker_style,
2283 )
2284 .block_on()
2285 })
2286 })
2287 .into_template();
2288 Ok(P::wrap_template(template))
2289 },
2290 );
2291 map.insert(
2292 "git",
2293 |language, diagnostics, build_ctx, self_property, function| {
2294 let ([], [context_node]) = function.expect_arguments()?;
2295 let context_property = context_node
2296 .map(|node| {
2297 template_builder::expect_usize_expression(
2298 language,
2299 diagnostics,
2300 build_ctx,
2301 node,
2302 )
2303 })
2304 .transpose()?;
2305 let options = diff_util::UnifiedDiffOptions::from_settings(language.settings())
2306 .map_err(|err| {
2307 let message = "Failed to load diff settings";
2308 TemplateParseError::expression(message, function.name_span).with_source(err)
2309 })?;
2310 let conflict_marker_style = language.conflict_marker_style;
2311 let template = (self_property, context_property)
2312 .map(move |(diff, context)| {
2313 let mut options = options.clone();
2314 if let Some(context) = context {
2315 options.context = context;
2316 }
2317 diff.into_formatted(move |formatter, store, tree_diff| {
2318 diff_util::show_git_diff(
2319 formatter,
2320 store,
2321 tree_diff,
2322 &options,
2323 conflict_marker_style,
2324 )
2325 .block_on()
2326 })
2327 })
2328 .into_template();
2329 Ok(P::wrap_template(template))
2330 },
2331 );
2332 map.insert(
2333 "stat",
2334 |language, diagnostics, build_ctx, self_property, function| {
2335 let ([], [width_node]) = function.expect_arguments()?;
2336 let width_property = width_node
2337 .map(|node| {
2338 template_builder::expect_usize_expression(
2339 language,
2340 diagnostics,
2341 build_ctx,
2342 node,
2343 )
2344 })
2345 .transpose()?;
2346 let path_converter = language.path_converter;
2347 let options = diff_util::DiffStatOptions::default();
2349 let conflict_marker_style = language.conflict_marker_style;
2350 let out_property = (self_property, width_property).and_then(move |(diff, width)| {
2352 let store = diff.from_tree.store();
2353 let tree_diff = diff.diff_stream();
2354 let stats = DiffStats::calculate(store, tree_diff, &options, conflict_marker_style)
2355 .block_on()?;
2356 Ok(DiffStatsFormatted {
2357 stats,
2358 path_converter,
2359 width: width.unwrap_or(80),
2361 })
2362 });
2363 Ok(out_property.into_dyn_wrapped())
2364 },
2365 );
2366 map.insert(
2367 "summary",
2368 |language, _diagnostics, _build_ctx, self_property, function| {
2369 function.expect_no_arguments()?;
2370 let path_converter = language.path_converter;
2371 let template = self_property
2372 .map(move |diff| {
2373 diff.into_formatted(move |formatter, _store, tree_diff| {
2374 diff_util::show_diff_summary(formatter, tree_diff, path_converter)
2375 .block_on()
2376 })
2377 })
2378 .into_template();
2379 Ok(P::wrap_template(template))
2380 },
2381 );
2382 map
2384}
2385
2386#[derive(Clone, Debug)]
2388pub struct TreeDiffEntry {
2389 pub path: CopiesTreeDiffEntryPath,
2390 pub values: Diff<MergedTreeValue>,
2391}
2392
2393impl TreeDiffEntry {
2394 pub fn from_backend_entry_with_copies(entry: CopiesTreeDiffEntry) -> BackendResult<Self> {
2395 Ok(Self {
2396 path: entry.path,
2397 values: entry.values?,
2398 })
2399 }
2400
2401 fn status_label(&self) -> &'static str {
2402 diff_util::diff_status(&self.path, &self.values).label()
2403 }
2404
2405 fn into_source_entry(self) -> TreeEntry {
2406 TreeEntry {
2407 path: self.path.source.map_or(self.path.target, |(path, _)| path),
2408 value: self.values.before,
2409 }
2410 }
2411
2412 fn into_target_entry(self) -> TreeEntry {
2413 TreeEntry {
2414 path: self.path.target,
2415 value: self.values.after,
2416 }
2417 }
2418}
2419
2420fn builtin_tree_diff_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, TreeDiffEntry>
2421{
2422 let mut map = CommitTemplateBuildMethodFnMap::<TreeDiffEntry>::new();
2425 map.insert(
2426 "path",
2427 |_language, _diagnostics, _build_ctx, self_property, function| {
2428 function.expect_no_arguments()?;
2429 let out_property = self_property.map(|entry| entry.path.target);
2430 Ok(out_property.into_dyn_wrapped())
2431 },
2432 );
2433 map.insert(
2434 "status",
2435 |_language, _diagnostics, _build_ctx, self_property, function| {
2436 function.expect_no_arguments()?;
2437 let out_property = self_property.map(|entry| entry.status_label().to_owned());
2438 Ok(out_property.into_dyn_wrapped())
2439 },
2440 );
2441 map.insert(
2443 "source",
2444 |_language, _diagnostics, _build_ctx, self_property, function| {
2445 function.expect_no_arguments()?;
2446 let out_property = self_property.map(TreeDiffEntry::into_source_entry);
2447 Ok(out_property.into_dyn_wrapped())
2448 },
2449 );
2450 map.insert(
2451 "target",
2452 |_language, _diagnostics, _build_ctx, self_property, function| {
2453 function.expect_no_arguments()?;
2454 let out_property = self_property.map(TreeDiffEntry::into_target_entry);
2455 Ok(out_property.into_dyn_wrapped())
2456 },
2457 );
2458 map
2459}
2460
2461#[derive(Clone, Debug)]
2463pub struct TreeEntry {
2464 pub path: RepoPathBuf,
2465 pub value: MergedTreeValue,
2466}
2467
2468fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, TreeEntry> {
2469 let mut map = CommitTemplateBuildMethodFnMap::<TreeEntry>::new();
2472 map.insert(
2473 "path",
2474 |_language, _diagnostics, _build_ctx, self_property, function| {
2475 function.expect_no_arguments()?;
2476 let out_property = self_property.map(|entry| entry.path);
2477 Ok(out_property.into_dyn_wrapped())
2478 },
2479 );
2480 map.insert(
2481 "conflict",
2482 |_language, _diagnostics, _build_ctx, self_property, function| {
2483 function.expect_no_arguments()?;
2484 let out_property = self_property.map(|entry| !entry.value.is_resolved());
2485 Ok(out_property.into_dyn_wrapped())
2486 },
2487 );
2488 map.insert(
2489 "file_type",
2490 |_language, _diagnostics, _build_ctx, self_property, function| {
2491 function.expect_no_arguments()?;
2492 let out_property =
2493 self_property.map(|entry| describe_file_type(&entry.value).to_owned());
2494 Ok(out_property.into_dyn_wrapped())
2495 },
2496 );
2497 map.insert(
2498 "executable",
2499 |_language, _diagnostics, _build_ctx, self_property, function| {
2500 function.expect_no_arguments()?;
2501 let out_property =
2502 self_property.map(|entry| is_executable_file(&entry.value).unwrap_or_default());
2503 Ok(out_property.into_dyn_wrapped())
2504 },
2505 );
2506 map
2507}
2508
2509fn describe_file_type(value: &MergedTreeValue) -> &'static str {
2510 match value.as_resolved() {
2511 Some(Some(TreeValue::File { .. })) => "file",
2512 Some(Some(TreeValue::Symlink(_))) => "symlink",
2513 Some(Some(TreeValue::Tree(_))) => "tree",
2514 Some(Some(TreeValue::GitSubmodule(_))) => "git-submodule",
2515 Some(None) => "", None => "conflict",
2517 }
2518}
2519
2520fn is_executable_file(value: &MergedTreeValue) -> Option<bool> {
2521 let executable = value.to_executable_merge()?;
2522 conflicts::resolve_file_executable(&executable)
2523}
2524
2525#[derive(Clone, Debug)]
2527pub struct DiffStatsFormatted<'a> {
2528 stats: DiffStats,
2529 path_converter: &'a RepoPathUiConverter,
2530 width: usize,
2531}
2532
2533impl Template for DiffStatsFormatted<'_> {
2534 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2535 diff_util::show_diff_stats(
2536 formatter.as_mut(),
2537 &self.stats,
2538 self.path_converter,
2539 self.width,
2540 )
2541 }
2542}
2543
2544fn builtin_diff_stats_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, DiffStats> {
2545 let mut map = CommitTemplateBuildMethodFnMap::<DiffStats>::new();
2548 map.insert(
2549 "files",
2550 |_language, _diagnostics, _build_ctx, self_property, function| {
2551 function.expect_no_arguments()?;
2552 let out_property = self_property.and_then(|diff| Ok(diff.entries().to_vec()));
2553 Ok(out_property.into_dyn_wrapped())
2554 },
2555 );
2556 map.insert(
2557 "total_added",
2558 |_language, _diagnostics, _build_ctx, self_property, function| {
2559 function.expect_no_arguments()?;
2560 let out_property =
2561 self_property.and_then(|stats| Ok(i64::try_from(stats.count_total_added())?));
2562 Ok(out_property.into_dyn_wrapped())
2563 },
2564 );
2565 map.insert(
2566 "total_removed",
2567 |_language, _diagnostics, _build_ctx, self_property, function| {
2568 function.expect_no_arguments()?;
2569 let out_property =
2570 self_property.and_then(|stats| Ok(i64::try_from(stats.count_total_removed())?));
2571 Ok(out_property.into_dyn_wrapped())
2572 },
2573 );
2574 map
2575}
2576
2577fn builtin_diff_stat_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, DiffStatEntry>
2578{
2579 let mut map = CommitTemplateBuildMethodFnMap::<DiffStatEntry>::new();
2582 map.insert(
2583 "path",
2584 |_language, _diagnostics, _build_ctx, self_property, function| {
2585 function.expect_no_arguments()?;
2586 let out_property = self_property.map(|entry| entry.path.target);
2587 Ok(out_property.into_dyn_wrapped())
2588 },
2589 );
2590 map.insert(
2591 "status",
2592 |_language, _diagnostics, _build_ctx, self_property, function| {
2593 function.expect_no_arguments()?;
2594 let out_property = self_property.map(|entry| entry.status.label().to_owned());
2595 Ok(out_property.into_dyn_wrapped())
2596 },
2597 );
2598 map.insert(
2599 "status_char",
2600 |_language, _diagnostics, _build_ctx, self_property, function| {
2601 function.expect_no_arguments()?;
2602 let out_property = self_property.map(|entry| entry.status.char().to_string());
2603 Ok(out_property.into_dyn_wrapped())
2604 },
2605 );
2606 map.insert(
2607 "lines_added",
2608 |_language, _diagnostics, _build_ctx, self_property, function| {
2609 function.expect_no_arguments()?;
2610 let out_property = self_property.and_then(|entry| {
2611 Ok(i64::try_from(
2612 entry.added_removed.map_or(0, |(added, _)| added),
2613 )?)
2614 });
2615 Ok(out_property.into_dyn_wrapped())
2616 },
2617 );
2618 map.insert(
2619 "lines_removed",
2620 |_language, _diagnostics, _build_ctx, self_property, function| {
2621 function.expect_no_arguments()?;
2622 let out_property = self_property.and_then(|entry| {
2623 Ok(i64::try_from(
2624 entry.added_removed.map_or(0, |(_, removed)| removed),
2625 )?)
2626 });
2627 Ok(out_property.into_dyn_wrapped())
2628 },
2629 );
2630 map.insert(
2631 "bytes_delta",
2632 |_language, _diagnostics, _build_ctx, self_property, function| {
2633 function.expect_no_arguments()?;
2634 let out_property =
2635 self_property.and_then(|entry| Ok(i64::try_from(entry.bytes_delta)?));
2636 Ok(out_property.into_dyn_wrapped())
2637 },
2638 );
2639 map
2640}
2641
2642#[derive(Debug)]
2643pub struct CryptographicSignature {
2644 commit: Commit,
2645}
2646
2647impl CryptographicSignature {
2648 fn new(commit: Commit) -> Option<Self> {
2649 commit.is_signed().then_some(Self { commit })
2650 }
2651
2652 fn verify(&self) -> SignResult<Verification> {
2653 self.commit
2654 .verification()
2655 .transpose()
2656 .expect("must have signature")
2657 }
2658
2659 fn status(&self) -> SignResult<SigStatus> {
2660 self.verify().map(|verification| verification.status)
2661 }
2662
2663 fn key(&self) -> SignResult<String> {
2665 self.verify()
2666 .map(|verification| verification.key.unwrap_or_default())
2667 }
2668
2669 fn display(&self) -> SignResult<String> {
2671 self.verify()
2672 .map(|verification| verification.display.unwrap_or_default())
2673 }
2674}
2675
2676fn builtin_cryptographic_signature_methods<'repo>()
2677-> CommitTemplateBuildMethodFnMap<'repo, CryptographicSignature> {
2678 let mut map = CommitTemplateBuildMethodFnMap::<CryptographicSignature>::new();
2681 map.insert(
2682 "status",
2683 |_language, _diagnostics, _build_ctx, self_property, function| {
2684 function.expect_no_arguments()?;
2685 let out_property = self_property.and_then(|sig| match sig.status() {
2686 Ok(status) => Ok(status.to_string()),
2687 Err(SignError::InvalidSignatureFormat) => Ok("invalid".to_string()),
2688 Err(err) => Err(err.into()),
2689 });
2690 Ok(out_property.into_dyn_wrapped())
2691 },
2692 );
2693 map.insert(
2694 "key",
2695 |_language, _diagnostics, _build_ctx, self_property, function| {
2696 function.expect_no_arguments()?;
2697 let out_property = self_property.and_then(|sig| Ok(sig.key()?));
2698 Ok(out_property.into_dyn_wrapped())
2699 },
2700 );
2701 map.insert(
2702 "display",
2703 |_language, _diagnostics, _build_ctx, self_property, function| {
2704 function.expect_no_arguments()?;
2705 let out_property = self_property.and_then(|sig| Ok(sig.display()?));
2706 Ok(out_property.into_dyn_wrapped())
2707 },
2708 );
2709 map
2710}
2711
2712#[derive(Debug, Clone)]
2713pub struct AnnotationLine {
2714 pub commit: Commit,
2715 pub content: BString,
2716 pub line_number: usize,
2717 pub original_line_number: usize,
2718 pub first_line_in_hunk: bool,
2719}
2720
2721fn builtin_annotation_line_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, AnnotationLine>
2722{
2723 type P<'repo> = CommitTemplatePropertyKind<'repo>;
2724 let mut map = CommitTemplateBuildMethodFnMap::<AnnotationLine>::new();
2725 map.insert(
2726 "commit",
2727 |_language, _diagnostics, _build_ctx, self_property, function| {
2728 function.expect_no_arguments()?;
2729 let out_property = self_property.map(|line| line.commit);
2730 Ok(out_property.into_dyn_wrapped())
2731 },
2732 );
2733 map.insert(
2734 "content",
2735 |_language, _diagnostics, _build_ctx, self_property, function| {
2736 function.expect_no_arguments()?;
2737 let out_property = self_property.map(|line| line.content);
2738 Ok(P::wrap_template(out_property.into_template()))
2740 },
2741 );
2742 map.insert(
2743 "line_number",
2744 |_language, _diagnostics, _build_ctx, self_property, function| {
2745 function.expect_no_arguments()?;
2746 let out_property = self_property.and_then(|line| Ok(i64::try_from(line.line_number)?));
2747 Ok(out_property.into_dyn_wrapped())
2748 },
2749 );
2750 map.insert(
2751 "original_line_number",
2752 |_language, _diagnostics, _build_ctx, self_property, function| {
2753 function.expect_no_arguments()?;
2754 let out_property =
2755 self_property.and_then(|line| Ok(i64::try_from(line.original_line_number)?));
2756 Ok(out_property.into_dyn_wrapped())
2757 },
2758 );
2759 map.insert(
2760 "first_line_in_hunk",
2761 |_language, _diagnostics, _build_ctx, self_property, function| {
2762 function.expect_no_arguments()?;
2763 let out_property = self_property.map(|line| line.first_line_in_hunk);
2764 Ok(out_property.into_dyn_wrapped())
2765 },
2766 );
2767 map
2768}
2769
2770impl Template for Trailer {
2771 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2772 write!(formatter, "{}: {}", self.key, self.value)
2773 }
2774}
2775
2776impl Template for Vec<Trailer> {
2777 fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
2778 templater::format_joined(formatter, self, "\n")
2779 }
2780}
2781
2782fn builtin_trailer_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Trailer> {
2783 let mut map = CommitTemplateBuildMethodFnMap::<Trailer>::new();
2784 map.insert(
2785 "key",
2786 |_language, _diagnostics, _build_ctx, self_property, function| {
2787 function.expect_no_arguments()?;
2788 let out_property = self_property.map(|trailer| trailer.key);
2789 Ok(out_property.into_dyn_wrapped())
2790 },
2791 );
2792 map.insert(
2793 "value",
2794 |_language, _diagnostics, _build_ctx, self_property, function| {
2795 function.expect_no_arguments()?;
2796 let out_property = self_property.map(|trailer| trailer.value);
2797 Ok(out_property.into_dyn_wrapped())
2798 },
2799 );
2800 map
2801}
2802
2803fn builtin_trailer_list_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Vec<Trailer>> {
2804 let mut map: CommitTemplateBuildMethodFnMap<Vec<Trailer>> =
2805 template_builder::builtin_formattable_list_methods();
2806 map.insert(
2807 "contains_key",
2808 |language, diagnostics, build_ctx, self_property, function| {
2809 let [key_node] = function.expect_exact_arguments()?;
2810 let key_property =
2811 expect_stringify_expression(language, diagnostics, build_ctx, key_node)?;
2812 let out_property = (self_property, key_property)
2813 .map(|(trailers, key)| trailers.iter().any(|t| t.key == key));
2814 Ok(out_property.into_dyn_wrapped())
2815 },
2816 );
2817 map
2818}
2819
2820#[cfg(test)]
2821mod tests {
2822 use std::path::Component;
2823 use std::path::Path;
2824 use std::path::PathBuf;
2825 use std::sync::Arc;
2826
2827 use jj_lib::config::ConfigLayer;
2828 use jj_lib::config::ConfigSource;
2829 use jj_lib::revset::RevsetAliasesMap;
2830 use jj_lib::revset::RevsetExpression;
2831 use jj_lib::revset::RevsetExtensions;
2832 use jj_lib::revset::RevsetWorkspaceContext;
2833 use testutils::TestRepoBackend;
2834 use testutils::TestWorkspace;
2835 use testutils::repo_path_buf;
2836
2837 use super::*;
2838 use crate::template_parser::TemplateAliasesMap;
2839 use crate::templater::TemplateRenderer;
2840 use crate::templater::WrapTemplateProperty;
2841
2842 type BuildFunctionFn = for<'a> fn(
2844 &CommitTemplateLanguage<'a>,
2845 &mut TemplateDiagnostics,
2846 &BuildContext<CommitTemplatePropertyKind<'a>>,
2847 &FunctionCallNode,
2848 ) -> TemplateParseResult<CommitTemplatePropertyKind<'a>>;
2849
2850 struct CommitTemplateTestEnv {
2851 test_workspace: TestWorkspace,
2852 path_converter: RepoPathUiConverter,
2853 revset_extensions: Arc<RevsetExtensions>,
2854 id_prefix_context: IdPrefixContext,
2855 revset_aliases_map: RevsetAliasesMap,
2856 template_aliases_map: TemplateAliasesMap,
2857 immutable_expression: Arc<UserRevsetExpression>,
2858 extra_functions: HashMap<&'static str, BuildFunctionFn>,
2859 }
2860
2861 impl CommitTemplateTestEnv {
2862 fn init() -> Self {
2863 let settings = stable_settings();
2865 let test_workspace =
2866 TestWorkspace::init_with_backend_and_settings(TestRepoBackend::Git, &settings);
2867 let path_converter = RepoPathUiConverter::Fs {
2868 cwd: test_workspace.workspace.workspace_root().to_owned(),
2869 base: test_workspace.workspace.workspace_root().to_owned(),
2870 };
2871 let revset_extensions = Arc::new(RevsetExtensions::new());
2872 let id_prefix_context = IdPrefixContext::new(revset_extensions.clone());
2873 Self {
2874 test_workspace,
2875 path_converter,
2876 revset_extensions,
2877 id_prefix_context,
2878 revset_aliases_map: RevsetAliasesMap::new(),
2879 template_aliases_map: TemplateAliasesMap::new(),
2880 immutable_expression: RevsetExpression::none(),
2881 extra_functions: HashMap::new(),
2882 }
2883 }
2884
2885 fn set_base_and_cwd(&mut self, base: PathBuf, cwd: impl AsRef<Path>) {
2886 self.path_converter = RepoPathUiConverter::Fs {
2887 cwd: base.join(cwd),
2888 base,
2889 };
2890 }
2891
2892 fn add_function(&mut self, name: &'static str, f: BuildFunctionFn) {
2893 self.extra_functions.insert(name, f);
2894 }
2895
2896 fn new_language(&self) -> CommitTemplateLanguage<'_> {
2897 let revset_parse_context = RevsetParseContext {
2898 aliases_map: &self.revset_aliases_map,
2899 local_variables: HashMap::new(),
2900 user_email: "test.user@example.com",
2901 date_pattern_context: chrono::DateTime::UNIX_EPOCH.fixed_offset().into(),
2902 default_ignored_remote: None,
2903 use_glob_by_default: false,
2904 extensions: &self.revset_extensions,
2905 workspace: Some(RevsetWorkspaceContext {
2906 path_converter: &self.path_converter,
2907 workspace_name: self.test_workspace.workspace.workspace_name(),
2908 }),
2909 };
2910 let mut language = CommitTemplateLanguage::new(
2911 self.test_workspace.repo.as_ref(),
2912 &self.path_converter,
2913 self.test_workspace.workspace.workspace_name(),
2914 revset_parse_context,
2915 &self.id_prefix_context,
2916 self.immutable_expression.clone(),
2917 ConflictMarkerStyle::Diff,
2918 &[] as &[Box<dyn CommitTemplateLanguageExtension>],
2919 );
2920 for (&name, &f) in &self.extra_functions {
2922 language.build_fn_table.core.functions.insert(name, f);
2923 }
2924 language
2925 }
2926
2927 fn parse<'a, C>(&'a self, text: &str) -> TemplateParseResult<TemplateRenderer<'a, C>>
2928 where
2929 C: Clone + 'a,
2930 CommitTemplatePropertyKind<'a>: WrapTemplateProperty<'a, C>,
2931 {
2932 let language = self.new_language();
2933 let mut diagnostics = TemplateDiagnostics::new();
2934 template_builder::parse(
2935 &language,
2936 &mut diagnostics,
2937 text,
2938 &self.template_aliases_map,
2939 )
2940 }
2941
2942 fn render_ok<'a, C>(&'a self, text: &str, context: &C) -> String
2943 where
2944 C: Clone + 'a,
2945 CommitTemplatePropertyKind<'a>: WrapTemplateProperty<'a, C>,
2946 {
2947 let template = self.parse(text).unwrap();
2948 let output = template.format_plain_text(context);
2949 String::from_utf8(output).unwrap()
2950 }
2951 }
2952
2953 fn stable_settings() -> UserSettings {
2954 let mut config = testutils::base_user_config();
2955 let mut layer = ConfigLayer::empty(ConfigSource::User);
2956 layer
2957 .set_value("debug.commit-timestamp", "2001-02-03T04:05:06+07:00")
2958 .unwrap();
2959 config.add_layer(layer);
2960 UserSettings::from_config(config).unwrap()
2961 }
2962
2963 #[test]
2964 fn test_ref_symbol_type() {
2965 let mut env = CommitTemplateTestEnv::init();
2966 env.add_function("sym", |language, diagnostics, build_ctx, function| {
2967 let [value_node] = function.expect_exact_arguments()?;
2968 let value = expect_stringify_expression(language, diagnostics, build_ctx, value_node)?;
2969 let out_property = value.map(RefSymbolBuf);
2970 Ok(out_property.into_dyn_wrapped())
2971 });
2972 let sym = |s: &str| RefSymbolBuf(s.to_owned());
2973
2974 insta::assert_snapshot!(env.render_ok("self", &sym("")), @r#""""#);
2976 insta::assert_snapshot!(env.render_ok("self", &sym("foo")), @"foo");
2977 insta::assert_snapshot!(env.render_ok("self", &sym("foo bar")), @r#""foo bar""#);
2978
2979 insta::assert_snapshot!(env.render_ok("self == 'foo'", &sym("foo")), @"true");
2981 insta::assert_snapshot!(env.render_ok("'bar' == self", &sym("foo")), @"false");
2982 insta::assert_snapshot!(env.render_ok("self == self", &sym("foo")), @"true");
2983 insta::assert_snapshot!(env.render_ok("self == sym('bar')", &sym("foo")), @"false");
2984
2985 insta::assert_snapshot!(env.render_ok("self == 'bar'", &Some(sym("foo"))), @"false");
2986 insta::assert_snapshot!(env.render_ok("self == sym('foo')", &Some(sym("foo"))), @"true");
2987 insta::assert_snapshot!(env.render_ok("'foo' == self", &Some(sym("foo"))), @"true");
2988 insta::assert_snapshot!(env.render_ok("sym('bar') == self", &Some(sym("foo"))), @"false");
2989 insta::assert_snapshot!(env.render_ok("self == self", &Some(sym("foo"))), @"true");
2990 insta::assert_snapshot!(env.render_ok("self == ''", &None::<RefSymbolBuf>), @"false");
2991 insta::assert_snapshot!(env.render_ok("sym('') == self", &None::<RefSymbolBuf>), @"false");
2992 insta::assert_snapshot!(env.render_ok("self == self", &None::<RefSymbolBuf>), @"true");
2993
2994 insta::assert_snapshot!(env.render_ok("stringify(self)", &sym("a b")), @"a b");
2997 insta::assert_snapshot!(env.render_ok("stringify(self)", &Some(sym("a b"))), @"a b");
2998 insta::assert_snapshot!(env.render_ok("stringify(self)", &None::<RefSymbolBuf>), @"");
2999
3000 insta::assert_snapshot!(env.render_ok("self.len()", &sym("a b")), @"3");
3002
3003 insta::assert_snapshot!(env.render_ok("json(self)", &sym("foo bar")), @r#""foo bar""#);
3005 }
3006
3007 #[test]
3008 fn test_repo_path_type() {
3009 let mut env = CommitTemplateTestEnv::init();
3010 let mut base = PathBuf::from(Component::RootDir.as_os_str());
3011 base.extend(["path", "to", "repo"]);
3012 env.set_base_and_cwd(base, "dir");
3013
3014 insta::assert_snapshot!(
3016 env.render_ok("self", &repo_path_buf("dir/file")), @"dir/file");
3017
3018 if cfg!(windows) {
3020 insta::assert_snapshot!(
3021 env.render_ok("self.absolute()", &repo_path_buf("file")),
3022 @"\\path\\to\\repo\\file");
3023 insta::assert_snapshot!(
3024 env.render_ok("self.absolute()", &repo_path_buf("dir/file")),
3025 @"\\path\\to\\repo\\dir\\file");
3026 } else {
3027 insta::assert_snapshot!(
3028 env.render_ok("self.absolute()", &repo_path_buf("file")), @"/path/to/repo/file");
3029 insta::assert_snapshot!(
3030 env.render_ok("self.absolute()", &repo_path_buf("dir/file")),
3031 @"/path/to/repo/dir/file");
3032 }
3033
3034 insta::assert_snapshot!(
3036 env.render_ok("self.display()", &repo_path_buf("dir/file")), @"file");
3037 if cfg!(windows) {
3038 insta::assert_snapshot!(
3039 env.render_ok("self.display()", &repo_path_buf("file")), @"..\\file");
3040 } else {
3041 insta::assert_snapshot!(
3042 env.render_ok("self.display()", &repo_path_buf("file")), @"../file");
3043 }
3044
3045 let template = "if(self.parent(), self.parent(), '<none>')";
3046 insta::assert_snapshot!(env.render_ok(template, &repo_path_buf("")), @"<none>");
3047 insta::assert_snapshot!(env.render_ok(template, &repo_path_buf("file")), @"");
3048 insta::assert_snapshot!(env.render_ok(template, &repo_path_buf("dir/file")), @"dir");
3049
3050 insta::assert_snapshot!(
3052 env.render_ok("json(self)", &repo_path_buf("dir/file")), @r#""dir/file""#);
3053 insta::assert_snapshot!(
3054 env.render_ok("json(self)", &None::<RepoPathBuf>), @"null");
3055 }
3056
3057 #[test]
3058 fn test_commit_id_type() {
3059 let env = CommitTemplateTestEnv::init();
3060
3061 let id = CommitId::from_hex("08a70ab33d7143b7130ed8594d8216ef688623c0");
3062 insta::assert_snapshot!(
3063 env.render_ok("self", &id), @"08a70ab33d7143b7130ed8594d8216ef688623c0");
3064 insta::assert_snapshot!(
3065 env.render_ok("self.normal_hex()", &id), @"08a70ab33d7143b7130ed8594d8216ef688623c0");
3066
3067 insta::assert_snapshot!(env.render_ok("self.short()", &id), @"08a70ab33d71");
3068 insta::assert_snapshot!(env.render_ok("self.short(0)", &id), @"");
3069 insta::assert_snapshot!(env.render_ok("self.short(-0)", &id), @"");
3070 insta::assert_snapshot!(
3071 env.render_ok("self.short(100)", &id), @"08a70ab33d7143b7130ed8594d8216ef688623c0");
3072 insta::assert_snapshot!(
3073 env.render_ok("self.short(-100)", &id),
3074 @"<Error: out of range integral type conversion attempted>");
3075
3076 insta::assert_snapshot!(env.render_ok("self.shortest()", &id), @"08");
3077 insta::assert_snapshot!(env.render_ok("self.shortest(0)", &id), @"08");
3078 insta::assert_snapshot!(env.render_ok("self.shortest(-0)", &id), @"08");
3079 insta::assert_snapshot!(
3080 env.render_ok("self.shortest(100)", &id), @"08a70ab33d7143b7130ed8594d8216ef688623c0");
3081 insta::assert_snapshot!(
3082 env.render_ok("self.shortest(-100)", &id),
3083 @"<Error: out of range integral type conversion attempted>");
3084
3085 insta::assert_snapshot!(
3087 env.render_ok("json(self)", &id), @r#""08a70ab33d7143b7130ed8594d8216ef688623c0""#);
3088 }
3089
3090 #[test]
3091 fn test_change_id_type() {
3092 let env = CommitTemplateTestEnv::init();
3093
3094 let id = ChangeId::from_hex("ffdaa62087a280bddc5e3d3ff933b8ae");
3095 insta::assert_snapshot!(
3096 env.render_ok("self", &id), @"kkmpptxzrspxrzommnulwmwkkqwworpl");
3097 insta::assert_snapshot!(
3098 env.render_ok("self.normal_hex()", &id), @"ffdaa62087a280bddc5e3d3ff933b8ae");
3099
3100 insta::assert_snapshot!(env.render_ok("self.short()", &id), @"kkmpptxzrspx");
3101 insta::assert_snapshot!(env.render_ok("self.short(0)", &id), @"");
3102 insta::assert_snapshot!(env.render_ok("self.short(-0)", &id), @"");
3103 insta::assert_snapshot!(
3104 env.render_ok("self.short(100)", &id), @"kkmpptxzrspxrzommnulwmwkkqwworpl");
3105 insta::assert_snapshot!(
3106 env.render_ok("self.short(-100)", &id),
3107 @"<Error: out of range integral type conversion attempted>");
3108
3109 insta::assert_snapshot!(env.render_ok("self.shortest()", &id), @"k");
3110 insta::assert_snapshot!(env.render_ok("self.shortest(0)", &id), @"k");
3111 insta::assert_snapshot!(env.render_ok("self.shortest(-0)", &id), @"k");
3112 insta::assert_snapshot!(
3113 env.render_ok("self.shortest(100)", &id), @"kkmpptxzrspxrzommnulwmwkkqwworpl");
3114 insta::assert_snapshot!(
3115 env.render_ok("self.shortest(-100)", &id),
3116 @"<Error: out of range integral type conversion attempted>");
3117
3118 insta::assert_snapshot!(
3120 env.render_ok("json(self)", &id), @r#""kkmpptxzrspxrzommnulwmwkkqwworpl""#);
3121 }
3122
3123 #[test]
3124 fn test_shortest_id_prefix_type() {
3125 let env = CommitTemplateTestEnv::init();
3126
3127 let id = ShortestIdPrefix {
3128 prefix: "012".to_owned(),
3129 rest: "3abcdef".to_owned(),
3130 };
3131
3132 insta::assert_snapshot!(
3134 env.render_ok("json(self)", &id), @r#"{"prefix":"012","rest":"3abcdef"}"#);
3135 }
3136}