1use std::collections::HashMap;
18use std::io;
19use std::sync::Arc;
20
21use futures::StreamExt as _;
22use futures::TryStreamExt as _;
23use futures::stream::LocalBoxStream;
24use itertools::Itertools as _;
25use jj_lib::backend::CommitId;
26use jj_lib::commit::Commit;
27use jj_lib::config::ConfigNamePathBuf;
28use jj_lib::config::ConfigSource;
29use jj_lib::config::StackedConfig;
30use jj_lib::id_prefix::IdPrefixContext;
31use jj_lib::ref_name::RefNameBuf;
32use jj_lib::ref_name::RemoteName;
33use jj_lib::ref_name::RemoteNameBuf;
34use jj_lib::ref_name::RemoteRefSymbolBuf;
35use jj_lib::repo::Repo;
36use jj_lib::revset;
37use jj_lib::revset::ResolvedRevsetExpression;
38use jj_lib::revset::Revset;
39use jj_lib::revset::RevsetDiagnostics;
40use jj_lib::revset::RevsetEvaluationError;
41use jj_lib::revset::RevsetExpression;
42use jj_lib::revset::RevsetExtensions;
43use jj_lib::revset::RevsetParseContext;
44use jj_lib::revset::RevsetParseError;
45use jj_lib::revset::RevsetResolutionError;
46use jj_lib::revset::RevsetStreamExt as _;
47use jj_lib::revset::SymbolResolver;
48use jj_lib::revset::SymbolResolverExtension;
49use jj_lib::revset::UserRevsetExpression;
50use jj_lib::settings::RemoteSettingsMap;
51use jj_lib::str_util::StringExpression;
52use jj_lib::str_util::StringMatcher;
53use thiserror::Error;
54
55use crate::command_error::CommandError;
56use crate::command_error::config_error_with_message;
57use crate::command_error::print_parse_diagnostics;
58use crate::command_error::revset_parse_error_hint;
59use crate::command_error::user_error;
60use crate::command_error::user_error_with_message;
61use crate::formatter::Formatter;
62use crate::templater::TemplateRenderer;
63use crate::ui::Ui;
64
65const USER_IMMUTABLE_HEADS: &str = "immutable_heads";
66
67#[derive(Debug, Error)]
68pub enum UserRevsetEvaluationError {
69 #[error(transparent)]
70 Resolution(RevsetResolutionError),
71 #[error(transparent)]
72 Evaluation(RevsetEvaluationError),
73}
74
75pub struct RevsetExpressionEvaluator<'repo> {
77 repo: &'repo dyn Repo,
78 extensions: Arc<RevsetExtensions>,
79 id_prefix_context: &'repo IdPrefixContext,
80 expression: Arc<UserRevsetExpression>,
81}
82
83impl<'repo> RevsetExpressionEvaluator<'repo> {
84 pub fn new(
85 repo: &'repo dyn Repo,
86 extensions: Arc<RevsetExtensions>,
87 id_prefix_context: &'repo IdPrefixContext,
88 expression: Arc<UserRevsetExpression>,
89 ) -> Self {
90 Self {
91 repo,
92 extensions,
93 id_prefix_context,
94 expression,
95 }
96 }
97
98 pub fn expression(&self) -> &Arc<UserRevsetExpression> {
100 &self.expression
101 }
102
103 pub fn intersect_with(&mut self, other: &Arc<UserRevsetExpression>) {
105 self.expression = self.expression.intersection(other);
106 }
107
108 pub fn resolve(&self) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
110 let symbol_resolver = default_symbol_resolver(
111 self.repo,
112 self.extensions.symbol_resolvers(),
113 self.id_prefix_context,
114 );
115 self.expression
116 .resolve_user_expression(self.repo, &symbol_resolver)
117 }
118
119 pub fn evaluate(&self) -> Result<Box<dyn Revset + 'repo>, UserRevsetEvaluationError> {
121 self.resolve()
122 .map_err(UserRevsetEvaluationError::Resolution)?
123 .evaluate(self.repo)
124 .map_err(UserRevsetEvaluationError::Evaluation)
125 }
126
127 pub fn evaluate_to_commit_ids(
130 &self,
131 ) -> Result<
132 LocalBoxStream<'repo, Result<CommitId, RevsetEvaluationError>>,
133 UserRevsetEvaluationError,
134 > {
135 Ok(self.evaluate()?.stream())
136 }
137
138 pub fn evaluate_to_commits(
141 &self,
142 ) -> Result<
143 LocalBoxStream<'repo, Result<Commit, RevsetEvaluationError>>,
144 UserRevsetEvaluationError,
145 > {
146 Ok(self
147 .evaluate()?
148 .stream()
149 .commits(self.repo.store())
150 .boxed_local())
151 }
152}
153
154pub(super) fn warn_user_redefined_builtin(
155 ui: &Ui,
156 config: &StackedConfig,
157 table_name: &ConfigNamePathBuf,
158) -> io::Result<()> {
159 let checked_mutability_builtins = ["mutable()", "immutable()", "builtin_immutable_heads()"];
160 for layer in config
161 .layers()
162 .iter()
163 .skip_while(|layer| layer.source == ConfigSource::Default)
164 {
165 let Ok(Some(table)) = layer.look_up_table(table_name) else {
166 continue;
167 };
168 for decl in checked_mutability_builtins
169 .iter()
170 .filter(|decl| table.contains_key(decl))
171 {
172 writeln!(
173 ui.warning_default(),
174 "Redefining `{table_name}.{decl}` is not recommended; redefine \
175 `immutable_heads()` instead.",
176 )?;
177 }
178 }
179 Ok(())
180}
181
182pub fn default_symbol_resolver<'a>(
185 repo: &'a dyn Repo,
186 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
187 id_prefix_context: &'a IdPrefixContext,
188) -> SymbolResolver<'a> {
189 SymbolResolver::new(repo, extensions).with_id_prefix_context(id_prefix_context)
190}
191
192pub fn parse_immutable_heads_expression(
195 diagnostics: &mut RevsetDiagnostics,
196 context: &RevsetParseContext,
197) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
198 let (_, _, immutable_heads_str, _) = context
199 .aliases_map
200 .get_function(USER_IMMUTABLE_HEADS, 0)
201 .unwrap();
202 let heads = revset::parse(diagnostics, immutable_heads_str, context)?;
203 Ok(heads.union(&RevsetExpression::root()))
204}
205
206pub(super) fn try_resolve_trunk_alias(
211 repo: &dyn Repo,
212 context: &RevsetParseContext,
213) -> Result<Option<Arc<ResolvedRevsetExpression>>, RevsetResolutionError> {
214 let (_, _, revset_str, _) = context
215 .aliases_map
216 .get_function("trunk", 0)
217 .expect("trunk() should be defined by default");
218 let Ok(expression) = revset::parse(&mut RevsetDiagnostics::new(), revset_str, context) else {
219 return Ok(None);
220 };
221 let symbol_resolver = SymbolResolver::new(repo, context.extensions.symbol_resolvers());
224 let resolved = expression.resolve_user_expression(repo, &symbol_resolver)?;
225 Ok(Some(resolved))
226}
227
228#[derive(Debug)]
230pub enum RevsetEvaluationSizeError {
231 Empty,
233 Multiple(Vec<Commit>, bool),
237 Other(UserRevsetEvaluationError),
239}
240
241impl RevsetEvaluationSizeError {
242 pub fn to_command_error(
243 self,
244 revision_str: &str,
245 commit_summary_template: &TemplateRenderer<'_, Commit>,
246 ) -> CommandError {
247 match self {
248 Self::Empty => user_error(format!(
249 "Revset `{revision_str}` didn't resolve to any revisions"
250 )),
251 Self::Multiple(commits, has_more) => format_multiple_revisions_error(
252 revision_str,
253 &commits,
254 has_more,
255 commit_summary_template,
256 ),
257 Self::Other(error) => error.into(),
258 }
259 }
260}
261
262pub(super) async fn evaluate_revset_to_single_commit(
263 expression: &RevsetExpressionEvaluator<'_>,
264) -> Result<Commit, RevsetEvaluationSizeError> {
265 let max_commits = 5;
268 let mut commits: Vec<_> = expression
269 .evaluate_to_commits()
270 .map_err(RevsetEvaluationSizeError::Other)?
271 .take(max_commits + 1)
272 .try_collect()
273 .await
274 .map_err(UserRevsetEvaluationError::Evaluation)
275 .map_err(RevsetEvaluationSizeError::Other)?;
276 match commits.as_slice() {
277 [commit] => Ok(commit.clone()),
278 [] => Err(RevsetEvaluationSizeError::Empty),
279 _ => {
280 let has_more = commits.len() > max_commits;
281 commits.truncate(max_commits);
282 Err(RevsetEvaluationSizeError::Multiple(commits, has_more))
283 }
284 }
285}
286
287fn format_multiple_revisions_error(
288 revision_str: &str,
289 commits: &[Commit],
290 elided: bool,
291 template: &TemplateRenderer<'_, Commit>,
292) -> CommandError {
293 assert!(commits.len() >= 2);
294 let mut cmd_err = user_error(format!(
295 "Revset `{revision_str}` resolved to more than one revision"
296 ));
297 let write_commits_summary = |formatter: &mut dyn Formatter| {
298 for commit in commits {
299 write!(formatter, " ")?;
300 template.format(commit, formatter)?;
301 writeln!(formatter)?;
302 }
303 if elided {
304 writeln!(formatter, " ...")?;
305 }
306 Ok(())
307 };
308 cmd_err.add_formatted_hint_with(|formatter| {
309 writeln!(
310 formatter,
311 "The revset `{revision_str}` resolved to these revisions:"
312 )?;
313 write_commits_summary(formatter)
314 });
315 cmd_err
316}
317
318#[derive(Debug, Error)]
319#[error("Failed to parse bookmark name: {}", source.kind())]
320pub struct BookmarkNameParseError {
321 pub input: String,
322 pub source: RevsetParseError,
323}
324
325pub fn parse_bookmark_name(text: &str) -> Result<RefNameBuf, BookmarkNameParseError> {
327 revset::parse_symbol(text)
328 .map(Into::into)
329 .map_err(|source| BookmarkNameParseError {
330 input: text.to_owned(),
331 source,
332 })
333}
334
335#[derive(Debug, Error)]
336#[error("Failed to parse tag name: {}", source.kind())]
337pub struct TagNameParseError {
338 pub source: RevsetParseError,
339}
340
341pub fn parse_tag_name(text: &str) -> Result<RefNameBuf, TagNameParseError> {
343 revset::parse_symbol(text)
344 .map(Into::into)
345 .map_err(|source| TagNameParseError { source })
346}
347
348pub fn parse_union_name_patterns<I>(ui: &Ui, texts: I) -> Result<StringExpression, CommandError>
350where
351 I: IntoIterator,
352 I::Item: AsRef<str>,
353{
354 let mut diagnostics = RevsetDiagnostics::new();
355 let expressions = texts
356 .into_iter()
357 .map(|text| revset::parse_string_expression(&mut diagnostics, text.as_ref()))
358 .try_collect()
359 .map_err(|err| {
360 let hint = revset_parse_error_hint(&err);
362 let message = format!("Failed to parse name pattern: {}", err.kind());
363 let mut cmd_err = user_error_with_message(message, err);
364 cmd_err.extend_hints(hint);
365 cmd_err
366 })?;
367 print_parse_diagnostics(ui, "In name pattern", &diagnostics)?;
368 Ok(StringExpression::union_all(expressions))
369}
370
371pub fn parse_name_patterns_or_remote_symbols<I>(
373 ui: &Ui,
374 texts: I,
375) -> Result<(Vec<StringExpression>, Vec<RemoteRefSymbolBuf>), CommandError>
376where
377 I: IntoIterator,
378 I::Item: AsRef<str>,
379{
380 let wrap_err = |err| {
381 let hint = revset_parse_error_hint(&err);
383 let message = format!(
384 "Failed to parse name pattern or remote symbol: {}",
385 err.kind()
386 );
387 let mut cmd_err = user_error_with_message(message, err);
388 cmd_err.extend_hints(hint);
389 cmd_err
390 };
391 let mut diagnostics = RevsetDiagnostics::new();
392 let mut name_expressions = Vec::new();
393 let mut remote_symbols = Vec::new();
394 for text in texts {
395 let node = revset::parse_program(text.as_ref()).map_err(wrap_err)?;
396 if let revset::ExpressionKind::RemoteSymbol(symbol) = node.kind {
397 remote_symbols.push(symbol);
398 } else {
399 let expr =
400 revset::expect_string_expression(&mut diagnostics, &node).map_err(wrap_err)?;
401 name_expressions.push(expr);
402 }
403 }
404 print_parse_diagnostics(ui, "In name pattern", &diagnostics)?;
405 Ok((name_expressions, remote_symbols))
406}
407
408pub fn parse_remote_auto_track_bookmarks_map(
411 ui: &Ui,
412 remote_settings: &RemoteSettingsMap,
413) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
414 let mut matchers = HashMap::new();
415 for (name, settings) in remote_settings {
416 let Some(text) = &settings.auto_track_bookmarks else {
417 continue;
418 };
419 let expr = parse_remote_string_expression(ui, name, text, "auto-track-bookmarks")?;
420 matchers.insert(name.clone(), expr.to_matcher());
421 }
422 Ok(matchers)
423}
424
425pub fn parse_remote_auto_track_bookmarks_map_for_new_bookmarks(
430 ui: &Ui,
431 remote_settings: &RemoteSettingsMap,
432) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
433 let mut matchers = HashMap::new();
434 for (name, settings) in remote_settings {
435 let mut exprs = Vec::new();
436 if let Some(text) = &settings.auto_track_bookmarks {
437 exprs.push(parse_remote_string_expression(
438 ui,
439 name,
440 text,
441 "auto-track-bookmarks",
442 )?);
443 }
444 if let Some(text) = &settings.auto_track_created_bookmarks {
445 exprs.push(parse_remote_string_expression(
446 ui,
447 name,
448 text,
449 "auto-track-created-bookmarks",
450 )?);
451 }
452 if exprs.is_empty() {
453 continue;
454 }
455 matchers.insert(
456 name.clone(),
457 StringExpression::union_all(exprs).to_matcher(),
458 );
459 }
460 Ok(matchers)
461}
462
463pub fn parse_remote_fetch_bookmarks(
465 ui: &Ui,
466 remote_settings: &RemoteSettingsMap,
467 name: &RemoteName,
468) -> Result<Option<StringExpression>, CommandError> {
469 remote_settings
470 .get(name)
471 .and_then(|settings| settings.fetch_bookmarks.as_ref())
472 .map(|text| parse_remote_string_expression(ui, name, text, "fetch-bookmarks"))
473 .transpose()
474}
475
476pub fn parse_remote_fetch_tags(
478 ui: &Ui,
479 remote_settings: &RemoteSettingsMap,
480 name: &RemoteName,
481) -> Result<Option<StringExpression>, CommandError> {
482 remote_settings
483 .get(name)
484 .and_then(|settings| settings.fetch_tags.as_ref())
485 .map(|text| parse_remote_string_expression(ui, name, text, "fetch-tags"))
486 .transpose()
487}
488
489fn parse_remote_string_expression(
490 ui: &Ui,
491 name: &RemoteName,
492 text: &str,
493 field_name: &str,
494) -> Result<StringExpression, CommandError> {
495 let mut diagnostics = RevsetDiagnostics::new();
496 let expr = revset::parse_string_expression(&mut diagnostics, text).map_err(|err| {
497 let hint = revset_parse_error_hint(&err);
499 let message = format!(
500 "Invalid `remotes.{}.{field_name}`: {}",
501 name.as_symbol(),
502 err.kind()
503 );
504 let mut cmd_err = config_error_with_message(message, err);
505 cmd_err.extend_hints(hint);
506 cmd_err
507 })?;
508 print_parse_diagnostics(
509 ui,
510 &format!("In `remotes.{}.{field_name}`", name.as_symbol()),
511 &diagnostics,
512 )?;
513 Ok(expr)
514}