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