Mercurial > public > mercurial-scm > hg
annotate rust/hg-core/src/operations/cat.rs @ 49000:dd6b67d5c256 stable
rust: fix unsound `OwningDirstateMap`
As per the previous patch, `OwningDirstateMap` is unsound. Self-referential
structs are difficult to implement correctly in Rust since the compiler is
free to move structs around as much as it wants to. They are also very rarely
needed in practice, so the state-of-the-art on how they should be done within
the Rust rules is still a bit new.
The crate `ouroboros` is an attempt at providing a safe way (in the Rust sense)
of declaring self-referential structs. It is getting a lot attention and was
improved very quickly when soundness issues were found in the past: rather than
relying on our own (limited) review circle, we might as well use the de-facto
common crate to fix this problem. This will give us a much better chance of
finding issues should any new ones be discovered as well as the benefit of
fewer `unsafe` APIs of our own.
I was starting to think about how I would present a safe API to the old struct
but soon realized that the callback-based approach was already done in
`ouroboros`, along with a lot more care towards refusing incorrect structs.
In short: we don't return a mutable reference to the `DirstateMap` anymore, we
expect users of its API to pass a `FnOnce` that takes the map as an argument.
This allows our `OwningDirstateMap` to control the input and output lifetimes
of the code that modifies it to prevent such issues.
Changing to `ouroboros` meant changing every API with it, but it is relatively
low churn in the end. It correctly identified the example buggy modification of
`copy_map_insert` outlined in the previous patch as violating the borrow rules.
Differential Revision: https://phab.mercurial-scm.org/D12429
author | Rapha?l Gom?s <rgomes@octobus.net> |
---|---|
date | Tue, 05 Apr 2022 10:55:28 +0200 |
parents | 20d0d896183e |
children | 49131579db62 |
rev | line source |
---|---|
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
1 // list_tracked_files.rs |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
2 // |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
4 // |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
5 // This software may be used and distributed according to the terms of the |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
6 // GNU General Public License version 2 or any later version. |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
7 |
46167
8a4914397d02
rust: introduce Repo and Vfs types for filesystem abstraction
Simon Sapin <simon.sapin@octobus.net>
parents:
46135
diff
changeset
|
8 use crate::repo::Repo; |
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
9 use crate::revlog::revlog::RevlogError; |
46033
88e741bf2d93
rust: use NodePrefix::from_hex instead of hex::decode directly
Simon Sapin <simon-commits@exyr.org>
parents:
46032
diff
changeset
|
10 use crate::revlog::Node; |
47961
4d2a5ca060e3
rust: Add a Filelog struct that wraps Revlog
Simon Sapin <simon.sapin@octobus.net>
parents:
47960
diff
changeset
|
11 |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
12 use crate::utils::hg_path::HgPath; |
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
13 |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
14 use crate::errors::HgError; |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
15 use crate::manifest::Manifest; |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
16 use crate::manifest::ManifestEntry; |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
17 use itertools::put_back; |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
18 use itertools::PutBack; |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
19 use std::cmp::Ordering; |
48224
6b5773f89183
rhg: faster hg cat when many files are requested
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47969
diff
changeset
|
20 |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
21 pub struct CatOutput<'a> { |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
22 /// Whether any file in the manifest matched the paths given as CLI |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
23 /// arguments |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
24 pub found_any: bool, |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
25 /// The contents of matching files, in manifest order |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
26 pub results: Vec<(&'a HgPath, Vec<u8>)>, |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
27 /// Which of the CLI arguments did not match any manifest file |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
28 pub missing: Vec<&'a HgPath>, |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
29 /// The node ID that the given revset was resolved to |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
30 pub node: Node, |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
31 } |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
32 |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
33 // Find an item in an iterator over a sorted collection. |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
34 fn find_item<'a>( |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
35 i: &mut PutBack<impl Iterator<Item = Result<ManifestEntry<'a>, HgError>>>, |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
36 needle: &HgPath, |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
37 ) -> Result<Option<Node>, HgError> { |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
38 loop { |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
39 match i.next() { |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
40 None => return Ok(None), |
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
41 Some(result) => { |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
42 let entry = result?; |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
43 match needle.as_bytes().cmp(entry.path.as_bytes()) { |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
44 Ordering::Less => { |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
45 i.put_back(Ok(entry)); |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
46 return Ok(None); |
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
47 } |
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
48 Ordering::Greater => continue, |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
49 Ordering::Equal => return Ok(Some(entry.node_id()?)), |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
50 } |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
51 } |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
52 } |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
53 } |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
54 } |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
55 |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
56 fn find_files_in_manifest<'query>( |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
57 manifest: &Manifest, |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
58 query: impl Iterator<Item = &'query HgPath>, |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
59 ) -> Result<(Vec<(&'query HgPath, Node)>, Vec<&'query HgPath>), HgError> { |
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
60 let mut manifest = put_back(manifest.iter()); |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
61 let mut res = vec![]; |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
62 let mut missing = vec![]; |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
63 |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
64 for file in query { |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
65 match find_item(&mut manifest, file)? { |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
66 None => missing.push(file), |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
67 Some(item) => res.push((file, item)), |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
68 } |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
69 } |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
70 return Ok((res, missing)); |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
71 } |
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
72 |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
73 /// Output the given revision of files |
46135
dca9cb99971c
rust: replace most "operation" structs with functions
Simon Sapin <simon.sapin@octobus.net>
parents:
46134
diff
changeset
|
74 /// |
dca9cb99971c
rust: replace most "operation" structs with functions
Simon Sapin <simon.sapin@octobus.net>
parents:
46134
diff
changeset
|
75 /// * `root`: Repository root |
dca9cb99971c
rust: replace most "operation" structs with functions
Simon Sapin <simon.sapin@octobus.net>
parents:
46134
diff
changeset
|
76 /// * `rev`: The revision to cat the files from. |
dca9cb99971c
rust: replace most "operation" structs with functions
Simon Sapin <simon.sapin@octobus.net>
parents:
46134
diff
changeset
|
77 /// * `files`: The files to output. |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
78 pub fn cat<'a>( |
46167
8a4914397d02
rust: introduce Repo and Vfs types for filesystem abstraction
Simon Sapin <simon.sapin@octobus.net>
parents:
46135
diff
changeset
|
79 repo: &Repo, |
46433
4b381dbbf8b7
rhg: centralize parsing of `--rev` CLI arguments
Simon Sapin <simon.sapin@octobus.net>
parents:
46431
diff
changeset
|
80 revset: &str, |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
81 mut files: Vec<&'a HgPath>, |
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
82 ) -> Result<CatOutput<'a>, RevlogError> { |
46433
4b381dbbf8b7
rhg: centralize parsing of `--rev` CLI arguments
Simon Sapin <simon.sapin@octobus.net>
parents:
46431
diff
changeset
|
83 let rev = crate::revset::resolve_single(revset, repo)?; |
47964
796206e74b10
rhg: Reuse manifest when checking status of multiple ambiguous files
Simon Sapin <simon.sapin@octobus.net>
parents:
47961
diff
changeset
|
84 let manifest = repo.manifest_for_rev(rev)?; |
47960
cfb6e6699b25
rust: Add Repo::manifest(revision)
Simon Sapin <simon.sapin@octobus.net>
parents:
47959
diff
changeset
|
85 let node = *repo |
cfb6e6699b25
rust: Add Repo::manifest(revision)
Simon Sapin <simon.sapin@octobus.net>
parents:
47959
diff
changeset
|
86 .changelog()? |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
87 .node_from_rev(rev) |
47960
cfb6e6699b25
rust: Add Repo::manifest(revision)
Simon Sapin <simon.sapin@octobus.net>
parents:
47959
diff
changeset
|
88 .expect("should succeed when repo.manifest did"); |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
89 let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![]; |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
90 let mut found_any = false; |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
91 |
48224
6b5773f89183
rhg: faster hg cat when many files are requested
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47969
diff
changeset
|
92 files.sort_unstable(); |
6b5773f89183
rhg: faster hg cat when many files are requested
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47969
diff
changeset
|
93 |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
94 let (found, missing) = find_files_in_manifest( |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
95 &manifest, |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
96 files.into_iter().map(|f| f.as_ref()), |
48342
10c32e1b892a
rhg: Propogate manifest parse errors instead of panicking
Simon Sapin <simon.sapin@octobus.net>
parents:
48237
diff
changeset
|
97 )?; |
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
98 |
48343
eb428010aad2
rhg: Also parse flags in the manifest parser
Simon Sapin <simon.sapin@octobus.net>
parents:
48342
diff
changeset
|
99 for (file_path, file_node) in found { |
48225
0cc69017d47f
rhg: stop manifest traversal when no more files are needed
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48224
diff
changeset
|
100 found_any = true; |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
101 let file_log = repo.filelog(file_path)?; |
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
102 results.push(( |
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
103 file_path, |
48540
20d0d896183e
rhg: Rename some revlog-related types and methods
Simon Sapin <simon.sapin@octobus.net>
parents:
48343
diff
changeset
|
104 file_log.data_for_node(file_node)?.into_file_data()?, |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
105 )); |
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
106 } |
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
107 |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
108 Ok(CatOutput { |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
109 found_any, |
48237
027ebad952ac
rhg: internally, return a structured representation from hg cat
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
48234
diff
changeset
|
110 results, |
46744
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
111 missing, |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
112 node, |
b1f2c2b336ec
rhg: `cat` command: print error messages for missing files
Simon Sapin <simon.sapin@octobus.net>
parents:
46443
diff
changeset
|
113 }) |
45541
522ec3dc44b9
hg-core: add a `CatRev` operation
Antoine Cezar <antoine.cezar@octobus.net>
parents:
diff
changeset
|
114 } |