OXYGENHUB
# c2e1d09047db oxygen / crates / oxy-core / src / repo / revset.rs
27.7 KiB · Rust · last changed The same file added on both sides, identically, is one file 17 days ago source blame history raw
1
//! Evaluating revsets (docs/revset_query_language.md §4) over a repository.
2
//!
3
//! The graph is the change metadata the state index already holds: a
4
//! symbol loads the closure of the heads it names, and `all()`, `X::`,
5
//! `dependents()` and `roots()` load every bookmark, remote bookmark, tag
6
//! and the working copy. Evaluation is read-only and has a time budget;
7
//! a query past it fails and says what to narrow.
8
9
use super::*;
10
use crate::ids::NodeId;
11
use crate::revset::{self, Expr};
12
use crate::state::ChangeMeta;
13
use std::time::{Duration, Instant};
14
15
type Set = BTreeSet<ChangeHash>;
16
17
/// The default budget for a command-line evaluation.
18
pub const REVSET_BUDGET: Duration = Duration::from_secs(10);
19
20
/// A revset's result with the metadata of every change it names.
21
#[derive(Clone, Debug)]
22
pub struct Revset {
23
    /// Newest first, as a log is ordered.
24
    pub hashes: Vec<ChangeHash>,
25
    pub metas: HashMap<ChangeHash, ChangeMeta>,
26
}
27
28
struct Eval<'a> {
29
    repo: &'a Repo,
30
    graph: HashMap<ChangeHash, ChangeMeta>,
31
    /// Which heads have had their closure loaded.
32
    loaded: HashSet<ChangeHash>,
33
    all_loaded: bool,
34
    dependents: Option<HashMap<ChangeHash, Vec<ChangeHash>>>,
35
    names: HashMap<KeyId, Option<(String, String)>>,
36
    /// Who authenticated each change, once asked (signature checks cost).
37
    auth: HashMap<ChangeHash, SignatureStatus>,
38
    deadline: Instant,
39
    budget: Duration,
40
    now_ms: u64,
41
    depth: usize,
