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::repo::Repo;
35use jj_lib::revset;
36use jj_lib::revset::ResolvedRevsetExpression;
37use jj_lib::revset::Revset;
38use jj_lib::revset::RevsetDiagnostics;
39use jj_lib::revset::RevsetEvaluationError;
40use jj_lib::revset::RevsetExpression;
41use jj_lib::revset::RevsetExtensions;
42use jj_lib::revset::RevsetParseContext;
43use jj_lib::revset::RevsetParseError;
44use jj_lib::revset::RevsetResolutionError;
45use jj_lib::revset::RevsetStreamExt as _;
46use jj_lib::revset::SymbolResolver;
47use jj_lib::revset::SymbolResolverExtension;
48use jj_lib::revset::UserRevsetExpression;
49use jj_lib::settings::RemoteSettingsMap;
50use jj_lib::str_util::StringExpression;
51use jj_lib::str_util::StringMatcher;
52use thiserror::Error;
53
54use crate::command_error::CommandError;
55use crate::command_error::config_error_with_message;
56use crate::command_error::print_parse_diagnostics;
57use crate::command_error::revset_parse_error_hint;
58use crate::command_error::user_error;
59use crate::command_error::user_error_with_message;
60use crate::formatter::Formatter;
61use crate::templater::TemplateRenderer;
62use crate::ui::Ui;
63
64const USER_IMMUTABLE_HEADS: &str = "immutable_heads";
65
66#[derive(Debug, Error)]
67pub enum UserRevsetEvaluationError {
68 #[error(transparent)]
69 Resolution(RevsetResolutionError),
70 #[error(transparent)]
71 Evaluation(RevsetEvaluationError),
72}
73
74pub struct RevsetExpressionEvaluator<'repo> {
76 repo: &'repo dyn Repo,
77 extensions: Arc<RevsetExtensions>,
78 id_prefix_context: &'repo IdPrefixContext,
79 expression: Arc<UserRevsetExpression>,
80}
81
82impl<'repo> RevsetExpressionEvaluator<'repo> {
83 pub fn new(
84 repo: &'repo dyn Repo,
85 extensions: Arc<RevsetExtensions>,
86 id_prefix_context: &'repo IdPrefixContext,
87 expression: Arc<UserRevsetExpression>,
88 ) -> Self {
89 Self {
90 repo,
91 extensions,
92 id_prefix_context,
93 expression,
94 }
95 }
96
97 pub fn expression(&self) -> &Arc<UserRevsetExpression> {
99 &self.expression
100 }
101
102 pub fn intersect_with(&mut self, other: &Arc<UserRevsetExpression>) {
104 self.expression = self.expression.intersection(other);
105 }
106
107 pub fn resolve(&self) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
109 let symbol_resolver = default_symbol_resolver(
110 self.repo,
111 self.extensions.symbol_resolvers(),
112 self.id_prefix_context,
113 );
114 self.expression
115 .resolve_user_expression(self.repo, &symbol_resolver)
116 }
117
118 pub fn evaluate(&self) -> Result<Box<dyn Revset + 'repo>, UserRevsetEvaluationError> {
120 self.resolve()
121 .map_err(UserRevsetEvaluationError::Resolution)?
122 .evaluate(self.repo)
123 .map_err(UserRevsetEvaluationError::Evaluation)
124 }
125
126 pub fn evaluate_to_commit_ids(
129 &self,
130 ) -> Result<
131 LocalBoxStream<'repo, Result<CommitId, RevsetEvaluationError>>,
132 UserRevsetEvaluationError,
133 > {
134 Ok(self.evaluate()?.stream())
135 }
136
137 pub fn evaluate_to_commits(
140 &self,
141 ) -> Result<
142 LocalBoxStream<'repo, Result<Commit, RevsetEvaluationError>>,
143 UserRevsetEvaluationError,
144 > {
145 Ok(self
146 .evaluate()?
147 .stream()
148 .commits(self.repo.store())
149 .boxed_local())
150 }
151}
152
153pub(super) fn warn_user_redefined_builtin(
154 ui: &Ui,
155 config: &StackedConfig,
156 table_name: &ConfigNamePathBuf,
157) -> io::Result<()> {
158 let checked_mutability_builtins = ["mutable()", "immutable()", "builtin_immutable_heads()"];
159 for layer in config
160 .layers()
161 .iter()
162 .skip_while(|layer| layer.source == ConfigSource::Default)
163 {
164 let Ok(Some(table)) = layer.look_up_table(table_name) else {
165 continue;
166 };
167 for decl in checked_mutability_builtins
168 .iter()
169 .filter(|decl| table.contains_key(decl))
170 {
171 writeln!(
172 ui.warning_default(),
173 "Redefining `{table_name}.{decl}` is not recommended; redefine \
174 `immutable_heads()` instead",
175 )?;
176 }
177 }
178 Ok(())
179}
180
181pub fn default_symbol_resolver<'a>(
184 repo: &'a dyn Repo,
185 extensions: &[impl AsRef<dyn SymbolResolverExtension>],
186 id_prefix_context: &'a IdPrefixContext,
187) -> SymbolResolver<'a> {
188 SymbolResolver::new(repo, extensions).with_id_prefix_context(id_prefix_context)
189}
190
191pub fn parse_immutable_heads_expression(
194 diagnostics: &mut RevsetDiagnostics,
195 context: &RevsetParseContext,
196) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
197 let (_, _, immutable_heads_str) = context
198 .aliases_map
199 .get_function(USER_IMMUTABLE_HEADS, 0)
200 .unwrap();
201 let heads = revset::parse(diagnostics, immutable_heads_str, context)?;
202 Ok(heads.union(&RevsetExpression::root()))
203}
204
205pub(super) fn try_resolve_trunk_alias(
210 repo: &dyn Repo,
211 context: &RevsetParseContext,
212) -> Result<Option<Arc<ResolvedRevsetExpression>>, RevsetResolutionError> {
213 let (_, _, revset_str) = context
214 .aliases_map
215 .get_function("trunk", 0)
216 .expect("trunk() should be defined by default");
217 let Ok(expression) = revset::parse(&mut RevsetDiagnostics::new(), revset_str, context) else {
218 return Ok(None);
219 };
220 let symbol_resolver = SymbolResolver::new(repo, context.extensions.symbol_resolvers());
223 let resolved = expression.resolve_user_expression(repo, &symbol_resolver)?;
224 Ok(Some(resolved))
225}
226
227pub(super) async fn evaluate_revset_to_single_commit<'a>(
228 revision_str: &str,
229 expression: &RevsetExpressionEvaluator<'_>,
230 commit_summary_template: impl FnOnce() -> TemplateRenderer<'a, Commit>,
231) -> Result<Commit, CommandError> {
232 let commits: Vec<_> = expression
233 .evaluate_to_commits()?
234 .take(6)
235 .try_collect()
236 .await?;
237 match commits.as_slice() {
238 [commit] => Ok(commit.clone()),
239 [] => Err(user_error(format!(
240 "Revset `{revision_str}` didn't resolve to any revisions"
241 ))),
242 _ => {
243 let elided = commits.len() > 5;
244 Err(format_multiple_revisions_error(
245 revision_str,
246 &commits[..std::cmp::min(5, commits.len())],
247 elided,
248 &commit_summary_template(),
249 ))
250 }
251 }
252}
253
254fn format_multiple_revisions_error(
255 revision_str: &str,
256 commits: &[Commit],
257 elided: bool,
258 template: &TemplateRenderer<'_, Commit>,
259) -> CommandError {
260 assert!(commits.len() >= 2);
261 let mut cmd_err = user_error(format!(
262 "Revset `{revision_str}` resolved to more than one revision"
263 ));
264 let write_commits_summary = |formatter: &mut dyn Formatter| {
265 for commit in commits {
266 write!(formatter, " ")?;
267 template.format(commit, formatter)?;
268 writeln!(formatter)?;
269 }
270 if elided {
271 writeln!(formatter, " ...")?;
272 }
273 Ok(())
274 };
275 cmd_err.add_formatted_hint_with(|formatter| {
276 writeln!(
277 formatter,
278 "The revset `{revision_str}` resolved to these revisions:"
279 )?;
280 write_commits_summary(formatter)
281 });
282 cmd_err
283}
284
285#[derive(Debug, Error)]
286#[error("Failed to parse bookmark name: {}", source.kind())]
287pub struct BookmarkNameParseError {
288 pub input: String,
289 pub source: RevsetParseError,
290}
291
292pub fn parse_bookmark_name(text: &str) -> Result<RefNameBuf, BookmarkNameParseError> {
294 revset::parse_symbol(text)
295 .map(Into::into)
296 .map_err(|source| BookmarkNameParseError {
297 input: text.to_owned(),
298 source,
299 })
300}
301
302#[derive(Debug, Error)]
303#[error("Failed to parse tag name: {}", source.kind())]
304pub struct TagNameParseError {
305 pub source: RevsetParseError,
306}
307
308pub fn parse_tag_name(text: &str) -> Result<RefNameBuf, TagNameParseError> {
310 revset::parse_symbol(text)
311 .map(Into::into)
312 .map_err(|source| TagNameParseError { source })
313}
314
315pub fn parse_union_name_patterns<I>(ui: &Ui, texts: I) -> Result<StringExpression, CommandError>
317where
318 I: IntoIterator,
319 I::Item: AsRef<str>,
320{
321 let mut diagnostics = RevsetDiagnostics::new();
322 let expressions = texts
323 .into_iter()
324 .map(|text| revset::parse_string_expression(&mut diagnostics, text.as_ref()))
325 .try_collect()
326 .map_err(|err| {
327 let hint = revset_parse_error_hint(&err);
329 let message = format!("Failed to parse name pattern: {}", err.kind());
330 let mut cmd_err = user_error_with_message(message, err);
331 cmd_err.extend_hints(hint);
332 cmd_err
333 })?;
334 print_parse_diagnostics(ui, "In name pattern", &diagnostics)?;
335 Ok(StringExpression::union_all(expressions))
336}
337
338pub fn parse_remote_auto_track_bookmarks_map(
341 ui: &Ui,
342 remote_settings: &RemoteSettingsMap,
343) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
344 let mut matchers = HashMap::new();
345 for (name, settings) in remote_settings {
346 let Some(text) = &settings.auto_track_bookmarks else {
347 continue;
348 };
349 let expr = parse_remote_auto_track_text(ui, name, text, "auto-track-bookmarks")?;
350 matchers.insert(name.clone(), expr.to_matcher());
351 }
352 Ok(matchers)
353}
354
355pub fn parse_remote_auto_track_bookmarks_map_for_new_bookmarks(
360 ui: &Ui,
361 remote_settings: &RemoteSettingsMap,
362) -> Result<HashMap<RemoteNameBuf, StringMatcher>, CommandError> {
363 let mut matchers = HashMap::new();
364 for (name, settings) in remote_settings {
365 let mut exprs = Vec::new();
366 if let Some(text) = &settings.auto_track_bookmarks {
367 exprs.push(parse_remote_auto_track_text(
368 ui,
369 name,
370 text,
371 "auto-track-bookmarks",
372 )?);
373 }
374 if let Some(text) = &settings.auto_track_created_bookmarks {
375 exprs.push(parse_remote_auto_track_text(
376 ui,
377 name,
378 text,
379 "auto-track-created-bookmarks",
380 )?);
381 }
382 matchers.insert(
383 name.clone(),
384 StringExpression::union_all(exprs).to_matcher(),
385 );
386 }
387 Ok(matchers)
388}
389
390fn parse_remote_auto_track_text(
391 ui: &Ui,
392 name: &RemoteName,
393 text: &str,
394 field_name: &str,
395) -> Result<StringExpression, CommandError> {
396 let mut diagnostics = RevsetDiagnostics::new();
397 let expr = revset::parse_string_expression(&mut diagnostics, text).map_err(|err| {
398 let hint = revset_parse_error_hint(&err);
400 let message = format!(
401 "Invalid `remotes.{}.{field_name}`: {}",
402 name.as_symbol(),
403 err.kind()
404 );
405 let mut cmd_err = config_error_with_message(message, err);
406 cmd_err.extend_hints(hint);
407 cmd_err
408 })?;
409 print_parse_diagnostics(
410 ui,
411 &format!("In `remotes.{}.{field_name}`", name.as_symbol()),
412 &diagnostics,
413 )?;
414 Ok(expr)
415}