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
228pub(super) async fn evaluate_revset_to_single_commit<'a>(
229 revision_str: &str,
230 expression: &RevsetExpressionEvaluator<'_>,
231 commit_summary_template: impl FnOnce() -> TemplateRenderer<'a, Commit>,
232) -> Result<Commit, CommandError> {
233 let commits: Vec<_> = expression
234 .evaluate_to_commits()?
235 .take(6)
236 .try_collect()
237 .await?;
238 match commits.as_slice() {
239 [commit] => Ok(commit.clone()),
240 [] => Err(user_error(format!(
241 "Revset `{revision_str}` didn't resolve to any revisions"
242 ))),
243 _ => {
244 let elided = commits.len() > 5;
245 Err(format_multiple_revisions_error(
246 revision_str,
247 &commits[..std::cmp::min(5, commits.len())],
248 elided,
249 &commit_summary_template(),
250 ))
251 }
252 }
253}
254
255fn format_multiple_revisions_error(
256 revision_str: &str,
257 commits: &[Commit],
258 elided: bool,
259 template: &TemplateRenderer<'_, Commit>,
260) -> CommandError {
261 assert!(commits.len() >= 2);
262 let mut cmd_err = user_error(format!(
263 "Revset `{revision_str}` resolved to more than one revision"
264 ));
265 let write_commits_summary = |formatter: &mut dyn Formatter| {
266 for commit in commits {
267 write!(formatter, " ")?;
268 template.format(commit, formatter)?;
269 writeln!(formatter)?;
270 }
271 if elided {
272 writeln!(formatter, " ...")?;
273 }
274 Ok(())
275 };
276 cmd_err.add_formatted_hint_with(|formatter| {
277 writeln!(
278 formatter,
279 "The revset `{revision_str}` resolved to these revisions:"
280 )?;
281 write_commits_summary(formatter)
282 });
283 cmd_err
284}
285
286#[derive(Debug, Error)]
287#[error("Failed to parse bookmark name: {}", source.kind())]
288pub struct BookmarkNameParseError {
289 pub input: String,
290 pub source: RevsetParseError,
291}
292
293pub fn parse_bookmark_name(text: &str) -> Result<RefNameBuf, BookmarkNameParseError> {
295 revset::parse_symbol(text)
296 .map(Into::into)
297 .map_err(|source| BookmarkNameParseError {
298 input: text.to_owned(),
299 source,
300 })
301}
302
303#[derive(Debug, Error)]
304#[error("Failed to parse tag name: {}", source.kind())]
305pub struct TagNameParseError {
306 pub source: RevsetParseError,
307}
308
309pub fn parse_tag_name(text: &str) -> Result<RefNameBuf, TagNameParseError> {
311 revset::parse_symbol(text)
312 .map(Into::into)
313 .map_err(|source| TagNameParseError { source })
314}
315
316pub fn parse_union_name_patterns<I>(ui: &Ui, texts: I) -> Result<StringExpression, CommandError>
318where
319 I: IntoIterator,
320 I::Item: AsRef<str>,
321{
322 let mut diagnostics = RevsetDiagnostics::new();
323 let expressions = texts
324 .into_iter()
325 .map(|text| revset::parse_string_expression(&mut diagnostics, text.as_ref()))
326 .try_collect()
327 .map_err(|err| {
328 let hint = revset_parse_error_hint(&err);
330 let message = format!("Failed to parse name pattern: {}", err.kind());
331 let mut cmd_err = user_error_with_message(message, err);
332 cmd_err.extend_hints(hint);
333 cmd_err
334 })?;
335 print_parse_diagnostics(ui, "In name pattern", &diagnostics)?;
336 Ok(StringExpression::union_all(expressions))
337}
338
339pub fn parse_name_patterns_or_remote_symbols<I>(
341 ui: &Ui,
342 texts: I,
343) -> Result<(Vec<StringExpression>, Vec<RemoteRefSymbolBuf>), CommandError>
344where
345 I: IntoIterator,
346 I::Item: AsRef<str>,
347{
348 let wrap_err = |err| {
349 let hint = revset_parse_error_hint(&err);
351 let message = format!(
352 "Failed to parse name pattern or remote symbol: {}",
353 err.kind()
354 );
355 let mut cmd_err = user_error_with_message(message, err);
356 cmd_err.extend_hints(hint);
357 cmd_err
358 };
359 let mut diagnostics = RevsetDiagnostics::new();
360 let mut name_expressions = Vec::new();
361 let mut remote_symbols = Vec::new();
362 for text in texts {
363 let node = revset::parse_program(text.as_ref()).map_err(wrap_err)?;
364 if let revset::ExpressionKind::RemoteSymbol(symbol) = node.kind {
365 remote_symbols.push(symbol);
366 } else {
367 let expr =
368 revset::expect_string_expression(&mut diagnostics, &node).map_err(wrap_err)?;
369 name_expressions.push(expr);
370 }
371 }
372 print_parse_diagnostics(ui, "In name pattern", &diagnostics)?;
373 Ok((name_expressions, remote_symbols))
374}
375
376pub fn parse_remote_auto_track_bookmarks_map(
379 ui: &Ui,
380 remote_settings: &RemoteSettingsMap,
381) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
382 let mut matchers = HashMap::new();
383 for (name, settings) in remote_settings {
384 let Some(text) = &settings.auto_track_bookmarks else {
385 continue;
386 };
387 let expr = parse_remote_string_expression(ui, name, text, "auto-track-bookmarks")?;
388 matchers.insert(name.clone(), expr.to_matcher());
389 }
390 Ok(matchers)
391}
392
393pub fn parse_remote_auto_track_bookmarks_map_for_new_bookmarks(
398 ui: &Ui,
399 remote_settings: &RemoteSettingsMap,
400) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
401 let mut matchers = HashMap::new();
402 for (name, settings) in remote_settings {
403 let mut exprs = Vec::new();
404 if let Some(text) = &settings.auto_track_bookmarks {
405 exprs.push(parse_remote_string_expression(
406 ui,
407 name,
408 text,
409 "auto-track-bookmarks",
410 )?);
411 }
412 if let Some(text) = &settings.auto_track_created_bookmarks {
413 exprs.push(parse_remote_string_expression(
414 ui,
415 name,
416 text,
417 "auto-track-created-bookmarks",
418 )?);
419 }
420 if exprs.is_empty() {
421 continue;
422 }
423 matchers.insert(
424 name.clone(),
425 StringExpression::union_all(exprs).to_matcher(),
426 );
427 }
428 Ok(matchers)
429}
430
431pub fn parse_remote_fetch_bookmarks(
433 ui: &Ui,
434 remote_settings: &RemoteSettingsMap,
435 name: &RemoteName,
436) -> Result<Option<StringExpression>, CommandError> {
437 remote_settings
438 .get(name)
439 .and_then(|settings| settings.fetch_bookmarks.as_ref())
440 .map(|text| parse_remote_string_expression(ui, name, text, "fetch-bookmarks"))
441 .transpose()
442}
443
444pub fn parse_remote_fetch_tags(
446 ui: &Ui,
447 remote_settings: &RemoteSettingsMap,
448 name: &RemoteName,
449) -> Result<Option<StringExpression>, CommandError> {
450 remote_settings
451 .get(name)
452 .and_then(|settings| settings.fetch_tags.as_ref())
453 .map(|text| parse_remote_string_expression(ui, name, text, "fetch-tags"))
454 .transpose()
455}
456
457fn parse_remote_string_expression(
458 ui: &Ui,
459 name: &RemoteName,
460 text: &str,
461 field_name: &str,
462) -> Result<StringExpression, CommandError> {
463 let mut diagnostics = RevsetDiagnostics::new();
464 let expr = revset::parse_string_expression(&mut diagnostics, text).map_err(|err| {
465 let hint = revset_parse_error_hint(&err);
467 let message = format!(
468 "Invalid `remotes.{}.{field_name}`: {}",
469 name.as_symbol(),
470 err.kind()
471 );
472 let mut cmd_err = config_error_with_message(message, err);
473 cmd_err.extend_hints(hint);
474 cmd_err
475 })?;
476 print_parse_diagnostics(
477 ui,
478 &format!("In `remotes.{}.{field_name}`", name.as_symbol()),
479 &diagnostics,
480 )?;
481 Ok(expr)
482}