42
}
43
44
fn err(msg: impl Into<String>) -> RepoError {
45
    RepoError::Msg(msg.into())
46
}
47
48
impl<'a> Eval<'a> {
49
    fn tick(&self) -> Result<()> {
50
        if Instant::now() > self.deadline {
51
            return Err(err(format!(
52
                "the revset went past its budget of {:.0} s; narrow it with a range such as main.. or a date()",
53
                self.budget.as_secs_f64()
54
            )));
55
        }
56
        Ok(())
57
    }
58
59
    /// Load the closure of `heads` into the graph.
60
    fn ensure(&mut self, heads: &[ChangeHash]) -> Result<()> {
61
        let fresh: Vec<ChangeHash> = heads
62
            .iter()
63
            .copied()
64
            .filter(|h| !self.loaded.contains(h))
65
            .collect();
66
        if fresh.is_empty() {
67
            return Ok(());
68
        }
69
        for (h, m) in self.repo.metas_of(&fresh)? {
70
            self.graph.insert(h, m);
71
        }
72
        self.loaded.extend(fresh);
73
        self.dependents = None;
74
        self.tick()
75
    }
76
77
    /// Load every change the repository names: bookmarks, remote
78
    /// bookmarks, tags, and the working copy.
79
    fn load_all(&mut self) -> Result<()> {
80
        if self.all_loaded {
81
            return Ok(());
82
        }
83
        let view = self.repo.view()?;
84
        let mut heads: Vec<ChangeHash> = Vec::new();
85
        for st in view.bookmarks.values() {
86
            heads.extend(self.repo.store().get_state(st)?.heads);
87
        }
88
        for bookmarks in view.remotes.values() {
89
            for st in bookmarks.values() {
90
                heads.extend(self.repo.store().get_state(st)?.heads);
91
            }
92
        }
93
        for st in view.tags.values() {
94
            heads.extend(self.repo.store().get_state(st)?.heads);
95
        }
96
        if let Some(wc) = self.working_copy() {
97
            heads.push(wc);
98
        }
99
        heads.sort();
100
        heads.dedup();
101
        self.ensure(&heads)?;
102
        self.all_loaded = true;
103
        Ok(())
104
    }
105
106
    fn working_copy(&self) -> Option<ChangeHash> {
107
        if self.repo.is_bare() {
108
            None
109
        } else {
110
            self.repo.working_copy().ok().map(|(h, _)| h)
111
        }
112
    }
113
114
    fn all(&mut self) -> Result<Set> {
115
        self.load_all()?;
116
        Ok(self.graph.keys().copied().collect())
117
    }
118
119
    fn dependents_map(&mut self) -> Result<&HashMap<ChangeHash, Vec<ChangeHash>>> {
120
        self.load_all()?;
121
        if self.dependents.is_none() {
122
            let mut map: HashMap<ChangeHash, Vec<ChangeHash>> = HashMap::new();
123
            for (h, m) in &self.graph {
124
                for d in &m.deps {
125
                    map.entry(*d).or_default().push(*h);
126
                }
127
            }
128
            self.dependents = Some(map);
129
        }
130
        Ok(self.dependents.as_ref().unwrap())
131
    }
132
133
    /// `::X`: the members and their dependency closures.
134
    fn ancestors(&mut self, set: &Set) -> Result<Set> {
135
        let mut out = set.clone();
136
        let mut stack: Vec<ChangeHash> = set.iter().copied().collect();
137
        let mut n = 0usize;
138
        while let Some(h) = stack.pop() {
139
            if let Some(m) = self.graph.get(&h) {
140
                for d in &m.deps {
141
                    if out.insert(*d) {
142
                        stack.push(*d);
143
                    }
144
                }
145
            }
146
            n += 1;
147
            if n.is_multiple_of(4096) {
148
                self.tick()?;
149
            }
150
        }
151
        Ok(out)
152
    }
153
154
    /// `X::`: the members and everything that transitively depends on them.
155
    fn descendants(&mut self, set: &Set) -> Result<Set> {
156
        let mut out = set.clone();
157
        let mut stack: Vec<ChangeHash> = set.iter().copied().collect();
158
        let deps = self.dependents_map()?;
159
        while let Some(h) = stack.pop() {
160
            if let Some(ds) = deps.get(&h) {
161
                for d in ds {
162
                    if out.insert(*d) {
163
                        stack.push(*d);
164
                    }
165
                }
166
            }
167
        }
168
        Ok(out)
169
    }
170
171
    /// Members no other member depends on, directly or through others.
172
    fn heads(&mut self, set: &Set) -> Result<Set> {
173
        let mut below: HashSet<ChangeHash> = HashSet::new();
174
        let mut stack: Vec<ChangeHash> = set
175
            .iter()
176
            .filter_map(|h| self.graph.get(h))
177
            .flat_map(|m| m.deps.iter().copied())
178
            .collect();
179
        while let Some(h) = stack.pop() {
180
            if below.insert(h) {
181
                if let Some(m) = self.graph.get(&h) {
182
                    stack.extend(m.deps.iter().copied());
183
                }
184
            }
185
        }
186
        Ok(set.iter().copied().filter(|h| !below.contains(h)).collect())
187
    }
188
189
    /// Members that depend on no other member, directly or through others.
190
    fn roots(&mut self, set: &Set) -> Result<Set> {
191
        let deps = self.dependents_map()?;
192
        let mut above: HashSet<ChangeHash> = HashSet::new();
193
        let mut stack: Vec<ChangeHash> = set
194
            .iter()
195
            .filter_map(|h| deps.get(h))
196
            .flat_map(|ds| ds.iter().copied())
197
            .collect();
198
        while let Some(h) = stack.pop() {
199
            if above.insert(h) {
200
                if let Some(ds) = deps.get(&h) {
201
                    stack.extend(ds.iter().copied());
202
                }
203
            }
204
        }
205
        Ok(set.iter().copied().filter(|h| !above.contains(h)).collect())
206
    }
207
208
    fn symbol(&mut self, name: &str) -> Result<Set> {
209
        if name == "@" {
210
            let Some(wc) = self.working_copy() else {
211
                return Err(err(
212
                    "@ names the working copy, and a bare repository has none",
213
                ));
214
            };
215
            self.ensure(&[wc])?;
216
            return Ok([wc].into_iter().collect());
217
        }
218
        let heads = self.repo.resolve_heads(name)?;
219
        self.ensure(&heads)?;
220
        Ok(heads.into_iter().collect())
221
    }
222
223
    /// The identity's name and email, if its object is known.
224
    fn who(&mut self, key: &KeyId) -> Option<(String, String)> {
225
        if let Some(w) = self.names.get(key) {
226
            return w.clone();
227
        }
228
        let w = self.repo.identity(key).ok().map(|id| (id.name, id.email));
229
        self.names.insert(*key, w.clone());
230
        w
231
    }
232
233
    /// The set a predicate ranges over: what it is intersected with when
234
    /// that is known, else everything.
235
    fn universe(&mut self, within: Option<&Set>) -> Result<Set> {
236
        match within {
237
            Some(s) => Ok(s.clone()),
238
            None => self.all(),
239
        }
240
    }
241
242
    fn filter_metas(
243
        &mut self,
244
        within: Option<&Set>,
245
        mut keep: impl FnMut(&ChangeHash, &ChangeMeta) -> bool,
246
    ) -> Result<Set> {
247
        let candidates = self.universe(within)?;
248
        let mut out = Set::new();
249
        for (i, h) in candidates.iter().enumerate() {
250
            if let Some(m) = self.graph.get(h) {
251
                if keep(h, m) {
252
                    out.insert(*h);
253
                }
254
            }
255
            if i % 8192 == 8191 {
256
                self.tick()?;
257
            }
258
        }
259
        Ok(out)
260
    }
261
262
    /// Changes touching any node whose path matches `glob` in the
263
    /// evaluation state: the working copy's base, or the bookmark's state
264
    /// in a bare repository. Nodes keep their identity through renames.
265
    fn touches(&mut self, within: Option<&Set>, glob: &str) -> Result<Set> {
266
        let matcher = globset::GlobBuilder::new(glob)
267
            .literal_separator(false)
268
            .build()
269
            .map_err(|e| err(format!("touches({glob:?}): {e}")))?
270
            .compile_matcher();
271
        // The tree alone names the nodes: no file graph is built (but for
272
        // same-path additions it compares). The working copy's base comes
273
        // from the workspace's state index.
274
        let mut state = match self.working_copy() {
275
            Some(wc) => {
276
                let deps = self
277
                    .graph
278
                    .get(&wc)
279
                    .map(|m| m.deps.clone())
280
                    .or_else(|| self.repo.working_copy().ok().map(|(_, c)| c.deps))
281
                    .unwrap_or_default();
282
                self.repo.load_state_indexed(&deps)?
283
            }
284
            None => self.repo.load_state_history(&self.repo.bookmark_heads()?)?,
285
        };
286
        let view = self.repo.tree_view(&mut state)?;
287
        let mut nodes: HashSet<NodeId> = view
288
            .entries
289
            .iter()
290
            .filter(|(path, _)| matcher.is_match(path))
291
            .map(|(_, e)| e.node)
292
            .collect();
293
        // A file shown as one of identical additions is all of them.
294
        for (dup, shown) in &view.unified {
295
            if nodes.contains(shown) {
296
                nodes.insert(*dup);
297
            }
298
        }
299
        if nodes.is_empty() {
300
            return Ok(Set::new());
301
        }
302
        let candidates = self.universe(within)?;
303
        let mut touched = self.repo.touched();
304
        touched.fill(self.repo, &candidates.iter().copied().collect::<Vec<_>>());
305
        let mut out = Set::new();
306
        for (i, h) in candidates.iter().enumerate() {
307
            if touched.touches(self.repo, h, &nodes) {
308
                out.insert(*h);
309
            }
310
            if i % 256 == 255 {
311
                self.tick()?;
312
            }
313
        }
314
        touched.save();
315
        Ok(out)
316
    }
317
318
    fn text_arg<'e>(&self, name: &str, args: &'e [Expr], i: usize) -> Result<&'e str> {
319
        match args.get(i) {
320
            Some(Expr::Symbol(s)) => Ok(s),
321
            _ => Err(err(format!("{name}() takes text, quoted if it has spaces"))),
322
        }
323
    }
324
325
    fn arity(&self, name: &str, args: &[Expr], n: usize) -> Result<()> {
326
        if args.len() != n {
327
            return Err(err(format!(
328
                "{name}() takes {n} argument{}, not {}",
329
                if n == 1 { "" } else { "s" },
330
                args.len()
331
            )));
332
        }
333
        Ok(())
334
    }
335
336
    fn call(&mut self, name: &str, args: &[Expr], within: Option<&Set>) -> Result<Set> {
337
        match name {
338
            "all" => {
339
                self.arity(name, args, 0)?;
340
                self.universe(within)
341
            }
342
            "none" => {
343
                self.arity(name, args, 0)?;
344
                Ok(Set::new())
345
            }
346
            "deps" => {
347
                self.arity(name, args, 1)?;
348
                let x = self.eval(&args[0], None)?;
349
                let mut out = Set::new();
350
                for h in &x {
351
                    if let Some(m) = self.graph.get(h) {
352
                        out.extend(m.deps.iter().copied());
353
                    }
354
                }
355
                Ok(out)
356
            }
357
            "dependents" => {
358
                self.arity(name, args, 1)?;
359
                let x = self.eval(&args[0], None)?;
360
                let deps = self.dependents_map()?;
361
                let mut out = Set::new();
362
                for h in &x {
363
                    if let Some(ds) = deps.get(h) {
364
                        out.extend(ds.iter().copied());
365
                    }
366
                }
367
                Ok(out)
368
            }
369
            "heads" => {
370
                self.arity(name, args, 1)?;
371
                let x = self.eval(&args[0], None)?;
372
                self.heads(&x)
373
            }
374
            "roots" => {
375
                self.arity(name, args, 1)?;
376
                let x = self.eval(&args[0], None)?;
377
                self.roots(&x)
378
            }
379
            "latest" => {
380
                self.arity(name, args, 2)?;
381
                let x = self.eval(&args[0], None)?;
382
                let n: usize = self
383
                    .text_arg(name, args, 1)?
384
                    .parse()
385
                    .map_err(|_| err("latest(X, n) takes a count"))?;
386
                let mut members: Vec<ChangeHash> = x.into_iter().collect();
387
                self.sort_newest_first(&mut members);
388
                Ok(members.into_iter().take(n).collect())
389
            }
390
            "author" => {
391
                self.arity(name, args, 1)?;
392
                let who = self.text_arg(name, args, 0)?.to_string();
393
                let mut matches: HashMap<KeyId, bool> = HashMap::new();
394
                self.filter_metas_with(within, |ev, _, m| {
395
                    if let Some(hit) = matches.get(&m.author) {
396
                        return *hit;
397
                    }
398
                    let hit = ev.names_key(&who, &m.author);
399
                    matches.insert(m.author, hit);
400
                    hit
401
                })
402
            }
403
            "description" => {
404
                self.arity(name, args, 1)?;
405
                let text = self.text_arg(name, args, 0)?.to_string();
406
                if text.starts_with("re:") {
407
                    return Err(err(
408
                        "description(re:…) is not supported yet; give a substring",
409
                    ));
410
                }
411
                let needle = text.to_lowercase();
412
                self.filter_metas(within, |_, m| m.message.to_lowercase().contains(&needle))
413
            }
414
            "date" => {
415
                self.arity(name, args, 1)?;
416
                let range = revset::parse_date_range(self.text_arg(name, args, 0)?, self.now_ms)
417
                    .map_err(err)?;
418
                self.filter_metas(within, |_, m| range.contains(m.hlc.wall_ms))
419
            }
420
            "kind" => {
421
                self.arity(name, args, 1)?;
422
                let kind = match self.text_arg(name, args, 0)? {
423
                    "normal" => ChangeKind::Normal,
424
                    "resolution" => ChangeKind::Resolution,
425
                    "working-copy" | "working_copy" => ChangeKind::WorkingCopy,
426
                    other => {
427
                        return Err(err(format!(
428
                            "kind({other:?}): use normal, resolution or working-copy"
429
                        )))
430
                    }
431
                };
432
                self.filter_metas(within, |_, m| m.kind == kind)
433
            }
434
            "touches" => {
435
                self.arity(name, args, 1)?;
436
                let glob = self.text_arg(name, args, 0)?.to_string();
437
                self.touches(within, &glob)
438
            }
439
            // Trust (§2.4): what authenticated a change, and what vouched
440
            // for the states it is in.
441
            "signed_by" => {
442
                self.arity(name, args, 1)?;
443
                let who = self.text_arg(name, args, 0)?.to_string();
444
                self.filter_metas_with(within, |ev, h, m| match ev.auth(h, m) {
445
                    SignatureStatus::Valid { key, .. } => ev.names_key(&who, &key),
446
                    _ => false,
447
                })
448
            }
449
            "delegated" => {
450
                self.arity(name, args, 0)?;
451
                self.filter_metas_with(within, |ev, h, m| {
452
                    !m.author.is_zero()
453
                        && matches!(ev.auth(h, m), SignatureStatus::Valid { key, .. } if key != m.author)
454
                })
455
            }
456
            "unsigned" => {
457
                self.arity(name, args, 0)?;
458
                self.filter_metas_with(within, |ev, h, m| {
459
                    m.kind != ChangeKind::WorkingCopy
460
                        && !matches!(ev.auth(h, m), SignatureStatus::Valid { .. })
461
                })
462
            }
463
            "revoked" => {
464
                self.arity(name, args, 0)?;
465
                self.filter_metas_with(within, |ev, h, m| {
466
                    matches!(ev.auth(h, m), SignatureStatus::Valid { revoked: true, .. })
467
                })
468
            }
469
            "attested" => {
470
                self.arity(name, args, 1)?;
471
                let kind = self.text_arg(name, args, 0)?.to_string();
472
                self.attested(within, &kind)
473
            }
474
            // A bookmark's heads by name: what a bare bookmark symbol means,
475
            // without falling through to tags and change prefixes.
476
            "bookmark" => {
477
                self.arity(name, args, 1)?;
478
                let b = self.text_arg(name, args, 0)?;
479
                let view = self.repo.view()?;
480
                let st = match view.bookmarks.get(b) {
481
                    Some(st) => *st,
482
                    None => match remote_bookmark(&view, b) {
483
                        Some((_, _, st)) => st,
484
                        None => return Err(err(format!("bookmark({b}): no such bookmark"))),
485
                    },
486
                };
487
                let heads = self.repo.store().get_state(&st)?.heads;
488
                self.ensure(&heads)?;
489
                Ok(heads.into_iter().collect())
490
            }
491
            "built" => {
492
                self.arity(name, args, 1)?;
493
                let job = self.text_arg(name, args, 0)?.to_string();
494
                self.built(within, &job)
495
            }
496
            other => {
497
                // A saved revset: `oxy config revset.NAME 'EXPR'` makes NAME().
498
                if let Some(saved) = self.repo.config.revsets.get(other).cloned() {
499
                    self.arity(name, args, 0)?;
500
                    if self.depth >= 16 {
501
                        return Err(err(format!("revset.{other} refers to itself")));
502
                    }
503
                    let e = revset::parse(&saved)
504
                        .map_err(|e| err(format!("revset.{other} = {saved:?}: {e}")))?;
505
                    self.depth += 1;
506
                    let r = self.eval(&e, within);
507
                    self.depth -= 1;
508
                    return r;
509
                }
510
                Err(err(format!(
511
                    "unknown function {other}(); see docs/revset_query_language.md"
512
                )))
513
            }
514
        }
515
    }
516
517
    /// The authentication of a change: its signature or seal, checked once.
518
    fn auth(&mut self, h: &ChangeHash, m: &ChangeMeta) -> SignatureStatus {
519
        if let Some(s) = self.auth.get(h) {
520
            return s.clone();
521
        }
522
        let s = self
523
            .repo
524
            .authenticate_change_known(h, Some(m))
525
            .unwrap_or_else(|e| SignatureStatus::Invalid(e.to_string()));
526
        self.auth.insert(*h, s.clone());
527
        s
528
    }
529
530
    /// Whether `who` names key `key`: by hex prefix, `me`, or the name or
531
    /// email of its identity.
532
    fn names_key(&mut self, who: &str, key: &KeyId) -> bool {
533
        (who == "me" && Some(*key) == self.repo.config.author_key)
534
            || key.starts_with_hex(who)
535
            || self.who(key).is_some_and(|(n, e)| {
536
                let needle = who.to_lowercase();
537
                n.to_lowercase().contains(&needle) || e.to_lowercase().contains(&needle)
538
            })
539
    }
540
541
    /// The closure of the states whose heads these are, within `within`.
542
    fn closure_of(&mut self, mut heads: Vec<ChangeHash>, within: Option<&Set>) -> Result<Set> {
543
        heads.sort();
544
        heads.dedup();
545
        self.ensure(&heads)?;
546
        let set: Set = heads.into_iter().collect();
547
        let closure = self.ancestors(&set)?;
548
        Ok(match within {
549
            Some(w) => closure.intersection(w).copied().collect(),
550
            None => closure,
551
        })
552
    }
553
554
    /// The closure of every state that carries an attestation of `kind`:
555
    /// `tag` (tagged states), `receipt` (states a runner reported on) or
556
    /// `ref` (states a remote attested, as its tracking bookmarks hold).
557
    fn attested(&mut self, within: Option<&Set>, kind: &str) -> Result<Set> {
558
        let mut heads: Vec<ChangeHash> = Vec::new();
559
        match kind {
560
            "tag" => {
561
                for t in self.repo.tags()? {
562
                    heads.extend(t.heads);
563
                }
564
            }
565
            "receipt" => {
566
                for st in self.repo.store().receipt_states()? {
567
                    if let Ok(s) = self.repo.store().get_state(&st) {
568
                        heads.extend(s.heads);
569
                    }
570
                }
571
            }
572
            "ref" => {
573
                let view = self.repo.view()?;
574
                for bookmarks in view.remotes.values() {
575
                    for st in bookmarks.values() {
576
                        heads.extend(self.repo.store().get_state(st)?.heads);
577
                    }
578
                }
579
            }
580
            other => return Err(err(format!("attested({other:?}): use tag, receipt or ref"))),
581
        }
582
        self.closure_of(heads, within)
583
    }
584
585
    /// The closure of every state with a valid receipt for Flow job `job`
586
    /// from a runner whose key is not revoked.
587
    fn built(&mut self, within: Option<&Set>, job: &str) -> Result<Set> {
588
        let mut heads: Vec<ChangeHash> = Vec::new();
589
        for st in self.repo.store().receipt_states()? {
590
            let Ok(state) = self.repo.store().get_state(&st) else {
591
                continue;
592
            };
593
            let good = self
594
                .repo
595
                .receipts(&state.heads)?
596
                .into_iter()
597
                .any(|r| r.signed.receipt.job_name == job && r.problem.is_none() && !r.revoked);
598
            if good {
599
                heads.extend(state.heads);
600
            }
601
            self.tick()?;
602
        }
603
        self.closure_of(heads, within)
604
    }
605
606
    /// `filter_metas` for a test that needs the evaluator (identity lookups).
607
    fn filter_metas_with(
608
        &mut self,
609
        within: Option<&Set>,
610
        mut keep: impl FnMut(&mut Eval<'a>, &ChangeHash, &ChangeMeta) -> bool,
611
    ) -> Result<Set> {
612
        let candidates = self.universe(within)?;
613
        let mut out = Set::new();
614
        for (i, h) in candidates.iter().enumerate() {
615
            if let Some(m) = self.graph.get(h).cloned() {
616
                if keep(self, h, &m) {
617
                    out.insert(*h);
618
                }
619
            }
620
            if i % 8192 == 8191 {
621
                self.tick()?;
622
            }
623
        }
624
        Ok(out)
625
    }
626
627
    /// Whether an expression selects by a property, so that evaluating it
628
    /// within the other side of an intersection saves scanning everything.
629
    fn is_predicate(e: &Expr) -> bool {
630
        match e {
631
            Expr::Call(name, _) => matches!(
632
                name.as_str(),
633
                "author"
634
                    | "description"
635
                    | "date"
636
                    | "kind"
637
                    | "touches"
638
                    | "all"
639
                    | "signed_by"
640
                    | "delegated"
641
                    | "unsigned"
642
                    | "revoked"
643
                    | "attested"
644
                    | "built"
645
            ),
646
            Expr::Intersection(a, b) => Self::is_predicate(a) || Self::is_predicate(b),
647
            Expr::Union(a, b) => Self::is_predicate(a) && Self::is_predicate(b),
648
            Expr::Difference(a, _) => Self::is_predicate(a),
649
            _ => false,
650
        }
651
    }
652
653
    fn eval(&mut self, e: &Expr, within: Option<&Set>) -> Result<Set> {
654
        self.tick()?;
655
        match e {
656
            Expr::Symbol(s) => self.symbol(s),
657
            Expr::Ancestors(x) => {
658
                let x = self.eval(x, None)?;
659
                self.ancestors(&x)
660
            }
661
            Expr::Descendants(x) => {
662
                let x = self.eval(x, None)?;
663
                self.descendants(&x)
664
            }
665
            Expr::Between(x, y) => {
666
                let x = self.eval(x, None)?;
667
                let y = self.eval(y, None)?;
668
                let down = self.descendants(&x)?;
669
                let up = self.ancestors(&y)?;
670
                Ok(down.intersection(&up).copied().collect())
671
            }
672
            Expr::Range(x, y) => {
673
                let x = self.eval(x, None)?;
674
                let y = self.eval(y, None)?;
675
                let from = self.ancestors(&x)?;
676
                let to = self.ancestors(&y)?;
677
                Ok(to.difference(&from).copied().collect())
678
            }
679
            Expr::Union(a, b) => {
680
                let mut a = self.eval(a, within)?;
681
                a.extend(self.eval(b, within)?);
682
                Ok(a)
683
            }
684
            Expr::Intersection(a, b) => {
685
                let (first, second) = if Self::is_predicate(a) && !Self::is_predicate(b) {
686
                    (b, a)
687
                } else {
688
                    (a, b)
689
                };
690
                let left = self.eval(first, within)?;
691
                let right = self.eval(second, Some(&left))?;
692
                Ok(left.intersection(&right).copied().collect())
693
            }
694
            Expr::Difference(a, b) => {
695
                let left = self.eval(a, within)?;
696
                let right = self.eval(b, Some(&left))?;
697
                Ok(left.difference(&right).copied().collect())
698
            }
699
            Expr::Call(name, args) => self.call(name, args, within),
700
        }
701
    }
702
703
    fn sort_newest_first(&self, hashes: &mut [ChangeHash]) {
704
        hashes.sort_by(|a, b| {
705
            let (ma, mb) = (self.graph.get(a), self.graph.get(b));
706
            mb.map(|m| m.hlc).cmp(&ma.map(|m| m.hlc)).then(b.cmp(a))
707
        });
708
    }
709
}
710
711
impl Repo {
712
    /// Evaluate a revset within [`REVSET_BUDGET`].
713
    pub fn revset(&self, src: &str) -> Result<Revset> {
714
        self.revset_with_budget(src, REVSET_BUDGET)
715
    }
716
717
    /// Evaluate a revset: the changes it selects, newest first, with
718
    /// their metadata. Read-only. Fails past `budget`.
719
    pub fn revset_with_budget(&self, src: &str, budget: Duration) -> Result<Revset> {
720
        let expr = revset::parse(src).map_err(|e| err(format!("revset {src:?}: {e}")))?;
721
        let mut ev = Eval {
722
            repo: self,
723
            graph: HashMap::new(),
724
            loaded: HashSet::new(),
725
            all_loaded: false,
726
            dependents: None,
727
            names: HashMap::new(),
728
            auth: HashMap::new(),
729
            deadline: Instant::now() + budget,
730
            budget,
731
            now_ms: Hlc::now().wall_ms,
732
            depth: 0,
733
        };
734
        let set = ev.eval(&expr, None)?;
735
        let mut hashes: Vec<ChangeHash> = set.into_iter().collect();
736
        ev.sort_newest_first(&mut hashes);
737
        let metas = hashes
738
            .iter()
739
            .filter_map(|h| ev.graph.get(h).map(|m| (*h, m.clone())))
740
            .collect();
741
        Ok(Revset { hashes, metas })
742
    }
743
744
    /// The log entries a revset selects, newest first. A head of the
745
    /// current bookmark is marked as in `log`.
746
    pub fn log_revset(&self, src: &str) -> Result<Vec<LogEntry>> {
747
        let r = self.revset(src)?;
748
        let heads: BTreeSet<ChangeHash> = self.bookmark_heads()?.into_iter().collect();
749
        let wc = if self.is_bare() {
750
            None
751
        } else {
752
            self.working_copy().ok().map(|(h, _)| h)
753
        };
754
        let mut entries: Vec<LogEntry> = r
755
            .hashes
756
            .iter()
757
            .filter_map(|h| {
758
                r.metas.get(h).map(|m| LogEntry {
759
                    hash: *h,
760
                    change: m.clone(),
761
                    is_working_copy: Some(*h) == wc,
762
                    is_head: heads.contains(h),
763
                })
764
            })
765
            .collect();
766
        // The working copy first, as `log` shows it.
767
        entries.sort_by_key(|e| !e.is_working_copy);
768
        Ok(entries)
769
    }
770
}