##// END OF EJS Templates
dirstate-tree: Add the new `status()` algorithm...
Simon Sapin -
r47883:be579775 default
parent child Browse files
Show More
@@ -1,1104 +1,1105 b''
1 # This file is automatically @generated by Cargo.
1 # This file is automatically @generated by Cargo.
2 # It is not intended for manual editing.
2 # It is not intended for manual editing.
3 [[package]]
3 [[package]]
4 name = "adler"
4 name = "adler"
5 version = "0.2.3"
5 version = "0.2.3"
6 source = "registry+https://github.com/rust-lang/crates.io-index"
6 source = "registry+https://github.com/rust-lang/crates.io-index"
7 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
7 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
8
8
9 [[package]]
9 [[package]]
10 name = "aho-corasick"
10 name = "aho-corasick"
11 version = "0.7.15"
11 version = "0.7.15"
12 source = "registry+https://github.com/rust-lang/crates.io-index"
12 source = "registry+https://github.com/rust-lang/crates.io-index"
13 checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
13 checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
14 dependencies = [
14 dependencies = [
15 "memchr",
15 "memchr",
16 ]
16 ]
17
17
18 [[package]]
18 [[package]]
19 name = "ansi_term"
19 name = "ansi_term"
20 version = "0.11.0"
20 version = "0.11.0"
21 source = "registry+https://github.com/rust-lang/crates.io-index"
21 source = "registry+https://github.com/rust-lang/crates.io-index"
22 checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
22 checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
23 dependencies = [
23 dependencies = [
24 "winapi",
24 "winapi",
25 ]
25 ]
26
26
27 [[package]]
27 [[package]]
28 name = "atty"
28 name = "atty"
29 version = "0.2.14"
29 version = "0.2.14"
30 source = "registry+https://github.com/rust-lang/crates.io-index"
30 source = "registry+https://github.com/rust-lang/crates.io-index"
31 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
31 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
32 dependencies = [
32 dependencies = [
33 "hermit-abi",
33 "hermit-abi",
34 "libc",
34 "libc",
35 "winapi",
35 "winapi",
36 ]
36 ]
37
37
38 [[package]]
38 [[package]]
39 name = "autocfg"
39 name = "autocfg"
40 version = "1.0.1"
40 version = "1.0.1"
41 source = "registry+https://github.com/rust-lang/crates.io-index"
41 source = "registry+https://github.com/rust-lang/crates.io-index"
42 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
42 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
43
43
44 [[package]]
44 [[package]]
45 name = "bitflags"
45 name = "bitflags"
46 version = "1.2.1"
46 version = "1.2.1"
47 source = "registry+https://github.com/rust-lang/crates.io-index"
47 source = "registry+https://github.com/rust-lang/crates.io-index"
48 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
48 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
49
49
50 [[package]]
50 [[package]]
51 name = "bitmaps"
51 name = "bitmaps"
52 version = "2.1.0"
52 version = "2.1.0"
53 source = "registry+https://github.com/rust-lang/crates.io-index"
53 source = "registry+https://github.com/rust-lang/crates.io-index"
54 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
54 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
55 dependencies = [
55 dependencies = [
56 "typenum",
56 "typenum",
57 ]
57 ]
58
58
59 [[package]]
59 [[package]]
60 name = "byteorder"
60 name = "byteorder"
61 version = "1.3.4"
61 version = "1.3.4"
62 source = "registry+https://github.com/rust-lang/crates.io-index"
62 source = "registry+https://github.com/rust-lang/crates.io-index"
63 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
63 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
64
64
65 [[package]]
65 [[package]]
66 name = "bytes-cast"
66 name = "bytes-cast"
67 version = "0.2.0"
67 version = "0.2.0"
68 source = "registry+https://github.com/rust-lang/crates.io-index"
68 source = "registry+https://github.com/rust-lang/crates.io-index"
69 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
69 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
70 dependencies = [
70 dependencies = [
71 "bytes-cast-derive",
71 "bytes-cast-derive",
72 ]
72 ]
73
73
74 [[package]]
74 [[package]]
75 name = "bytes-cast-derive"
75 name = "bytes-cast-derive"
76 version = "0.1.0"
76 version = "0.1.0"
77 source = "registry+https://github.com/rust-lang/crates.io-index"
77 source = "registry+https://github.com/rust-lang/crates.io-index"
78 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
78 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
79 dependencies = [
79 dependencies = [
80 "proc-macro2",
80 "proc-macro2",
81 "quote",
81 "quote",
82 "syn",
82 "syn",
83 ]
83 ]
84
84
85 [[package]]
85 [[package]]
86 name = "cc"
86 name = "cc"
87 version = "1.0.66"
87 version = "1.0.66"
88 source = "registry+https://github.com/rust-lang/crates.io-index"
88 source = "registry+https://github.com/rust-lang/crates.io-index"
89 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
89 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
90 dependencies = [
90 dependencies = [
91 "jobserver",
91 "jobserver",
92 ]
92 ]
93
93
94 [[package]]
94 [[package]]
95 name = "cfg-if"
95 name = "cfg-if"
96 version = "0.1.10"
96 version = "0.1.10"
97 source = "registry+https://github.com/rust-lang/crates.io-index"
97 source = "registry+https://github.com/rust-lang/crates.io-index"
98 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
98 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
99
99
100 [[package]]
100 [[package]]
101 name = "cfg-if"
101 name = "cfg-if"
102 version = "1.0.0"
102 version = "1.0.0"
103 source = "registry+https://github.com/rust-lang/crates.io-index"
103 source = "registry+https://github.com/rust-lang/crates.io-index"
104 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
104 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
105
105
106 [[package]]
106 [[package]]
107 name = "chrono"
107 name = "chrono"
108 version = "0.4.19"
108 version = "0.4.19"
109 source = "registry+https://github.com/rust-lang/crates.io-index"
109 source = "registry+https://github.com/rust-lang/crates.io-index"
110 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
110 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
111 dependencies = [
111 dependencies = [
112 "libc",
112 "libc",
113 "num-integer",
113 "num-integer",
114 "num-traits",
114 "num-traits",
115 "time",
115 "time",
116 "winapi",
116 "winapi",
117 ]
117 ]
118
118
119 [[package]]
119 [[package]]
120 name = "clap"
120 name = "clap"
121 version = "2.33.3"
121 version = "2.33.3"
122 source = "registry+https://github.com/rust-lang/crates.io-index"
122 source = "registry+https://github.com/rust-lang/crates.io-index"
123 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
123 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
124 dependencies = [
124 dependencies = [
125 "ansi_term",
125 "ansi_term",
126 "atty",
126 "atty",
127 "bitflags",
127 "bitflags",
128 "strsim",
128 "strsim",
129 "textwrap",
129 "textwrap",
130 "unicode-width",
130 "unicode-width",
131 "vec_map",
131 "vec_map",
132 ]
132 ]
133
133
134 [[package]]
134 [[package]]
135 name = "const_fn"
135 name = "const_fn"
136 version = "0.4.4"
136 version = "0.4.4"
137 source = "registry+https://github.com/rust-lang/crates.io-index"
137 source = "registry+https://github.com/rust-lang/crates.io-index"
138 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
138 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
139
139
140 [[package]]
140 [[package]]
141 name = "cpython"
141 name = "cpython"
142 version = "0.5.2"
142 version = "0.5.2"
143 source = "registry+https://github.com/rust-lang/crates.io-index"
143 source = "registry+https://github.com/rust-lang/crates.io-index"
144 checksum = "0f11357af68648b6a227e7e2384d439cec8595de65970f45e3f7f4b2600be472"
144 checksum = "0f11357af68648b6a227e7e2384d439cec8595de65970f45e3f7f4b2600be472"
145 dependencies = [
145 dependencies = [
146 "libc",
146 "libc",
147 "num-traits",
147 "num-traits",
148 "paste",
148 "paste",
149 "python27-sys",
149 "python27-sys",
150 "python3-sys",
150 "python3-sys",
151 ]
151 ]
152
152
153 [[package]]
153 [[package]]
154 name = "crc32fast"
154 name = "crc32fast"
155 version = "1.2.1"
155 version = "1.2.1"
156 source = "registry+https://github.com/rust-lang/crates.io-index"
156 source = "registry+https://github.com/rust-lang/crates.io-index"
157 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
157 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
158 dependencies = [
158 dependencies = [
159 "cfg-if 1.0.0",
159 "cfg-if 1.0.0",
160 ]
160 ]
161
161
162 [[package]]
162 [[package]]
163 name = "crossbeam-channel"
163 name = "crossbeam-channel"
164 version = "0.4.4"
164 version = "0.4.4"
165 source = "registry+https://github.com/rust-lang/crates.io-index"
165 source = "registry+https://github.com/rust-lang/crates.io-index"
166 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
166 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
167 dependencies = [
167 dependencies = [
168 "crossbeam-utils 0.7.2",
168 "crossbeam-utils 0.7.2",
169 "maybe-uninit",
169 "maybe-uninit",
170 ]
170 ]
171
171
172 [[package]]
172 [[package]]
173 name = "crossbeam-channel"
173 name = "crossbeam-channel"
174 version = "0.5.0"
174 version = "0.5.0"
175 source = "registry+https://github.com/rust-lang/crates.io-index"
175 source = "registry+https://github.com/rust-lang/crates.io-index"
176 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
176 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
177 dependencies = [
177 dependencies = [
178 "cfg-if 1.0.0",
178 "cfg-if 1.0.0",
179 "crossbeam-utils 0.8.1",
179 "crossbeam-utils 0.8.1",
180 ]
180 ]
181
181
182 [[package]]
182 [[package]]
183 name = "crossbeam-deque"
183 name = "crossbeam-deque"
184 version = "0.8.0"
184 version = "0.8.0"
185 source = "registry+https://github.com/rust-lang/crates.io-index"
185 source = "registry+https://github.com/rust-lang/crates.io-index"
186 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
186 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
187 dependencies = [
187 dependencies = [
188 "cfg-if 1.0.0",
188 "cfg-if 1.0.0",
189 "crossbeam-epoch",
189 "crossbeam-epoch",
190 "crossbeam-utils 0.8.1",
190 "crossbeam-utils 0.8.1",
191 ]
191 ]
192
192
193 [[package]]
193 [[package]]
194 name = "crossbeam-epoch"
194 name = "crossbeam-epoch"
195 version = "0.9.1"
195 version = "0.9.1"
196 source = "registry+https://github.com/rust-lang/crates.io-index"
196 source = "registry+https://github.com/rust-lang/crates.io-index"
197 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
197 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
198 dependencies = [
198 dependencies = [
199 "cfg-if 1.0.0",
199 "cfg-if 1.0.0",
200 "const_fn",
200 "const_fn",
201 "crossbeam-utils 0.8.1",
201 "crossbeam-utils 0.8.1",
202 "lazy_static",
202 "lazy_static",
203 "memoffset",
203 "memoffset",
204 "scopeguard",
204 "scopeguard",
205 ]
205 ]
206
206
207 [[package]]
207 [[package]]
208 name = "crossbeam-utils"
208 name = "crossbeam-utils"
209 version = "0.7.2"
209 version = "0.7.2"
210 source = "registry+https://github.com/rust-lang/crates.io-index"
210 source = "registry+https://github.com/rust-lang/crates.io-index"
211 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
211 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
212 dependencies = [
212 dependencies = [
213 "autocfg",
213 "autocfg",
214 "cfg-if 0.1.10",
214 "cfg-if 0.1.10",
215 "lazy_static",
215 "lazy_static",
216 ]
216 ]
217
217
218 [[package]]
218 [[package]]
219 name = "crossbeam-utils"
219 name = "crossbeam-utils"
220 version = "0.8.1"
220 version = "0.8.1"
221 source = "registry+https://github.com/rust-lang/crates.io-index"
221 source = "registry+https://github.com/rust-lang/crates.io-index"
222 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
222 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
223 dependencies = [
223 dependencies = [
224 "autocfg",
224 "autocfg",
225 "cfg-if 1.0.0",
225 "cfg-if 1.0.0",
226 "lazy_static",
226 "lazy_static",
227 ]
227 ]
228
228
229 [[package]]
229 [[package]]
230 name = "ctor"
230 name = "ctor"
231 version = "0.1.16"
231 version = "0.1.16"
232 source = "registry+https://github.com/rust-lang/crates.io-index"
232 source = "registry+https://github.com/rust-lang/crates.io-index"
233 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
233 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
234 dependencies = [
234 dependencies = [
235 "quote",
235 "quote",
236 "syn",
236 "syn",
237 ]
237 ]
238
238
239 [[package]]
239 [[package]]
240 name = "derive_more"
240 name = "derive_more"
241 version = "0.99.11"
241 version = "0.99.11"
242 source = "registry+https://github.com/rust-lang/crates.io-index"
242 source = "registry+https://github.com/rust-lang/crates.io-index"
243 checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
243 checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
244 dependencies = [
244 dependencies = [
245 "proc-macro2",
245 "proc-macro2",
246 "quote",
246 "quote",
247 "syn",
247 "syn",
248 ]
248 ]
249
249
250 [[package]]
250 [[package]]
251 name = "difference"
251 name = "difference"
252 version = "2.0.0"
252 version = "2.0.0"
253 source = "registry+https://github.com/rust-lang/crates.io-index"
253 source = "registry+https://github.com/rust-lang/crates.io-index"
254 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
254 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
255
255
256 [[package]]
256 [[package]]
257 name = "either"
257 name = "either"
258 version = "1.6.1"
258 version = "1.6.1"
259 source = "registry+https://github.com/rust-lang/crates.io-index"
259 source = "registry+https://github.com/rust-lang/crates.io-index"
260 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
260 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
261
261
262 [[package]]
262 [[package]]
263 name = "env_logger"
263 name = "env_logger"
264 version = "0.7.1"
264 version = "0.7.1"
265 source = "registry+https://github.com/rust-lang/crates.io-index"
265 source = "registry+https://github.com/rust-lang/crates.io-index"
266 checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
266 checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
267 dependencies = [
267 dependencies = [
268 "atty",
268 "atty",
269 "humantime",
269 "humantime",
270 "log",
270 "log",
271 "regex",
271 "regex",
272 "termcolor",
272 "termcolor",
273 ]
273 ]
274
274
275 [[package]]
275 [[package]]
276 name = "flate2"
276 name = "flate2"
277 version = "1.0.19"
277 version = "1.0.19"
278 source = "registry+https://github.com/rust-lang/crates.io-index"
278 source = "registry+https://github.com/rust-lang/crates.io-index"
279 checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
279 checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
280 dependencies = [
280 dependencies = [
281 "cfg-if 1.0.0",
281 "cfg-if 1.0.0",
282 "crc32fast",
282 "crc32fast",
283 "libc",
283 "libc",
284 "libz-sys",
284 "libz-sys",
285 "miniz_oxide",
285 "miniz_oxide",
286 ]
286 ]
287
287
288 [[package]]
288 [[package]]
289 name = "format-bytes"
289 name = "format-bytes"
290 version = "0.2.2"
290 version = "0.2.2"
291 source = "registry+https://github.com/rust-lang/crates.io-index"
291 source = "registry+https://github.com/rust-lang/crates.io-index"
292 checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762"
292 checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762"
293 dependencies = [
293 dependencies = [
294 "format-bytes-macros",
294 "format-bytes-macros",
295 "proc-macro-hack",
295 "proc-macro-hack",
296 ]
296 ]
297
297
298 [[package]]
298 [[package]]
299 name = "format-bytes-macros"
299 name = "format-bytes-macros"
300 version = "0.3.0"
300 version = "0.3.0"
301 source = "registry+https://github.com/rust-lang/crates.io-index"
301 source = "registry+https://github.com/rust-lang/crates.io-index"
302 checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d"
302 checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d"
303 dependencies = [
303 dependencies = [
304 "proc-macro-hack",
304 "proc-macro-hack",
305 "proc-macro2",
305 "proc-macro2",
306 "quote",
306 "quote",
307 "syn",
307 "syn",
308 ]
308 ]
309
309
310 [[package]]
310 [[package]]
311 name = "fuchsia-cprng"
311 name = "fuchsia-cprng"
312 version = "0.1.1"
312 version = "0.1.1"
313 source = "registry+https://github.com/rust-lang/crates.io-index"
313 source = "registry+https://github.com/rust-lang/crates.io-index"
314 checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
314 checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
315
315
316 [[package]]
316 [[package]]
317 name = "gcc"
317 name = "gcc"
318 version = "0.3.55"
318 version = "0.3.55"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
320 checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
320 checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
321
321
322 [[package]]
322 [[package]]
323 name = "getrandom"
323 name = "getrandom"
324 version = "0.1.15"
324 version = "0.1.15"
325 source = "registry+https://github.com/rust-lang/crates.io-index"
325 source = "registry+https://github.com/rust-lang/crates.io-index"
326 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
326 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
327 dependencies = [
327 dependencies = [
328 "cfg-if 0.1.10",
328 "cfg-if 0.1.10",
329 "libc",
329 "libc",
330 "wasi 0.9.0+wasi-snapshot-preview1",
330 "wasi 0.9.0+wasi-snapshot-preview1",
331 ]
331 ]
332
332
333 [[package]]
333 [[package]]
334 name = "glob"
334 name = "glob"
335 version = "0.3.0"
335 version = "0.3.0"
336 source = "registry+https://github.com/rust-lang/crates.io-index"
336 source = "registry+https://github.com/rust-lang/crates.io-index"
337 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
337 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
338
338
339 [[package]]
339 [[package]]
340 name = "hermit-abi"
340 name = "hermit-abi"
341 version = "0.1.17"
341 version = "0.1.17"
342 source = "registry+https://github.com/rust-lang/crates.io-index"
342 source = "registry+https://github.com/rust-lang/crates.io-index"
343 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
343 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
344 dependencies = [
344 dependencies = [
345 "libc",
345 "libc",
346 ]
346 ]
347
347
348 [[package]]
348 [[package]]
349 name = "hg-core"
349 name = "hg-core"
350 version = "0.1.0"
350 version = "0.1.0"
351 dependencies = [
351 dependencies = [
352 "byteorder",
352 "byteorder",
353 "bytes-cast",
353 "bytes-cast",
354 "clap",
354 "clap",
355 "crossbeam-channel 0.4.4",
355 "crossbeam-channel 0.4.4",
356 "derive_more",
356 "derive_more",
357 "flate2",
357 "flate2",
358 "format-bytes",
358 "format-bytes",
359 "home",
359 "home",
360 "im-rc",
360 "im-rc",
361 "itertools",
361 "lazy_static",
362 "lazy_static",
362 "log",
363 "log",
363 "memmap",
364 "memmap",
364 "micro-timer",
365 "micro-timer",
365 "pretty_assertions",
366 "pretty_assertions",
366 "rand 0.7.3",
367 "rand 0.7.3",
367 "rand_distr",
368 "rand_distr",
368 "rand_pcg",
369 "rand_pcg",
369 "rayon",
370 "rayon",
370 "regex",
371 "regex",
371 "rust-crypto",
372 "rust-crypto",
372 "same-file",
373 "same-file",
373 "tempfile",
374 "tempfile",
374 "twox-hash",
375 "twox-hash",
375 "zstd",
376 "zstd",
376 ]
377 ]
377
378
378 [[package]]
379 [[package]]
379 name = "hg-cpython"
380 name = "hg-cpython"
380 version = "0.1.0"
381 version = "0.1.0"
381 dependencies = [
382 dependencies = [
382 "cpython",
383 "cpython",
383 "crossbeam-channel 0.4.4",
384 "crossbeam-channel 0.4.4",
384 "env_logger",
385 "env_logger",
385 "hg-core",
386 "hg-core",
386 "libc",
387 "libc",
387 "log",
388 "log",
388 ]
389 ]
389
390
390 [[package]]
391 [[package]]
391 name = "home"
392 name = "home"
392 version = "0.5.3"
393 version = "0.5.3"
393 source = "registry+https://github.com/rust-lang/crates.io-index"
394 source = "registry+https://github.com/rust-lang/crates.io-index"
394 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
395 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
395 dependencies = [
396 dependencies = [
396 "winapi",
397 "winapi",
397 ]
398 ]
398
399
399 [[package]]
400 [[package]]
400 name = "humantime"
401 name = "humantime"
401 version = "1.3.0"
402 version = "1.3.0"
402 source = "registry+https://github.com/rust-lang/crates.io-index"
403 source = "registry+https://github.com/rust-lang/crates.io-index"
403 checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
404 checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
404 dependencies = [
405 dependencies = [
405 "quick-error",
406 "quick-error",
406 ]
407 ]
407
408
408 [[package]]
409 [[package]]
409 name = "im-rc"
410 name = "im-rc"
410 version = "15.0.0"
411 version = "15.0.0"
411 source = "registry+https://github.com/rust-lang/crates.io-index"
412 source = "registry+https://github.com/rust-lang/crates.io-index"
412 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
413 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
413 dependencies = [
414 dependencies = [
414 "bitmaps",
415 "bitmaps",
415 "rand_core 0.5.1",
416 "rand_core 0.5.1",
416 "rand_xoshiro",
417 "rand_xoshiro",
417 "sized-chunks",
418 "sized-chunks",
418 "typenum",
419 "typenum",
419 "version_check",
420 "version_check",
420 ]
421 ]
421
422
422 [[package]]
423 [[package]]
423 name = "itertools"
424 name = "itertools"
424 version = "0.9.0"
425 version = "0.9.0"
425 source = "registry+https://github.com/rust-lang/crates.io-index"
426 source = "registry+https://github.com/rust-lang/crates.io-index"
426 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
427 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
427 dependencies = [
428 dependencies = [
428 "either",
429 "either",
429 ]
430 ]
430
431
431 [[package]]
432 [[package]]
432 name = "jobserver"
433 name = "jobserver"
433 version = "0.1.21"
434 version = "0.1.21"
434 source = "registry+https://github.com/rust-lang/crates.io-index"
435 source = "registry+https://github.com/rust-lang/crates.io-index"
435 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
436 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
436 dependencies = [
437 dependencies = [
437 "libc",
438 "libc",
438 ]
439 ]
439
440
440 [[package]]
441 [[package]]
441 name = "lazy_static"
442 name = "lazy_static"
442 version = "1.4.0"
443 version = "1.4.0"
443 source = "registry+https://github.com/rust-lang/crates.io-index"
444 source = "registry+https://github.com/rust-lang/crates.io-index"
444 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
445 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
445
446
446 [[package]]
447 [[package]]
447 name = "libc"
448 name = "libc"
448 version = "0.2.81"
449 version = "0.2.81"
449 source = "registry+https://github.com/rust-lang/crates.io-index"
450 source = "registry+https://github.com/rust-lang/crates.io-index"
450 checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
451 checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
451
452
452 [[package]]
453 [[package]]
453 name = "libz-sys"
454 name = "libz-sys"
454 version = "1.1.2"
455 version = "1.1.2"
455 source = "registry+https://github.com/rust-lang/crates.io-index"
456 source = "registry+https://github.com/rust-lang/crates.io-index"
456 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
457 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
457 dependencies = [
458 dependencies = [
458 "cc",
459 "cc",
459 "pkg-config",
460 "pkg-config",
460 "vcpkg",
461 "vcpkg",
461 ]
462 ]
462
463
463 [[package]]
464 [[package]]
464 name = "log"
465 name = "log"
465 version = "0.4.11"
466 version = "0.4.11"
466 source = "registry+https://github.com/rust-lang/crates.io-index"
467 source = "registry+https://github.com/rust-lang/crates.io-index"
467 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
468 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
468 dependencies = [
469 dependencies = [
469 "cfg-if 0.1.10",
470 "cfg-if 0.1.10",
470 ]
471 ]
471
472
472 [[package]]
473 [[package]]
473 name = "maybe-uninit"
474 name = "maybe-uninit"
474 version = "2.0.0"
475 version = "2.0.0"
475 source = "registry+https://github.com/rust-lang/crates.io-index"
476 source = "registry+https://github.com/rust-lang/crates.io-index"
476 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
477 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
477
478
478 [[package]]
479 [[package]]
479 name = "memchr"
480 name = "memchr"
480 version = "2.3.4"
481 version = "2.3.4"
481 source = "registry+https://github.com/rust-lang/crates.io-index"
482 source = "registry+https://github.com/rust-lang/crates.io-index"
482 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
483 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
483
484
484 [[package]]
485 [[package]]
485 name = "memmap"
486 name = "memmap"
486 version = "0.7.0"
487 version = "0.7.0"
487 source = "registry+https://github.com/rust-lang/crates.io-index"
488 source = "registry+https://github.com/rust-lang/crates.io-index"
488 checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
489 checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
489 dependencies = [
490 dependencies = [
490 "libc",
491 "libc",
491 "winapi",
492 "winapi",
492 ]
493 ]
493
494
494 [[package]]
495 [[package]]
495 name = "memoffset"
496 name = "memoffset"
496 version = "0.6.1"
497 version = "0.6.1"
497 source = "registry+https://github.com/rust-lang/crates.io-index"
498 source = "registry+https://github.com/rust-lang/crates.io-index"
498 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
499 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
499 dependencies = [
500 dependencies = [
500 "autocfg",
501 "autocfg",
501 ]
502 ]
502
503
503 [[package]]
504 [[package]]
504 name = "micro-timer"
505 name = "micro-timer"
505 version = "0.3.1"
506 version = "0.3.1"
506 source = "registry+https://github.com/rust-lang/crates.io-index"
507 source = "registry+https://github.com/rust-lang/crates.io-index"
507 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
508 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
508 dependencies = [
509 dependencies = [
509 "micro-timer-macros",
510 "micro-timer-macros",
510 "scopeguard",
511 "scopeguard",
511 ]
512 ]
512
513
513 [[package]]
514 [[package]]
514 name = "micro-timer-macros"
515 name = "micro-timer-macros"
515 version = "0.3.1"
516 version = "0.3.1"
516 source = "registry+https://github.com/rust-lang/crates.io-index"
517 source = "registry+https://github.com/rust-lang/crates.io-index"
517 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
518 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
518 dependencies = [
519 dependencies = [
519 "proc-macro2",
520 "proc-macro2",
520 "quote",
521 "quote",
521 "scopeguard",
522 "scopeguard",
522 "syn",
523 "syn",
523 ]
524 ]
524
525
525 [[package]]
526 [[package]]
526 name = "miniz_oxide"
527 name = "miniz_oxide"
527 version = "0.4.3"
528 version = "0.4.3"
528 source = "registry+https://github.com/rust-lang/crates.io-index"
529 source = "registry+https://github.com/rust-lang/crates.io-index"
529 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
530 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
530 dependencies = [
531 dependencies = [
531 "adler",
532 "adler",
532 "autocfg",
533 "autocfg",
533 ]
534 ]
534
535
535 [[package]]
536 [[package]]
536 name = "num-integer"
537 name = "num-integer"
537 version = "0.1.44"
538 version = "0.1.44"
538 source = "registry+https://github.com/rust-lang/crates.io-index"
539 source = "registry+https://github.com/rust-lang/crates.io-index"
539 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
540 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
540 dependencies = [
541 dependencies = [
541 "autocfg",
542 "autocfg",
542 "num-traits",
543 "num-traits",
543 ]
544 ]
544
545
545 [[package]]
546 [[package]]
546 name = "num-traits"
547 name = "num-traits"
547 version = "0.2.14"
548 version = "0.2.14"
548 source = "registry+https://github.com/rust-lang/crates.io-index"
549 source = "registry+https://github.com/rust-lang/crates.io-index"
549 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
550 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
550 dependencies = [
551 dependencies = [
551 "autocfg",
552 "autocfg",
552 ]
553 ]
553
554
554 [[package]]
555 [[package]]
555 name = "num_cpus"
556 name = "num_cpus"
556 version = "1.13.0"
557 version = "1.13.0"
557 source = "registry+https://github.com/rust-lang/crates.io-index"
558 source = "registry+https://github.com/rust-lang/crates.io-index"
558 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
559 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
559 dependencies = [
560 dependencies = [
560 "hermit-abi",
561 "hermit-abi",
561 "libc",
562 "libc",
562 ]
563 ]
563
564
564 [[package]]
565 [[package]]
565 name = "output_vt100"
566 name = "output_vt100"
566 version = "0.1.2"
567 version = "0.1.2"
567 source = "registry+https://github.com/rust-lang/crates.io-index"
568 source = "registry+https://github.com/rust-lang/crates.io-index"
568 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
569 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
569 dependencies = [
570 dependencies = [
570 "winapi",
571 "winapi",
571 ]
572 ]
572
573
573 [[package]]
574 [[package]]
574 name = "paste"
575 name = "paste"
575 version = "0.1.18"
576 version = "0.1.18"
576 source = "registry+https://github.com/rust-lang/crates.io-index"
577 source = "registry+https://github.com/rust-lang/crates.io-index"
577 checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
578 checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
578 dependencies = [
579 dependencies = [
579 "paste-impl",
580 "paste-impl",
580 "proc-macro-hack",
581 "proc-macro-hack",
581 ]
582 ]
582
583
583 [[package]]
584 [[package]]
584 name = "paste-impl"
585 name = "paste-impl"
585 version = "0.1.18"
586 version = "0.1.18"
586 source = "registry+https://github.com/rust-lang/crates.io-index"
587 source = "registry+https://github.com/rust-lang/crates.io-index"
587 checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
588 checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
588 dependencies = [
589 dependencies = [
589 "proc-macro-hack",
590 "proc-macro-hack",
590 ]
591 ]
591
592
592 [[package]]
593 [[package]]
593 name = "pkg-config"
594 name = "pkg-config"
594 version = "0.3.19"
595 version = "0.3.19"
595 source = "registry+https://github.com/rust-lang/crates.io-index"
596 source = "registry+https://github.com/rust-lang/crates.io-index"
596 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
597 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
597
598
598 [[package]]
599 [[package]]
599 name = "ppv-lite86"
600 name = "ppv-lite86"
600 version = "0.2.10"
601 version = "0.2.10"
601 source = "registry+https://github.com/rust-lang/crates.io-index"
602 source = "registry+https://github.com/rust-lang/crates.io-index"
602 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
603 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
603
604
604 [[package]]
605 [[package]]
605 name = "pretty_assertions"
606 name = "pretty_assertions"
606 version = "0.6.1"
607 version = "0.6.1"
607 source = "registry+https://github.com/rust-lang/crates.io-index"
608 source = "registry+https://github.com/rust-lang/crates.io-index"
608 checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
609 checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
609 dependencies = [
610 dependencies = [
610 "ansi_term",
611 "ansi_term",
611 "ctor",
612 "ctor",
612 "difference",
613 "difference",
613 "output_vt100",
614 "output_vt100",
614 ]
615 ]
615
616
616 [[package]]
617 [[package]]
617 name = "proc-macro-hack"
618 name = "proc-macro-hack"
618 version = "0.5.19"
619 version = "0.5.19"
619 source = "registry+https://github.com/rust-lang/crates.io-index"
620 source = "registry+https://github.com/rust-lang/crates.io-index"
620 checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
621 checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
621
622
622 [[package]]
623 [[package]]
623 name = "proc-macro2"
624 name = "proc-macro2"
624 version = "1.0.24"
625 version = "1.0.24"
625 source = "registry+https://github.com/rust-lang/crates.io-index"
626 source = "registry+https://github.com/rust-lang/crates.io-index"
626 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
627 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
627 dependencies = [
628 dependencies = [
628 "unicode-xid",
629 "unicode-xid",
629 ]
630 ]
630
631
631 [[package]]
632 [[package]]
632 name = "python27-sys"
633 name = "python27-sys"
633 version = "0.5.2"
634 version = "0.5.2"
634 source = "registry+https://github.com/rust-lang/crates.io-index"
635 source = "registry+https://github.com/rust-lang/crates.io-index"
635 checksum = "f485897ed7048f5032317c4e427800ef9f2053355516524d73952b8b07032054"
636 checksum = "f485897ed7048f5032317c4e427800ef9f2053355516524d73952b8b07032054"
636 dependencies = [
637 dependencies = [
637 "libc",
638 "libc",
638 "regex",
639 "regex",
639 ]
640 ]
640
641
641 [[package]]
642 [[package]]
642 name = "python3-sys"
643 name = "python3-sys"
643 version = "0.5.2"
644 version = "0.5.2"
644 source = "registry+https://github.com/rust-lang/crates.io-index"
645 source = "registry+https://github.com/rust-lang/crates.io-index"
645 checksum = "5b29b99c6868eb02beb3bf6ed025c8bcdf02efc149b8e80347d3e5d059a806db"
646 checksum = "5b29b99c6868eb02beb3bf6ed025c8bcdf02efc149b8e80347d3e5d059a806db"
646 dependencies = [
647 dependencies = [
647 "libc",
648 "libc",
648 "regex",
649 "regex",
649 ]
650 ]
650
651
651 [[package]]
652 [[package]]
652 name = "quick-error"
653 name = "quick-error"
653 version = "1.2.3"
654 version = "1.2.3"
654 source = "registry+https://github.com/rust-lang/crates.io-index"
655 source = "registry+https://github.com/rust-lang/crates.io-index"
655 checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
656 checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
656
657
657 [[package]]
658 [[package]]
658 name = "quote"
659 name = "quote"
659 version = "1.0.7"
660 version = "1.0.7"
660 source = "registry+https://github.com/rust-lang/crates.io-index"
661 source = "registry+https://github.com/rust-lang/crates.io-index"
661 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
662 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
662 dependencies = [
663 dependencies = [
663 "proc-macro2",
664 "proc-macro2",
664 ]
665 ]
665
666
666 [[package]]
667 [[package]]
667 name = "rand"
668 name = "rand"
668 version = "0.3.23"
669 version = "0.3.23"
669 source = "registry+https://github.com/rust-lang/crates.io-index"
670 source = "registry+https://github.com/rust-lang/crates.io-index"
670 checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
671 checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
671 dependencies = [
672 dependencies = [
672 "libc",
673 "libc",
673 "rand 0.4.6",
674 "rand 0.4.6",
674 ]
675 ]
675
676
676 [[package]]
677 [[package]]
677 name = "rand"
678 name = "rand"
678 version = "0.4.6"
679 version = "0.4.6"
679 source = "registry+https://github.com/rust-lang/crates.io-index"
680 source = "registry+https://github.com/rust-lang/crates.io-index"
680 checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
681 checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
681 dependencies = [
682 dependencies = [
682 "fuchsia-cprng",
683 "fuchsia-cprng",
683 "libc",
684 "libc",
684 "rand_core 0.3.1",
685 "rand_core 0.3.1",
685 "rdrand",
686 "rdrand",
686 "winapi",
687 "winapi",
687 ]
688 ]
688
689
689 [[package]]
690 [[package]]
690 name = "rand"
691 name = "rand"
691 version = "0.7.3"
692 version = "0.7.3"
692 source = "registry+https://github.com/rust-lang/crates.io-index"
693 source = "registry+https://github.com/rust-lang/crates.io-index"
693 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
694 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
694 dependencies = [
695 dependencies = [
695 "getrandom",
696 "getrandom",
696 "libc",
697 "libc",
697 "rand_chacha",
698 "rand_chacha",
698 "rand_core 0.5.1",
699 "rand_core 0.5.1",
699 "rand_hc",
700 "rand_hc",
700 ]
701 ]
701
702
702 [[package]]
703 [[package]]
703 name = "rand_chacha"
704 name = "rand_chacha"
704 version = "0.2.2"
705 version = "0.2.2"
705 source = "registry+https://github.com/rust-lang/crates.io-index"
706 source = "registry+https://github.com/rust-lang/crates.io-index"
706 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
707 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
707 dependencies = [
708 dependencies = [
708 "ppv-lite86",
709 "ppv-lite86",
709 "rand_core 0.5.1",
710 "rand_core 0.5.1",
710 ]
711 ]
711
712
712 [[package]]
713 [[package]]
713 name = "rand_core"
714 name = "rand_core"
714 version = "0.3.1"
715 version = "0.3.1"
715 source = "registry+https://github.com/rust-lang/crates.io-index"
716 source = "registry+https://github.com/rust-lang/crates.io-index"
716 checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
717 checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
717 dependencies = [
718 dependencies = [
718 "rand_core 0.4.2",
719 "rand_core 0.4.2",
719 ]
720 ]
720
721
721 [[package]]
722 [[package]]
722 name = "rand_core"
723 name = "rand_core"
723 version = "0.4.2"
724 version = "0.4.2"
724 source = "registry+https://github.com/rust-lang/crates.io-index"
725 source = "registry+https://github.com/rust-lang/crates.io-index"
725 checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
726 checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
726
727
727 [[package]]
728 [[package]]
728 name = "rand_core"
729 name = "rand_core"
729 version = "0.5.1"
730 version = "0.5.1"
730 source = "registry+https://github.com/rust-lang/crates.io-index"
731 source = "registry+https://github.com/rust-lang/crates.io-index"
731 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
732 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
732 dependencies = [
733 dependencies = [
733 "getrandom",
734 "getrandom",
734 ]
735 ]
735
736
736 [[package]]
737 [[package]]
737 name = "rand_distr"
738 name = "rand_distr"
738 version = "0.2.2"
739 version = "0.2.2"
739 source = "registry+https://github.com/rust-lang/crates.io-index"
740 source = "registry+https://github.com/rust-lang/crates.io-index"
740 checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
741 checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
741 dependencies = [
742 dependencies = [
742 "rand 0.7.3",
743 "rand 0.7.3",
743 ]
744 ]
744
745
745 [[package]]
746 [[package]]
746 name = "rand_hc"
747 name = "rand_hc"
747 version = "0.2.0"
748 version = "0.2.0"
748 source = "registry+https://github.com/rust-lang/crates.io-index"
749 source = "registry+https://github.com/rust-lang/crates.io-index"
749 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
750 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
750 dependencies = [
751 dependencies = [
751 "rand_core 0.5.1",
752 "rand_core 0.5.1",
752 ]
753 ]
753
754
754 [[package]]
755 [[package]]
755 name = "rand_pcg"
756 name = "rand_pcg"
756 version = "0.2.1"
757 version = "0.2.1"
757 source = "registry+https://github.com/rust-lang/crates.io-index"
758 source = "registry+https://github.com/rust-lang/crates.io-index"
758 checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
759 checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
759 dependencies = [
760 dependencies = [
760 "rand_core 0.5.1",
761 "rand_core 0.5.1",
761 ]
762 ]
762
763
763 [[package]]
764 [[package]]
764 name = "rand_xoshiro"
765 name = "rand_xoshiro"
765 version = "0.4.0"
766 version = "0.4.0"
766 source = "registry+https://github.com/rust-lang/crates.io-index"
767 source = "registry+https://github.com/rust-lang/crates.io-index"
767 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
768 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
768 dependencies = [
769 dependencies = [
769 "rand_core 0.5.1",
770 "rand_core 0.5.1",
770 ]
771 ]
771
772
772 [[package]]
773 [[package]]
773 name = "rayon"
774 name = "rayon"
774 version = "1.5.0"
775 version = "1.5.0"
775 source = "registry+https://github.com/rust-lang/crates.io-index"
776 source = "registry+https://github.com/rust-lang/crates.io-index"
776 checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
777 checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
777 dependencies = [
778 dependencies = [
778 "autocfg",
779 "autocfg",
779 "crossbeam-deque",
780 "crossbeam-deque",
780 "either",
781 "either",
781 "rayon-core",
782 "rayon-core",
782 ]
783 ]
783
784
784 [[package]]
785 [[package]]
785 name = "rayon-core"
786 name = "rayon-core"
786 version = "1.9.0"
787 version = "1.9.0"
787 source = "registry+https://github.com/rust-lang/crates.io-index"
788 source = "registry+https://github.com/rust-lang/crates.io-index"
788 checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
789 checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
789 dependencies = [
790 dependencies = [
790 "crossbeam-channel 0.5.0",
791 "crossbeam-channel 0.5.0",
791 "crossbeam-deque",
792 "crossbeam-deque",
792 "crossbeam-utils 0.8.1",
793 "crossbeam-utils 0.8.1",
793 "lazy_static",
794 "lazy_static",
794 "num_cpus",
795 "num_cpus",
795 ]
796 ]
796
797
797 [[package]]
798 [[package]]
798 name = "rdrand"
799 name = "rdrand"
799 version = "0.4.0"
800 version = "0.4.0"
800 source = "registry+https://github.com/rust-lang/crates.io-index"
801 source = "registry+https://github.com/rust-lang/crates.io-index"
801 checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
802 checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
802 dependencies = [
803 dependencies = [
803 "rand_core 0.3.1",
804 "rand_core 0.3.1",
804 ]
805 ]
805
806
806 [[package]]
807 [[package]]
807 name = "redox_syscall"
808 name = "redox_syscall"
808 version = "0.1.57"
809 version = "0.1.57"
809 source = "registry+https://github.com/rust-lang/crates.io-index"
810 source = "registry+https://github.com/rust-lang/crates.io-index"
810 checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
811 checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
811
812
812 [[package]]
813 [[package]]
813 name = "regex"
814 name = "regex"
814 version = "1.4.2"
815 version = "1.4.2"
815 source = "registry+https://github.com/rust-lang/crates.io-index"
816 source = "registry+https://github.com/rust-lang/crates.io-index"
816 checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
817 checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
817 dependencies = [
818 dependencies = [
818 "aho-corasick",
819 "aho-corasick",
819 "memchr",
820 "memchr",
820 "regex-syntax",
821 "regex-syntax",
821 "thread_local",
822 "thread_local",
822 ]
823 ]
823
824
824 [[package]]
825 [[package]]
825 name = "regex-syntax"
826 name = "regex-syntax"
826 version = "0.6.21"
827 version = "0.6.21"
827 source = "registry+https://github.com/rust-lang/crates.io-index"
828 source = "registry+https://github.com/rust-lang/crates.io-index"
828 checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
829 checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
829
830
830 [[package]]
831 [[package]]
831 name = "remove_dir_all"
832 name = "remove_dir_all"
832 version = "0.5.3"
833 version = "0.5.3"
833 source = "registry+https://github.com/rust-lang/crates.io-index"
834 source = "registry+https://github.com/rust-lang/crates.io-index"
834 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
835 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
835 dependencies = [
836 dependencies = [
836 "winapi",
837 "winapi",
837 ]
838 ]
838
839
839 [[package]]
840 [[package]]
840 name = "rhg"
841 name = "rhg"
841 version = "0.1.0"
842 version = "0.1.0"
842 dependencies = [
843 dependencies = [
843 "chrono",
844 "chrono",
844 "clap",
845 "clap",
845 "derive_more",
846 "derive_more",
846 "env_logger",
847 "env_logger",
847 "format-bytes",
848 "format-bytes",
848 "hg-core",
849 "hg-core",
849 "lazy_static",
850 "lazy_static",
850 "log",
851 "log",
851 "micro-timer",
852 "micro-timer",
852 "regex",
853 "regex",
853 "users",
854 "users",
854 ]
855 ]
855
856
856 [[package]]
857 [[package]]
857 name = "rust-crypto"
858 name = "rust-crypto"
858 version = "0.2.36"
859 version = "0.2.36"
859 source = "registry+https://github.com/rust-lang/crates.io-index"
860 source = "registry+https://github.com/rust-lang/crates.io-index"
860 checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
861 checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
861 dependencies = [
862 dependencies = [
862 "gcc",
863 "gcc",
863 "libc",
864 "libc",
864 "rand 0.3.23",
865 "rand 0.3.23",
865 "rustc-serialize",
866 "rustc-serialize",
866 "time",
867 "time",
867 ]
868 ]
868
869
869 [[package]]
870 [[package]]
870 name = "rustc-serialize"
871 name = "rustc-serialize"
871 version = "0.3.24"
872 version = "0.3.24"
872 source = "registry+https://github.com/rust-lang/crates.io-index"
873 source = "registry+https://github.com/rust-lang/crates.io-index"
873 checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
874 checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
874
875
875 [[package]]
876 [[package]]
876 name = "same-file"
877 name = "same-file"
877 version = "1.0.6"
878 version = "1.0.6"
878 source = "registry+https://github.com/rust-lang/crates.io-index"
879 source = "registry+https://github.com/rust-lang/crates.io-index"
879 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
880 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
880 dependencies = [
881 dependencies = [
881 "winapi-util",
882 "winapi-util",
882 ]
883 ]
883
884
884 [[package]]
885 [[package]]
885 name = "scopeguard"
886 name = "scopeguard"
886 version = "1.1.0"
887 version = "1.1.0"
887 source = "registry+https://github.com/rust-lang/crates.io-index"
888 source = "registry+https://github.com/rust-lang/crates.io-index"
888 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
889 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
889
890
890 [[package]]
891 [[package]]
891 name = "sized-chunks"
892 name = "sized-chunks"
892 version = "0.6.2"
893 version = "0.6.2"
893 source = "registry+https://github.com/rust-lang/crates.io-index"
894 source = "registry+https://github.com/rust-lang/crates.io-index"
894 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
895 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
895 dependencies = [
896 dependencies = [
896 "bitmaps",
897 "bitmaps",
897 "typenum",
898 "typenum",
898 ]
899 ]
899
900
900 [[package]]
901 [[package]]
901 name = "static_assertions"
902 name = "static_assertions"
902 version = "1.1.0"
903 version = "1.1.0"
903 source = "registry+https://github.com/rust-lang/crates.io-index"
904 source = "registry+https://github.com/rust-lang/crates.io-index"
904 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
905 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
905
906
906 [[package]]
907 [[package]]
907 name = "strsim"
908 name = "strsim"
908 version = "0.8.0"
909 version = "0.8.0"
909 source = "registry+https://github.com/rust-lang/crates.io-index"
910 source = "registry+https://github.com/rust-lang/crates.io-index"
910 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
911 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
911
912
912 [[package]]
913 [[package]]
913 name = "syn"
914 name = "syn"
914 version = "1.0.54"
915 version = "1.0.54"
915 source = "registry+https://github.com/rust-lang/crates.io-index"
916 source = "registry+https://github.com/rust-lang/crates.io-index"
916 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
917 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
917 dependencies = [
918 dependencies = [
918 "proc-macro2",
919 "proc-macro2",
919 "quote",
920 "quote",
920 "unicode-xid",
921 "unicode-xid",
921 ]
922 ]
922
923
923 [[package]]
924 [[package]]
924 name = "tempfile"
925 name = "tempfile"
925 version = "3.1.0"
926 version = "3.1.0"
926 source = "registry+https://github.com/rust-lang/crates.io-index"
927 source = "registry+https://github.com/rust-lang/crates.io-index"
927 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
928 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
928 dependencies = [
929 dependencies = [
929 "cfg-if 0.1.10",
930 "cfg-if 0.1.10",
930 "libc",
931 "libc",
931 "rand 0.7.3",
932 "rand 0.7.3",
932 "redox_syscall",
933 "redox_syscall",
933 "remove_dir_all",
934 "remove_dir_all",
934 "winapi",
935 "winapi",
935 ]
936 ]
936
937
937 [[package]]
938 [[package]]
938 name = "termcolor"
939 name = "termcolor"
939 version = "1.1.2"
940 version = "1.1.2"
940 source = "registry+https://github.com/rust-lang/crates.io-index"
941 source = "registry+https://github.com/rust-lang/crates.io-index"
941 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
942 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
942 dependencies = [
943 dependencies = [
943 "winapi-util",
944 "winapi-util",
944 ]
945 ]
945
946
946 [[package]]
947 [[package]]
947 name = "textwrap"
948 name = "textwrap"
948 version = "0.11.0"
949 version = "0.11.0"
949 source = "registry+https://github.com/rust-lang/crates.io-index"
950 source = "registry+https://github.com/rust-lang/crates.io-index"
950 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
951 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
951 dependencies = [
952 dependencies = [
952 "unicode-width",
953 "unicode-width",
953 ]
954 ]
954
955
955 [[package]]
956 [[package]]
956 name = "thread_local"
957 name = "thread_local"
957 version = "1.0.1"
958 version = "1.0.1"
958 source = "registry+https://github.com/rust-lang/crates.io-index"
959 source = "registry+https://github.com/rust-lang/crates.io-index"
959 checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
960 checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
960 dependencies = [
961 dependencies = [
961 "lazy_static",
962 "lazy_static",
962 ]
963 ]
963
964
964 [[package]]
965 [[package]]
965 name = "time"
966 name = "time"
966 version = "0.1.44"
967 version = "0.1.44"
967 source = "registry+https://github.com/rust-lang/crates.io-index"
968 source = "registry+https://github.com/rust-lang/crates.io-index"
968 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
969 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
969 dependencies = [
970 dependencies = [
970 "libc",
971 "libc",
971 "wasi 0.10.0+wasi-snapshot-preview1",
972 "wasi 0.10.0+wasi-snapshot-preview1",
972 "winapi",
973 "winapi",
973 ]
974 ]
974
975
975 [[package]]
976 [[package]]
976 name = "twox-hash"
977 name = "twox-hash"
977 version = "1.6.0"
978 version = "1.6.0"
978 source = "registry+https://github.com/rust-lang/crates.io-index"
979 source = "registry+https://github.com/rust-lang/crates.io-index"
979 checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
980 checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
980 dependencies = [
981 dependencies = [
981 "cfg-if 0.1.10",
982 "cfg-if 0.1.10",
982 "rand 0.7.3",
983 "rand 0.7.3",
983 "static_assertions",
984 "static_assertions",
984 ]
985 ]
985
986
986 [[package]]
987 [[package]]
987 name = "typenum"
988 name = "typenum"
988 version = "1.12.0"
989 version = "1.12.0"
989 source = "registry+https://github.com/rust-lang/crates.io-index"
990 source = "registry+https://github.com/rust-lang/crates.io-index"
990 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
991 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
991
992
992 [[package]]
993 [[package]]
993 name = "unicode-width"
994 name = "unicode-width"
994 version = "0.1.8"
995 version = "0.1.8"
995 source = "registry+https://github.com/rust-lang/crates.io-index"
996 source = "registry+https://github.com/rust-lang/crates.io-index"
996 checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
997 checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
997
998
998 [[package]]
999 [[package]]
999 name = "unicode-xid"
1000 name = "unicode-xid"
1000 version = "0.2.1"
1001 version = "0.2.1"
1001 source = "registry+https://github.com/rust-lang/crates.io-index"
1002 source = "registry+https://github.com/rust-lang/crates.io-index"
1002 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1003 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1003
1004
1004 [[package]]
1005 [[package]]
1005 name = "users"
1006 name = "users"
1006 version = "0.11.0"
1007 version = "0.11.0"
1007 source = "registry+https://github.com/rust-lang/crates.io-index"
1008 source = "registry+https://github.com/rust-lang/crates.io-index"
1008 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1009 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1009 dependencies = [
1010 dependencies = [
1010 "libc",
1011 "libc",
1011 "log",
1012 "log",
1012 ]
1013 ]
1013
1014
1014 [[package]]
1015 [[package]]
1015 name = "vcpkg"
1016 name = "vcpkg"
1016 version = "0.2.11"
1017 version = "0.2.11"
1017 source = "registry+https://github.com/rust-lang/crates.io-index"
1018 source = "registry+https://github.com/rust-lang/crates.io-index"
1018 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1019 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1019
1020
1020 [[package]]
1021 [[package]]
1021 name = "vec_map"
1022 name = "vec_map"
1022 version = "0.8.2"
1023 version = "0.8.2"
1023 source = "registry+https://github.com/rust-lang/crates.io-index"
1024 source = "registry+https://github.com/rust-lang/crates.io-index"
1024 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1025 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1025
1026
1026 [[package]]
1027 [[package]]
1027 name = "version_check"
1028 name = "version_check"
1028 version = "0.9.2"
1029 version = "0.9.2"
1029 source = "registry+https://github.com/rust-lang/crates.io-index"
1030 source = "registry+https://github.com/rust-lang/crates.io-index"
1030 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1031 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1031
1032
1032 [[package]]
1033 [[package]]
1033 name = "wasi"
1034 name = "wasi"
1034 version = "0.9.0+wasi-snapshot-preview1"
1035 version = "0.9.0+wasi-snapshot-preview1"
1035 source = "registry+https://github.com/rust-lang/crates.io-index"
1036 source = "registry+https://github.com/rust-lang/crates.io-index"
1036 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1037 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1037
1038
1038 [[package]]
1039 [[package]]
1039 name = "wasi"
1040 name = "wasi"
1040 version = "0.10.0+wasi-snapshot-preview1"
1041 version = "0.10.0+wasi-snapshot-preview1"
1041 source = "registry+https://github.com/rust-lang/crates.io-index"
1042 source = "registry+https://github.com/rust-lang/crates.io-index"
1042 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1043 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1043
1044
1044 [[package]]
1045 [[package]]
1045 name = "winapi"
1046 name = "winapi"
1046 version = "0.3.9"
1047 version = "0.3.9"
1047 source = "registry+https://github.com/rust-lang/crates.io-index"
1048 source = "registry+https://github.com/rust-lang/crates.io-index"
1048 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1049 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1049 dependencies = [
1050 dependencies = [
1050 "winapi-i686-pc-windows-gnu",
1051 "winapi-i686-pc-windows-gnu",
1051 "winapi-x86_64-pc-windows-gnu",
1052 "winapi-x86_64-pc-windows-gnu",
1052 ]
1053 ]
1053
1054
1054 [[package]]
1055 [[package]]
1055 name = "winapi-i686-pc-windows-gnu"
1056 name = "winapi-i686-pc-windows-gnu"
1056 version = "0.4.0"
1057 version = "0.4.0"
1057 source = "registry+https://github.com/rust-lang/crates.io-index"
1058 source = "registry+https://github.com/rust-lang/crates.io-index"
1058 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1059 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1059
1060
1060 [[package]]
1061 [[package]]
1061 name = "winapi-util"
1062 name = "winapi-util"
1062 version = "0.1.5"
1063 version = "0.1.5"
1063 source = "registry+https://github.com/rust-lang/crates.io-index"
1064 source = "registry+https://github.com/rust-lang/crates.io-index"
1064 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1065 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1065 dependencies = [
1066 dependencies = [
1066 "winapi",
1067 "winapi",
1067 ]
1068 ]
1068
1069
1069 [[package]]
1070 [[package]]
1070 name = "winapi-x86_64-pc-windows-gnu"
1071 name = "winapi-x86_64-pc-windows-gnu"
1071 version = "0.4.0"
1072 version = "0.4.0"
1072 source = "registry+https://github.com/rust-lang/crates.io-index"
1073 source = "registry+https://github.com/rust-lang/crates.io-index"
1073 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1074 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1074
1075
1075 [[package]]
1076 [[package]]
1076 name = "zstd"
1077 name = "zstd"
1077 version = "0.5.3+zstd.1.4.5"
1078 version = "0.5.3+zstd.1.4.5"
1078 source = "registry+https://github.com/rust-lang/crates.io-index"
1079 source = "registry+https://github.com/rust-lang/crates.io-index"
1079 checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
1080 checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
1080 dependencies = [
1081 dependencies = [
1081 "zstd-safe",
1082 "zstd-safe",
1082 ]
1083 ]
1083
1084
1084 [[package]]
1085 [[package]]
1085 name = "zstd-safe"
1086 name = "zstd-safe"
1086 version = "2.0.5+zstd.1.4.5"
1087 version = "2.0.5+zstd.1.4.5"
1087 source = "registry+https://github.com/rust-lang/crates.io-index"
1088 source = "registry+https://github.com/rust-lang/crates.io-index"
1088 checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
1089 checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
1089 dependencies = [
1090 dependencies = [
1090 "libc",
1091 "libc",
1091 "zstd-sys",
1092 "zstd-sys",
1092 ]
1093 ]
1093
1094
1094 [[package]]
1095 [[package]]
1095 name = "zstd-sys"
1096 name = "zstd-sys"
1096 version = "1.4.17+zstd.1.4.5"
1097 version = "1.4.17+zstd.1.4.5"
1097 source = "registry+https://github.com/rust-lang/crates.io-index"
1098 source = "registry+https://github.com/rust-lang/crates.io-index"
1098 checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
1099 checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
1099 dependencies = [
1100 dependencies = [
1100 "cc",
1101 "cc",
1101 "glob",
1102 "glob",
1102 "itertools",
1103 "itertools",
1103 "libc",
1104 "libc",
1104 ]
1105 ]
@@ -1,43 +1,44 b''
1 [package]
1 [package]
2 name = "hg-core"
2 name = "hg-core"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
6 edition = "2018"
6 edition = "2018"
7
7
8 [lib]
8 [lib]
9 name = "hg"
9 name = "hg"
10
10
11 [dependencies]
11 [dependencies]
12 bytes-cast = "0.2"
12 bytes-cast = "0.2"
13 byteorder = "1.3.4"
13 byteorder = "1.3.4"
14 derive_more = "0.99"
14 derive_more = "0.99"
15 home = "0.5"
15 home = "0.5"
16 im-rc = "15.0.*"
16 im-rc = "15.0.*"
17 itertools = "0.9"
17 lazy_static = "1.4.0"
18 lazy_static = "1.4.0"
18 rand = "0.7.3"
19 rand = "0.7.3"
19 rand_pcg = "0.2.1"
20 rand_pcg = "0.2.1"
20 rand_distr = "0.2.2"
21 rand_distr = "0.2.2"
21 rayon = "1.3.0"
22 rayon = "1.3.0"
22 regex = "1.3.9"
23 regex = "1.3.9"
23 twox-hash = "1.5.0"
24 twox-hash = "1.5.0"
24 same-file = "1.0.6"
25 same-file = "1.0.6"
25 crossbeam-channel = "0.4"
26 crossbeam-channel = "0.4"
26 micro-timer = "0.3.0"
27 micro-timer = "0.3.0"
27 log = "0.4.8"
28 log = "0.4.8"
28 memmap = "0.7.0"
29 memmap = "0.7.0"
29 zstd = "0.5.3"
30 zstd = "0.5.3"
30 rust-crypto = "0.2.36"
31 rust-crypto = "0.2.36"
31 format-bytes = "0.2.2"
32 format-bytes = "0.2.2"
32
33
33 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
34 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
34 # we have a clearer view of which backend is the fastest.
35 # we have a clearer view of which backend is the fastest.
35 [dependencies.flate2]
36 [dependencies.flate2]
36 version = "1.0.16"
37 version = "1.0.16"
37 features = ["zlib"]
38 features = ["zlib"]
38 default-features = false
39 default-features = false
39
40
40 [dev-dependencies]
41 [dev-dependencies]
41 clap = "*"
42 clap = "*"
42 pretty_assertions = "0.6.1"
43 pretty_assertions = "0.6.1"
43 tempfile = "3.1.0"
44 tempfile = "3.1.0"
@@ -1,119 +1,132 b''
1 // dirstate module
1 // dirstate module
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::revlog::Node;
9 use crate::revlog::Node;
10 use crate::{utils::hg_path::HgPathBuf, FastHashMap};
10 use crate::{utils::hg_path::HgPathBuf, FastHashMap};
11 use bytes_cast::{unaligned, BytesCast};
11 use bytes_cast::{unaligned, BytesCast};
12 use std::convert::TryFrom;
12 use std::convert::TryFrom;
13
13
14 pub mod dirs_multiset;
14 pub mod dirs_multiset;
15 pub mod dirstate_map;
15 pub mod dirstate_map;
16 pub mod parsers;
16 pub mod parsers;
17 pub mod status;
17 pub mod status;
18
18
19 #[derive(Debug, PartialEq, Clone, BytesCast)]
19 #[derive(Debug, PartialEq, Clone, BytesCast)]
20 #[repr(C)]
20 #[repr(C)]
21 pub struct DirstateParents {
21 pub struct DirstateParents {
22 pub p1: Node,
22 pub p1: Node,
23 pub p2: Node,
23 pub p2: Node,
24 }
24 }
25
25
26 /// The C implementation uses all signed types. This will be an issue
26 /// The C implementation uses all signed types. This will be an issue
27 /// either when 4GB+ source files are commonplace or in 2038, whichever
27 /// either when 4GB+ source files are commonplace or in 2038, whichever
28 /// comes first.
28 /// comes first.
29 #[derive(Debug, PartialEq, Copy, Clone)]
29 #[derive(Debug, PartialEq, Copy, Clone)]
30 pub struct DirstateEntry {
30 pub struct DirstateEntry {
31 pub state: EntryState,
31 pub state: EntryState,
32 pub mode: i32,
32 pub mode: i32,
33 pub mtime: i32,
33 pub mtime: i32,
34 pub size: i32,
34 pub size: i32,
35 }
35 }
36
36
37 impl DirstateEntry {
37 impl DirstateEntry {
38 pub fn is_non_normal(&self) -> bool {
38 pub fn is_non_normal(&self) -> bool {
39 self.state != EntryState::Normal || self.mtime == MTIME_UNSET
39 self.state != EntryState::Normal || self.mtime == MTIME_UNSET
40 }
40 }
41
41
42 pub fn is_from_other_parent(&self) -> bool {
42 pub fn is_from_other_parent(&self) -> bool {
43 self.state == EntryState::Normal && self.size == SIZE_FROM_OTHER_PARENT
43 self.state == EntryState::Normal && self.size == SIZE_FROM_OTHER_PARENT
44 }
44 }
45
46 // TODO: other platforms
47 #[cfg(unix)]
48 pub fn mode_changed(
49 &self,
50 filesystem_metadata: &std::fs::Metadata,
51 ) -> bool {
52 use std::os::unix::fs::MetadataExt;
53 const EXEC_BIT_MASK: u32 = 0o100;
54 let dirstate_exec_bit = (self.mode as u32) & EXEC_BIT_MASK;
55 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
56 dirstate_exec_bit != fs_exec_bit
57 }
45 }
58 }
46
59
47 #[derive(BytesCast)]
60 #[derive(BytesCast)]
48 #[repr(C)]
61 #[repr(C)]
49 struct RawEntry {
62 struct RawEntry {
50 state: u8,
63 state: u8,
51 mode: unaligned::I32Be,
64 mode: unaligned::I32Be,
52 size: unaligned::I32Be,
65 size: unaligned::I32Be,
53 mtime: unaligned::I32Be,
66 mtime: unaligned::I32Be,
54 length: unaligned::I32Be,
67 length: unaligned::I32Be,
55 }
68 }
56
69
57 const MTIME_UNSET: i32 = -1;
70 const MTIME_UNSET: i32 = -1;
58
71
59 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
72 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
60 /// other parent. This allows revert to pick the right status back during a
73 /// other parent. This allows revert to pick the right status back during a
61 /// merge.
74 /// merge.
62 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
75 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
63
76
64 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
77 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
65 pub type StateMapIter<'a> =
78 pub type StateMapIter<'a> =
66 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a DirstateEntry)> + Send + 'a>;
79 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a DirstateEntry)> + Send + 'a>;
67
80
68 pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
81 pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
69 pub type CopyMapIter<'a> =
82 pub type CopyMapIter<'a> =
70 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a HgPathBuf)> + Send + 'a>;
83 Box<dyn Iterator<Item = (&'a HgPathBuf, &'a HgPathBuf)> + Send + 'a>;
71
84
72 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
85 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
73 pub enum EntryState {
86 pub enum EntryState {
74 Normal,
87 Normal,
75 Added,
88 Added,
76 Removed,
89 Removed,
77 Merged,
90 Merged,
78 Unknown,
91 Unknown,
79 }
92 }
80
93
81 impl EntryState {
94 impl EntryState {
82 pub fn is_tracked(self) -> bool {
95 pub fn is_tracked(self) -> bool {
83 use EntryState::*;
96 use EntryState::*;
84 match self {
97 match self {
85 Normal | Added | Merged => true,
98 Normal | Added | Merged => true,
86 Removed | Unknown => false,
99 Removed | Unknown => false,
87 }
100 }
88 }
101 }
89 }
102 }
90
103
91 impl TryFrom<u8> for EntryState {
104 impl TryFrom<u8> for EntryState {
92 type Error = HgError;
105 type Error = HgError;
93
106
94 fn try_from(value: u8) -> Result<Self, Self::Error> {
107 fn try_from(value: u8) -> Result<Self, Self::Error> {
95 match value {
108 match value {
96 b'n' => Ok(EntryState::Normal),
109 b'n' => Ok(EntryState::Normal),
97 b'a' => Ok(EntryState::Added),
110 b'a' => Ok(EntryState::Added),
98 b'r' => Ok(EntryState::Removed),
111 b'r' => Ok(EntryState::Removed),
99 b'm' => Ok(EntryState::Merged),
112 b'm' => Ok(EntryState::Merged),
100 b'?' => Ok(EntryState::Unknown),
113 b'?' => Ok(EntryState::Unknown),
101 _ => Err(HgError::CorruptedRepository(format!(
114 _ => Err(HgError::CorruptedRepository(format!(
102 "Incorrect dirstate entry state {}",
115 "Incorrect dirstate entry state {}",
103 value
116 value
104 ))),
117 ))),
105 }
118 }
106 }
119 }
107 }
120 }
108
121
109 impl Into<u8> for EntryState {
122 impl Into<u8> for EntryState {
110 fn into(self) -> u8 {
123 fn into(self) -> u8 {
111 match self {
124 match self {
112 EntryState::Normal => b'n',
125 EntryState::Normal => b'n',
113 EntryState::Added => b'a',
126 EntryState::Added => b'a',
114 EntryState::Removed => b'r',
127 EntryState::Removed => b'r',
115 EntryState::Merged => b'm',
128 EntryState::Merged => b'm',
116 EntryState::Unknown => b'?',
129 EntryState::Unknown => b'?',
117 }
130 }
118 }
131 }
119 }
132 }
@@ -1,934 +1,934 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::utils::path_auditor::PathAuditor;
12 use crate::utils::path_auditor::PathAuditor;
13 use crate::{
13 use crate::{
14 dirstate::SIZE_FROM_OTHER_PARENT,
14 dirstate::SIZE_FROM_OTHER_PARENT,
15 filepatterns::PatternFileWarning,
15 filepatterns::PatternFileWarning,
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
17 utils::{
17 utils::{
18 files::{find_dirs, HgMetadata},
18 files::{find_dirs, HgMetadata},
19 hg_path::{
19 hg_path::{
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
21 HgPathError,
21 HgPathError,
22 },
22 },
23 },
23 },
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
25 PatternError,
25 PatternError,
26 };
26 };
27 use lazy_static::lazy_static;
27 use lazy_static::lazy_static;
28 use micro_timer::timed;
28 use micro_timer::timed;
29 use rayon::prelude::*;
29 use rayon::prelude::*;
30 use std::{
30 use std::{
31 borrow::Cow,
31 borrow::Cow,
32 collections::HashSet,
32 collections::HashSet,
33 fmt,
33 fmt,
34 fs::{read_dir, DirEntry},
34 fs::{read_dir, DirEntry},
35 io::ErrorKind,
35 io::ErrorKind,
36 ops::Deref,
36 ops::Deref,
37 path::{Path, PathBuf},
37 path::{Path, PathBuf},
38 };
38 };
39
39
40 /// Wrong type of file from a `BadMatch`
40 /// Wrong type of file from a `BadMatch`
41 /// Note: a lot of those don't exist on all platforms.
41 /// Note: a lot of those don't exist on all platforms.
42 #[derive(Debug, Copy, Clone)]
42 #[derive(Debug, Copy, Clone)]
43 pub enum BadType {
43 pub enum BadType {
44 CharacterDevice,
44 CharacterDevice,
45 BlockDevice,
45 BlockDevice,
46 FIFO,
46 FIFO,
47 Socket,
47 Socket,
48 Directory,
48 Directory,
49 Unknown,
49 Unknown,
50 }
50 }
51
51
52 impl fmt::Display for BadType {
52 impl fmt::Display for BadType {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 f.write_str(match self {
54 f.write_str(match self {
55 BadType::CharacterDevice => "character device",
55 BadType::CharacterDevice => "character device",
56 BadType::BlockDevice => "block device",
56 BadType::BlockDevice => "block device",
57 BadType::FIFO => "fifo",
57 BadType::FIFO => "fifo",
58 BadType::Socket => "socket",
58 BadType::Socket => "socket",
59 BadType::Directory => "directory",
59 BadType::Directory => "directory",
60 BadType::Unknown => "unknown",
60 BadType::Unknown => "unknown",
61 })
61 })
62 }
62 }
63 }
63 }
64
64
65 /// Was explicitly matched but cannot be found/accessed
65 /// Was explicitly matched but cannot be found/accessed
66 #[derive(Debug, Copy, Clone)]
66 #[derive(Debug, Copy, Clone)]
67 pub enum BadMatch {
67 pub enum BadMatch {
68 OsError(i32),
68 OsError(i32),
69 BadType(BadType),
69 BadType(BadType),
70 }
70 }
71
71
72 /// Enum used to dispatch new status entries into the right collections.
72 /// Enum used to dispatch new status entries into the right collections.
73 /// Is similar to `crate::EntryState`, but represents the transient state of
73 /// Is similar to `crate::EntryState`, but represents the transient state of
74 /// entries during the lifetime of a command.
74 /// entries during the lifetime of a command.
75 #[derive(Debug, Copy, Clone)]
75 #[derive(Debug, Copy, Clone)]
76 pub enum Dispatch {
76 pub enum Dispatch {
77 Unsure,
77 Unsure,
78 Modified,
78 Modified,
79 Added,
79 Added,
80 Removed,
80 Removed,
81 Deleted,
81 Deleted,
82 Clean,
82 Clean,
83 Unknown,
83 Unknown,
84 Ignored,
84 Ignored,
85 /// Empty dispatch, the file is not worth listing
85 /// Empty dispatch, the file is not worth listing
86 None,
86 None,
87 /// Was explicitly matched but cannot be found/accessed
87 /// Was explicitly matched but cannot be found/accessed
88 Bad(BadMatch),
88 Bad(BadMatch),
89 Directory {
89 Directory {
90 /// True if the directory used to be a file in the dmap so we can say
90 /// True if the directory used to be a file in the dmap so we can say
91 /// that it's been removed.
91 /// that it's been removed.
92 was_file: bool,
92 was_file: bool,
93 },
93 },
94 }
94 }
95
95
96 type IoResult<T> = std::io::Result<T>;
96 type IoResult<T> = std::io::Result<T>;
97
97
98 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
98 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
99 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
99 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
100 pub type IgnoreFnType<'a> =
100 pub type IgnoreFnType<'a> =
101 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
101 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
102
102
103 /// We have a good mix of owned (from directory traversal) and borrowed (from
103 /// We have a good mix of owned (from directory traversal) and borrowed (from
104 /// the dirstate/explicit) paths, this comes up a lot.
104 /// the dirstate/explicit) paths, this comes up a lot.
105 pub type HgPathCow<'a> = Cow<'a, HgPath>;
105 pub type HgPathCow<'a> = Cow<'a, HgPath>;
106
106
107 /// A path with its computed ``Dispatch`` information
107 /// A path with its computed ``Dispatch`` information
108 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
108 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
109
109
110 /// The conversion from `HgPath` to a real fs path failed.
110 /// The conversion from `HgPath` to a real fs path failed.
111 /// `22` is the error code for "Invalid argument"
111 /// `22` is the error code for "Invalid argument"
112 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
112 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
113
113
114 /// Dates and times that are outside the 31-bit signed range are compared
114 /// Dates and times that are outside the 31-bit signed range are compared
115 /// modulo 2^31. This should prevent hg from behaving badly with very large
115 /// modulo 2^31. This should prevent hg from behaving badly with very large
116 /// files or corrupt dates while still having a high probability of detecting
116 /// files or corrupt dates while still having a high probability of detecting
117 /// changes. (issue2608)
117 /// changes. (issue2608)
118 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
118 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
119 /// is not defined for `i32`, and there is no `As` trait. This forces the
119 /// is not defined for `i32`, and there is no `As` trait. This forces the
120 /// caller to cast `b` as `i32`.
120 /// caller to cast `b` as `i32`.
121 fn mod_compare(a: i32, b: i32) -> bool {
121 fn mod_compare(a: i32, b: i32) -> bool {
122 a & i32::max_value() != b & i32::max_value()
122 a & i32::max_value() != b & i32::max_value()
123 }
123 }
124
124
125 /// Return a sorted list containing information about the entries
125 /// Return a sorted list containing information about the entries
126 /// in the directory.
126 /// in the directory.
127 ///
127 ///
128 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
128 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
129 fn list_directory(
129 fn list_directory(
130 path: impl AsRef<Path>,
130 path: impl AsRef<Path>,
131 skip_dot_hg: bool,
131 skip_dot_hg: bool,
132 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
132 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
133 let mut results = vec![];
133 let mut results = vec![];
134 let entries = read_dir(path.as_ref())?;
134 let entries = read_dir(path.as_ref())?;
135
135
136 for entry in entries {
136 for entry in entries {
137 let entry = entry?;
137 let entry = entry?;
138 let filename = os_string_to_hg_path_buf(entry.file_name())?;
138 let filename = os_string_to_hg_path_buf(entry.file_name())?;
139 let file_type = entry.file_type()?;
139 let file_type = entry.file_type()?;
140 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
140 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
141 return Ok(vec![]);
141 return Ok(vec![]);
142 } else {
142 } else {
143 results.push((filename, entry))
143 results.push((filename, entry))
144 }
144 }
145 }
145 }
146
146
147 results.sort_unstable_by_key(|e| e.0.clone());
147 results.sort_unstable_by_key(|e| e.0.clone());
148 Ok(results)
148 Ok(results)
149 }
149 }
150
150
151 /// The file corresponding to the dirstate entry was found on the filesystem.
151 /// The file corresponding to the dirstate entry was found on the filesystem.
152 fn dispatch_found(
152 fn dispatch_found(
153 filename: impl AsRef<HgPath>,
153 filename: impl AsRef<HgPath>,
154 entry: DirstateEntry,
154 entry: DirstateEntry,
155 metadata: HgMetadata,
155 metadata: HgMetadata,
156 copy_map: &CopyMap,
156 copy_map: &CopyMap,
157 options: StatusOptions,
157 options: StatusOptions,
158 ) -> Dispatch {
158 ) -> Dispatch {
159 let DirstateEntry {
159 let DirstateEntry {
160 state,
160 state,
161 mode,
161 mode,
162 mtime,
162 mtime,
163 size,
163 size,
164 } = entry;
164 } = entry;
165
165
166 let HgMetadata {
166 let HgMetadata {
167 st_mode,
167 st_mode,
168 st_size,
168 st_size,
169 st_mtime,
169 st_mtime,
170 ..
170 ..
171 } = metadata;
171 } = metadata;
172
172
173 match state {
173 match state {
174 EntryState::Normal => {
174 EntryState::Normal => {
175 let size_changed = mod_compare(size, st_size as i32);
175 let size_changed = mod_compare(size, st_size as i32);
176 let mode_changed =
176 let mode_changed =
177 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
177 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
178 let metadata_changed = size >= 0 && (size_changed || mode_changed);
178 let metadata_changed = size >= 0 && (size_changed || mode_changed);
179 let other_parent = size == SIZE_FROM_OTHER_PARENT;
179 let other_parent = size == SIZE_FROM_OTHER_PARENT;
180
180
181 if metadata_changed
181 if metadata_changed
182 || other_parent
182 || other_parent
183 || copy_map.contains_key(filename.as_ref())
183 || copy_map.contains_key(filename.as_ref())
184 {
184 {
185 if metadata.is_symlink() && size_changed {
185 if metadata.is_symlink() && size_changed {
186 // issue6456: Size returned may be longer due to encryption
186 // issue6456: Size returned may be longer due to encryption
187 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
187 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
188 Dispatch::Unsure
188 Dispatch::Unsure
189 } else {
189 } else {
190 Dispatch::Modified
190 Dispatch::Modified
191 }
191 }
192 } else if mod_compare(mtime, st_mtime as i32)
192 } else if mod_compare(mtime, st_mtime as i32)
193 || st_mtime == options.last_normal_time
193 || st_mtime == options.last_normal_time
194 {
194 {
195 // the file may have just been marked as normal and
195 // the file may have just been marked as normal and
196 // it may have changed in the same second without
196 // it may have changed in the same second without
197 // changing its size. This can happen if we quickly
197 // changing its size. This can happen if we quickly
198 // do multiple commits. Force lookup, so we don't
198 // do multiple commits. Force lookup, so we don't
199 // miss such a racy file change.
199 // miss such a racy file change.
200 Dispatch::Unsure
200 Dispatch::Unsure
201 } else if options.list_clean {
201 } else if options.list_clean {
202 Dispatch::Clean
202 Dispatch::Clean
203 } else {
203 } else {
204 Dispatch::None
204 Dispatch::None
205 }
205 }
206 }
206 }
207 EntryState::Merged => Dispatch::Modified,
207 EntryState::Merged => Dispatch::Modified,
208 EntryState::Added => Dispatch::Added,
208 EntryState::Added => Dispatch::Added,
209 EntryState::Removed => Dispatch::Removed,
209 EntryState::Removed => Dispatch::Removed,
210 EntryState::Unknown => Dispatch::Unknown,
210 EntryState::Unknown => Dispatch::Unknown,
211 }
211 }
212 }
212 }
213
213
214 /// The file corresponding to this Dirstate entry is missing.
214 /// The file corresponding to this Dirstate entry is missing.
215 fn dispatch_missing(state: EntryState) -> Dispatch {
215 fn dispatch_missing(state: EntryState) -> Dispatch {
216 match state {
216 match state {
217 // File was removed from the filesystem during commands
217 // File was removed from the filesystem during commands
218 EntryState::Normal | EntryState::Merged | EntryState::Added => {
218 EntryState::Normal | EntryState::Merged | EntryState::Added => {
219 Dispatch::Deleted
219 Dispatch::Deleted
220 }
220 }
221 // File was removed, everything is normal
221 // File was removed, everything is normal
222 EntryState::Removed => Dispatch::Removed,
222 EntryState::Removed => Dispatch::Removed,
223 // File is unknown to Mercurial, everything is normal
223 // File is unknown to Mercurial, everything is normal
224 EntryState::Unknown => Dispatch::Unknown,
224 EntryState::Unknown => Dispatch::Unknown,
225 }
225 }
226 }
226 }
227
227
228 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
228 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
229 Dispatch::Bad(BadMatch::OsError(
229 Dispatch::Bad(BadMatch::OsError(
230 e.raw_os_error().expect("expected real OS error"),
230 e.raw_os_error().expect("expected real OS error"),
231 ))
231 ))
232 }
232 }
233
233
234 lazy_static! {
234 lazy_static! {
235 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
235 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
236 let mut h = HashSet::new();
236 let mut h = HashSet::new();
237 h.insert(HgPath::new(b""));
237 h.insert(HgPath::new(b""));
238 h
238 h
239 };
239 };
240 }
240 }
241
241
242 #[derive(Debug, Copy, Clone)]
242 #[derive(Debug, Copy, Clone)]
243 pub struct StatusOptions {
243 pub struct StatusOptions {
244 /// Remember the most recent modification timeslot for status, to make
244 /// Remember the most recent modification timeslot for status, to make
245 /// sure we won't miss future size-preserving file content modifications
245 /// sure we won't miss future size-preserving file content modifications
246 /// that happen within the same timeslot.
246 /// that happen within the same timeslot.
247 pub last_normal_time: i64,
247 pub last_normal_time: i64,
248 /// Whether we are on a filesystem with UNIX-like exec flags
248 /// Whether we are on a filesystem with UNIX-like exec flags
249 pub check_exec: bool,
249 pub check_exec: bool,
250 pub list_clean: bool,
250 pub list_clean: bool,
251 pub list_unknown: bool,
251 pub list_unknown: bool,
252 pub list_ignored: bool,
252 pub list_ignored: bool,
253 /// Whether to collect traversed dirs for applying a callback later.
253 /// Whether to collect traversed dirs for applying a callback later.
254 /// Used by `hg purge` for example.
254 /// Used by `hg purge` for example.
255 pub collect_traversed_dirs: bool,
255 pub collect_traversed_dirs: bool,
256 }
256 }
257
257
258 #[derive(Debug)]
258 #[derive(Debug, Default)]
259 pub struct DirstateStatus<'a> {
259 pub struct DirstateStatus<'a> {
260 /// Tracked files whose contents have changed since the parent revision
260 /// Tracked files whose contents have changed since the parent revision
261 pub modified: Vec<HgPathCow<'a>>,
261 pub modified: Vec<HgPathCow<'a>>,
262
262
263 /// Newly-tracked files that were not present in the parent
263 /// Newly-tracked files that were not present in the parent
264 pub added: Vec<HgPathCow<'a>>,
264 pub added: Vec<HgPathCow<'a>>,
265
265
266 /// Previously-tracked files that have been (re)moved with an hg command
266 /// Previously-tracked files that have been (re)moved with an hg command
267 pub removed: Vec<HgPathCow<'a>>,
267 pub removed: Vec<HgPathCow<'a>>,
268
268
269 /// (Still) tracked files that are missing, (re)moved with an non-hg
269 /// (Still) tracked files that are missing, (re)moved with an non-hg
270 /// command
270 /// command
271 pub deleted: Vec<HgPathCow<'a>>,
271 pub deleted: Vec<HgPathCow<'a>>,
272
272
273 /// Tracked files that are up to date with the parent.
273 /// Tracked files that are up to date with the parent.
274 /// Only pupulated if `StatusOptions::list_clean` is true.
274 /// Only pupulated if `StatusOptions::list_clean` is true.
275 pub clean: Vec<HgPathCow<'a>>,
275 pub clean: Vec<HgPathCow<'a>>,
276
276
277 /// Files in the working directory that are ignored with `.hgignore`.
277 /// Files in the working directory that are ignored with `.hgignore`.
278 /// Only pupulated if `StatusOptions::list_ignored` is true.
278 /// Only pupulated if `StatusOptions::list_ignored` is true.
279 pub ignored: Vec<HgPathCow<'a>>,
279 pub ignored: Vec<HgPathCow<'a>>,
280
280
281 /// Files in the working directory that are neither tracked nor ignored.
281 /// Files in the working directory that are neither tracked nor ignored.
282 /// Only pupulated if `StatusOptions::list_unknown` is true.
282 /// Only pupulated if `StatusOptions::list_unknown` is true.
283 pub unknown: Vec<HgPathCow<'a>>,
283 pub unknown: Vec<HgPathCow<'a>>,
284
284
285 /// Was explicitly matched but cannot be found/accessed
285 /// Was explicitly matched but cannot be found/accessed
286 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
286 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
287
287
288 /// Either clean or modified, but we can’t tell from filesystem metadata
288 /// Either clean or modified, but we can’t tell from filesystem metadata
289 /// alone. The file contents need to be read and compared with that in
289 /// alone. The file contents need to be read and compared with that in
290 /// the parent.
290 /// the parent.
291 pub unsure: Vec<HgPathCow<'a>>,
291 pub unsure: Vec<HgPathCow<'a>>,
292
292
293 /// Only filled if `collect_traversed_dirs` is `true`
293 /// Only filled if `collect_traversed_dirs` is `true`
294 pub traversed: Vec<HgPathBuf>,
294 pub traversed: Vec<HgPathBuf>,
295 }
295 }
296
296
297 #[derive(Debug, derive_more::From)]
297 #[derive(Debug, derive_more::From)]
298 pub enum StatusError {
298 pub enum StatusError {
299 /// Generic IO error
299 /// Generic IO error
300 IO(std::io::Error),
300 IO(std::io::Error),
301 /// An invalid path that cannot be represented in Mercurial was found
301 /// An invalid path that cannot be represented in Mercurial was found
302 Path(HgPathError),
302 Path(HgPathError),
303 /// An invalid "ignore" pattern was found
303 /// An invalid "ignore" pattern was found
304 Pattern(PatternError),
304 Pattern(PatternError),
305 }
305 }
306
306
307 pub type StatusResult<T> = Result<T, StatusError>;
307 pub type StatusResult<T> = Result<T, StatusError>;
308
308
309 impl fmt::Display for StatusError {
309 impl fmt::Display for StatusError {
310 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311 match self {
311 match self {
312 StatusError::IO(error) => error.fmt(f),
312 StatusError::IO(error) => error.fmt(f),
313 StatusError::Path(error) => error.fmt(f),
313 StatusError::Path(error) => error.fmt(f),
314 StatusError::Pattern(error) => error.fmt(f),
314 StatusError::Pattern(error) => error.fmt(f),
315 }
315 }
316 }
316 }
317 }
317 }
318
318
319 /// Gives information about which files are changed in the working directory
319 /// Gives information about which files are changed in the working directory
320 /// and how, compared to the revision we're based on
320 /// and how, compared to the revision we're based on
321 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
321 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
322 dmap: &'a DirstateMap,
322 dmap: &'a DirstateMap,
323 pub(crate) matcher: &'a M,
323 pub(crate) matcher: &'a M,
324 root_dir: PathBuf,
324 root_dir: PathBuf,
325 pub(crate) options: StatusOptions,
325 pub(crate) options: StatusOptions,
326 ignore_fn: IgnoreFnType<'a>,
326 ignore_fn: IgnoreFnType<'a>,
327 }
327 }
328
328
329 impl<'a, M> Status<'a, M>
329 impl<'a, M> Status<'a, M>
330 where
330 where
331 M: ?Sized + Matcher + Sync,
331 M: ?Sized + Matcher + Sync,
332 {
332 {
333 pub fn new(
333 pub fn new(
334 dmap: &'a DirstateMap,
334 dmap: &'a DirstateMap,
335 matcher: &'a M,
335 matcher: &'a M,
336 root_dir: PathBuf,
336 root_dir: PathBuf,
337 ignore_files: Vec<PathBuf>,
337 ignore_files: Vec<PathBuf>,
338 options: StatusOptions,
338 options: StatusOptions,
339 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
339 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
340 // Needs to outlive `dir_ignore_fn` since it's captured.
340 // Needs to outlive `dir_ignore_fn` since it's captured.
341
341
342 let (ignore_fn, warnings): (IgnoreFnType, _) =
342 let (ignore_fn, warnings): (IgnoreFnType, _) =
343 if options.list_ignored || options.list_unknown {
343 if options.list_ignored || options.list_unknown {
344 get_ignore_function(ignore_files, &root_dir)?
344 get_ignore_function(ignore_files, &root_dir)?
345 } else {
345 } else {
346 (Box::new(|&_| true), vec![])
346 (Box::new(|&_| true), vec![])
347 };
347 };
348
348
349 Ok((
349 Ok((
350 Self {
350 Self {
351 dmap,
351 dmap,
352 matcher,
352 matcher,
353 root_dir,
353 root_dir,
354 options,
354 options,
355 ignore_fn,
355 ignore_fn,
356 },
356 },
357 warnings,
357 warnings,
358 ))
358 ))
359 }
359 }
360
360
361 /// Is the path ignored?
361 /// Is the path ignored?
362 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
362 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
363 (self.ignore_fn)(path.as_ref())
363 (self.ignore_fn)(path.as_ref())
364 }
364 }
365
365
366 /// Is the path or one of its ancestors ignored?
366 /// Is the path or one of its ancestors ignored?
367 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
367 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
368 // Only involve ignore mechanism if we're listing unknowns or ignored.
368 // Only involve ignore mechanism if we're listing unknowns or ignored.
369 if self.options.list_ignored || self.options.list_unknown {
369 if self.options.list_ignored || self.options.list_unknown {
370 if self.is_ignored(&dir) {
370 if self.is_ignored(&dir) {
371 true
371 true
372 } else {
372 } else {
373 for p in find_dirs(dir.as_ref()) {
373 for p in find_dirs(dir.as_ref()) {
374 if self.is_ignored(p) {
374 if self.is_ignored(p) {
375 return true;
375 return true;
376 }
376 }
377 }
377 }
378 false
378 false
379 }
379 }
380 } else {
380 } else {
381 true
381 true
382 }
382 }
383 }
383 }
384
384
385 /// Get stat data about the files explicitly specified by the matcher.
385 /// Get stat data about the files explicitly specified by the matcher.
386 /// Returns a tuple of the directories that need to be traversed and the
386 /// Returns a tuple of the directories that need to be traversed and the
387 /// files with their corresponding `Dispatch`.
387 /// files with their corresponding `Dispatch`.
388 /// TODO subrepos
388 /// TODO subrepos
389 #[timed]
389 #[timed]
390 pub fn walk_explicit(
390 pub fn walk_explicit(
391 &self,
391 &self,
392 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
392 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
393 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
393 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
394 self.matcher
394 self.matcher
395 .file_set()
395 .file_set()
396 .unwrap_or(&DEFAULT_WORK)
396 .unwrap_or(&DEFAULT_WORK)
397 .par_iter()
397 .par_iter()
398 .flat_map(|&filename| -> Option<_> {
398 .flat_map(|&filename| -> Option<_> {
399 // TODO normalization
399 // TODO normalization
400 let normalized = filename;
400 let normalized = filename;
401
401
402 let buf = match hg_path_to_path_buf(normalized) {
402 let buf = match hg_path_to_path_buf(normalized) {
403 Ok(x) => x,
403 Ok(x) => x,
404 Err(_) => {
404 Err(_) => {
405 return Some((
405 return Some((
406 Cow::Borrowed(normalized),
406 Cow::Borrowed(normalized),
407 INVALID_PATH_DISPATCH,
407 INVALID_PATH_DISPATCH,
408 ))
408 ))
409 }
409 }
410 };
410 };
411 let target = self.root_dir.join(buf);
411 let target = self.root_dir.join(buf);
412 let st = target.symlink_metadata();
412 let st = target.symlink_metadata();
413 let in_dmap = self.dmap.get(normalized);
413 let in_dmap = self.dmap.get(normalized);
414 match st {
414 match st {
415 Ok(meta) => {
415 Ok(meta) => {
416 let file_type = meta.file_type();
416 let file_type = meta.file_type();
417 return if file_type.is_file() || file_type.is_symlink()
417 return if file_type.is_file() || file_type.is_symlink()
418 {
418 {
419 if let Some(entry) = in_dmap {
419 if let Some(entry) = in_dmap {
420 return Some((
420 return Some((
421 Cow::Borrowed(normalized),
421 Cow::Borrowed(normalized),
422 dispatch_found(
422 dispatch_found(
423 &normalized,
423 &normalized,
424 *entry,
424 *entry,
425 HgMetadata::from_metadata(meta),
425 HgMetadata::from_metadata(meta),
426 &self.dmap.copy_map,
426 &self.dmap.copy_map,
427 self.options,
427 self.options,
428 ),
428 ),
429 ));
429 ));
430 }
430 }
431 Some((
431 Some((
432 Cow::Borrowed(normalized),
432 Cow::Borrowed(normalized),
433 Dispatch::Unknown,
433 Dispatch::Unknown,
434 ))
434 ))
435 } else if file_type.is_dir() {
435 } else if file_type.is_dir() {
436 if self.options.collect_traversed_dirs {
436 if self.options.collect_traversed_dirs {
437 traversed_sender
437 traversed_sender
438 .send(normalized.to_owned())
438 .send(normalized.to_owned())
439 .expect("receiver should outlive sender");
439 .expect("receiver should outlive sender");
440 }
440 }
441 Some((
441 Some((
442 Cow::Borrowed(normalized),
442 Cow::Borrowed(normalized),
443 Dispatch::Directory {
443 Dispatch::Directory {
444 was_file: in_dmap.is_some(),
444 was_file: in_dmap.is_some(),
445 },
445 },
446 ))
446 ))
447 } else {
447 } else {
448 Some((
448 Some((
449 Cow::Borrowed(normalized),
449 Cow::Borrowed(normalized),
450 Dispatch::Bad(BadMatch::BadType(
450 Dispatch::Bad(BadMatch::BadType(
451 // TODO do more than unknown
451 // TODO do more than unknown
452 // Support for all `BadType` variant
452 // Support for all `BadType` variant
453 // varies greatly between platforms.
453 // varies greatly between platforms.
454 // So far, no tests check the type and
454 // So far, no tests check the type and
455 // this should be good enough for most
455 // this should be good enough for most
456 // users.
456 // users.
457 BadType::Unknown,
457 BadType::Unknown,
458 )),
458 )),
459 ))
459 ))
460 };
460 };
461 }
461 }
462 Err(_) => {
462 Err(_) => {
463 if let Some(entry) = in_dmap {
463 if let Some(entry) = in_dmap {
464 return Some((
464 return Some((
465 Cow::Borrowed(normalized),
465 Cow::Borrowed(normalized),
466 dispatch_missing(entry.state),
466 dispatch_missing(entry.state),
467 ));
467 ));
468 }
468 }
469 }
469 }
470 };
470 };
471 None
471 None
472 })
472 })
473 .partition(|(_, dispatch)| match dispatch {
473 .partition(|(_, dispatch)| match dispatch {
474 Dispatch::Directory { .. } => true,
474 Dispatch::Directory { .. } => true,
475 _ => false,
475 _ => false,
476 })
476 })
477 }
477 }
478
478
479 /// Walk the working directory recursively to look for changes compared to
479 /// Walk the working directory recursively to look for changes compared to
480 /// the current `DirstateMap`.
480 /// the current `DirstateMap`.
481 ///
481 ///
482 /// This takes a mutable reference to the results to account for the
482 /// This takes a mutable reference to the results to account for the
483 /// `extend` in timings
483 /// `extend` in timings
484 #[timed]
484 #[timed]
485 pub fn traverse(
485 pub fn traverse(
486 &self,
486 &self,
487 path: impl AsRef<HgPath>,
487 path: impl AsRef<HgPath>,
488 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
488 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
489 results: &mut Vec<DispatchedPath<'a>>,
489 results: &mut Vec<DispatchedPath<'a>>,
490 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
490 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
491 ) {
491 ) {
492 // The traversal is done in parallel, so use a channel to gather
492 // The traversal is done in parallel, so use a channel to gather
493 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
493 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
494 // is not.
494 // is not.
495 let (files_transmitter, files_receiver) =
495 let (files_transmitter, files_receiver) =
496 crossbeam_channel::unbounded();
496 crossbeam_channel::unbounded();
497
497
498 self.traverse_dir(
498 self.traverse_dir(
499 &files_transmitter,
499 &files_transmitter,
500 path,
500 path,
501 &old_results,
501 &old_results,
502 traversed_sender,
502 traversed_sender,
503 );
503 );
504
504
505 // Disconnect the channel so the receiver stops waiting
505 // Disconnect the channel so the receiver stops waiting
506 drop(files_transmitter);
506 drop(files_transmitter);
507
507
508 let new_results = files_receiver
508 let new_results = files_receiver
509 .into_iter()
509 .into_iter()
510 .par_bridge()
510 .par_bridge()
511 .map(|(f, d)| (Cow::Owned(f), d));
511 .map(|(f, d)| (Cow::Owned(f), d));
512
512
513 results.par_extend(new_results);
513 results.par_extend(new_results);
514 }
514 }
515
515
516 /// Dispatch a single entry (file, folder, symlink...) found during
516 /// Dispatch a single entry (file, folder, symlink...) found during
517 /// `traverse`. If the entry is a folder that needs to be traversed, it
517 /// `traverse`. If the entry is a folder that needs to be traversed, it
518 /// will be handled in a separate thread.
518 /// will be handled in a separate thread.
519 fn handle_traversed_entry<'b>(
519 fn handle_traversed_entry<'b>(
520 &'a self,
520 &'a self,
521 scope: &rayon::Scope<'b>,
521 scope: &rayon::Scope<'b>,
522 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
522 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
523 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
523 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
524 filename: HgPathBuf,
524 filename: HgPathBuf,
525 dir_entry: DirEntry,
525 dir_entry: DirEntry,
526 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
526 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
527 ) -> IoResult<()>
527 ) -> IoResult<()>
528 where
528 where
529 'a: 'b,
529 'a: 'b,
530 {
530 {
531 let file_type = dir_entry.file_type()?;
531 let file_type = dir_entry.file_type()?;
532 let entry_option = self.dmap.get(&filename);
532 let entry_option = self.dmap.get(&filename);
533
533
534 if filename.as_bytes() == b".hg" {
534 if filename.as_bytes() == b".hg" {
535 // Could be a directory or a symlink
535 // Could be a directory or a symlink
536 return Ok(());
536 return Ok(());
537 }
537 }
538
538
539 if file_type.is_dir() {
539 if file_type.is_dir() {
540 self.handle_traversed_dir(
540 self.handle_traversed_dir(
541 scope,
541 scope,
542 files_sender,
542 files_sender,
543 old_results,
543 old_results,
544 entry_option,
544 entry_option,
545 filename,
545 filename,
546 traversed_sender,
546 traversed_sender,
547 );
547 );
548 } else if file_type.is_file() || file_type.is_symlink() {
548 } else if file_type.is_file() || file_type.is_symlink() {
549 if let Some(entry) = entry_option {
549 if let Some(entry) = entry_option {
550 if self.matcher.matches_everything()
550 if self.matcher.matches_everything()
551 || self.matcher.matches(&filename)
551 || self.matcher.matches(&filename)
552 {
552 {
553 let metadata = dir_entry.metadata()?;
553 let metadata = dir_entry.metadata()?;
554 files_sender
554 files_sender
555 .send((
555 .send((
556 filename.to_owned(),
556 filename.to_owned(),
557 dispatch_found(
557 dispatch_found(
558 &filename,
558 &filename,
559 *entry,
559 *entry,
560 HgMetadata::from_metadata(metadata),
560 HgMetadata::from_metadata(metadata),
561 &self.dmap.copy_map,
561 &self.dmap.copy_map,
562 self.options,
562 self.options,
563 ),
563 ),
564 ))
564 ))
565 .unwrap();
565 .unwrap();
566 }
566 }
567 } else if (self.matcher.matches_everything()
567 } else if (self.matcher.matches_everything()
568 || self.matcher.matches(&filename))
568 || self.matcher.matches(&filename))
569 && !self.is_ignored(&filename)
569 && !self.is_ignored(&filename)
570 {
570 {
571 if (self.options.list_ignored
571 if (self.options.list_ignored
572 || self.matcher.exact_match(&filename))
572 || self.matcher.exact_match(&filename))
573 && self.dir_ignore(&filename)
573 && self.dir_ignore(&filename)
574 {
574 {
575 if self.options.list_ignored {
575 if self.options.list_ignored {
576 files_sender
576 files_sender
577 .send((filename.to_owned(), Dispatch::Ignored))
577 .send((filename.to_owned(), Dispatch::Ignored))
578 .unwrap();
578 .unwrap();
579 }
579 }
580 } else if self.options.list_unknown {
580 } else if self.options.list_unknown {
581 files_sender
581 files_sender
582 .send((filename.to_owned(), Dispatch::Unknown))
582 .send((filename.to_owned(), Dispatch::Unknown))
583 .unwrap();
583 .unwrap();
584 }
584 }
585 } else if self.is_ignored(&filename) && self.options.list_ignored {
585 } else if self.is_ignored(&filename) && self.options.list_ignored {
586 files_sender
586 files_sender
587 .send((filename.to_owned(), Dispatch::Ignored))
587 .send((filename.to_owned(), Dispatch::Ignored))
588 .unwrap();
588 .unwrap();
589 }
589 }
590 } else if let Some(entry) = entry_option {
590 } else if let Some(entry) = entry_option {
591 // Used to be a file or a folder, now something else.
591 // Used to be a file or a folder, now something else.
592 if self.matcher.matches_everything()
592 if self.matcher.matches_everything()
593 || self.matcher.matches(&filename)
593 || self.matcher.matches(&filename)
594 {
594 {
595 files_sender
595 files_sender
596 .send((filename.to_owned(), dispatch_missing(entry.state)))
596 .send((filename.to_owned(), dispatch_missing(entry.state)))
597 .unwrap();
597 .unwrap();
598 }
598 }
599 }
599 }
600
600
601 Ok(())
601 Ok(())
602 }
602 }
603
603
604 /// A directory was found in the filesystem and needs to be traversed
604 /// A directory was found in the filesystem and needs to be traversed
605 fn handle_traversed_dir<'b>(
605 fn handle_traversed_dir<'b>(
606 &'a self,
606 &'a self,
607 scope: &rayon::Scope<'b>,
607 scope: &rayon::Scope<'b>,
608 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
608 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
609 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
609 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
610 entry_option: Option<&'a DirstateEntry>,
610 entry_option: Option<&'a DirstateEntry>,
611 directory: HgPathBuf,
611 directory: HgPathBuf,
612 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
612 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
613 ) where
613 ) where
614 'a: 'b,
614 'a: 'b,
615 {
615 {
616 scope.spawn(move |_| {
616 scope.spawn(move |_| {
617 // Nested `if` until `rust-lang/rust#53668` is stable
617 // Nested `if` until `rust-lang/rust#53668` is stable
618 if let Some(entry) = entry_option {
618 if let Some(entry) = entry_option {
619 // Used to be a file, is now a folder
619 // Used to be a file, is now a folder
620 if self.matcher.matches_everything()
620 if self.matcher.matches_everything()
621 || self.matcher.matches(&directory)
621 || self.matcher.matches(&directory)
622 {
622 {
623 files_sender
623 files_sender
624 .send((
624 .send((
625 directory.to_owned(),
625 directory.to_owned(),
626 dispatch_missing(entry.state),
626 dispatch_missing(entry.state),
627 ))
627 ))
628 .unwrap();
628 .unwrap();
629 }
629 }
630 }
630 }
631 // Do we need to traverse it?
631 // Do we need to traverse it?
632 if !self.is_ignored(&directory) || self.options.list_ignored {
632 if !self.is_ignored(&directory) || self.options.list_ignored {
633 self.traverse_dir(
633 self.traverse_dir(
634 files_sender,
634 files_sender,
635 directory,
635 directory,
636 &old_results,
636 &old_results,
637 traversed_sender,
637 traversed_sender,
638 )
638 )
639 }
639 }
640 });
640 });
641 }
641 }
642
642
643 /// Decides whether the directory needs to be listed, and if so handles the
643 /// Decides whether the directory needs to be listed, and if so handles the
644 /// entries in a separate thread.
644 /// entries in a separate thread.
645 fn traverse_dir(
645 fn traverse_dir(
646 &self,
646 &self,
647 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
647 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
648 directory: impl AsRef<HgPath>,
648 directory: impl AsRef<HgPath>,
649 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
649 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
650 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
650 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
651 ) {
651 ) {
652 let directory = directory.as_ref();
652 let directory = directory.as_ref();
653
653
654 if self.options.collect_traversed_dirs {
654 if self.options.collect_traversed_dirs {
655 traversed_sender
655 traversed_sender
656 .send(directory.to_owned())
656 .send(directory.to_owned())
657 .expect("receiver should outlive sender");
657 .expect("receiver should outlive sender");
658 }
658 }
659
659
660 let visit_entries = match self.matcher.visit_children_set(directory) {
660 let visit_entries = match self.matcher.visit_children_set(directory) {
661 VisitChildrenSet::Empty => return,
661 VisitChildrenSet::Empty => return,
662 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
662 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
663 VisitChildrenSet::Set(set) => Some(set),
663 VisitChildrenSet::Set(set) => Some(set),
664 };
664 };
665 let buf = match hg_path_to_path_buf(directory) {
665 let buf = match hg_path_to_path_buf(directory) {
666 Ok(b) => b,
666 Ok(b) => b,
667 Err(_) => {
667 Err(_) => {
668 files_sender
668 files_sender
669 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
669 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
670 .expect("receiver should outlive sender");
670 .expect("receiver should outlive sender");
671 return;
671 return;
672 }
672 }
673 };
673 };
674 let dir_path = self.root_dir.join(buf);
674 let dir_path = self.root_dir.join(buf);
675
675
676 let skip_dot_hg = !directory.as_bytes().is_empty();
676 let skip_dot_hg = !directory.as_bytes().is_empty();
677 let entries = match list_directory(dir_path, skip_dot_hg) {
677 let entries = match list_directory(dir_path, skip_dot_hg) {
678 Err(e) => {
678 Err(e) => {
679 files_sender
679 files_sender
680 .send((directory.to_owned(), dispatch_os_error(&e)))
680 .send((directory.to_owned(), dispatch_os_error(&e)))
681 .expect("receiver should outlive sender");
681 .expect("receiver should outlive sender");
682 return;
682 return;
683 }
683 }
684 Ok(entries) => entries,
684 Ok(entries) => entries,
685 };
685 };
686
686
687 rayon::scope(|scope| {
687 rayon::scope(|scope| {
688 for (filename, dir_entry) in entries {
688 for (filename, dir_entry) in entries {
689 if let Some(ref set) = visit_entries {
689 if let Some(ref set) = visit_entries {
690 if !set.contains(filename.deref()) {
690 if !set.contains(filename.deref()) {
691 continue;
691 continue;
692 }
692 }
693 }
693 }
694 // TODO normalize
694 // TODO normalize
695 let filename = if directory.is_empty() {
695 let filename = if directory.is_empty() {
696 filename.to_owned()
696 filename.to_owned()
697 } else {
697 } else {
698 directory.join(&filename)
698 directory.join(&filename)
699 };
699 };
700
700
701 if !old_results.contains_key(filename.deref()) {
701 if !old_results.contains_key(filename.deref()) {
702 match self.handle_traversed_entry(
702 match self.handle_traversed_entry(
703 scope,
703 scope,
704 files_sender,
704 files_sender,
705 old_results,
705 old_results,
706 filename,
706 filename,
707 dir_entry,
707 dir_entry,
708 traversed_sender.clone(),
708 traversed_sender.clone(),
709 ) {
709 ) {
710 Err(e) => {
710 Err(e) => {
711 files_sender
711 files_sender
712 .send((
712 .send((
713 directory.to_owned(),
713 directory.to_owned(),
714 dispatch_os_error(&e),
714 dispatch_os_error(&e),
715 ))
715 ))
716 .expect("receiver should outlive sender");
716 .expect("receiver should outlive sender");
717 }
717 }
718 Ok(_) => {}
718 Ok(_) => {}
719 }
719 }
720 }
720 }
721 }
721 }
722 })
722 })
723 }
723 }
724
724
725 /// Add the files in the dirstate to the results.
725 /// Add the files in the dirstate to the results.
726 ///
726 ///
727 /// This takes a mutable reference to the results to account for the
727 /// This takes a mutable reference to the results to account for the
728 /// `extend` in timings
728 /// `extend` in timings
729 #[timed]
729 #[timed]
730 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
730 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
731 results.par_extend(
731 results.par_extend(
732 self.dmap
732 self.dmap
733 .par_iter()
733 .par_iter()
734 .filter(|(path, _)| self.matcher.matches(path))
734 .filter(|(path, _)| self.matcher.matches(path))
735 .map(move |(filename, entry)| {
735 .map(move |(filename, entry)| {
736 let filename: &HgPath = filename;
736 let filename: &HgPath = filename;
737 let filename_as_path = match hg_path_to_path_buf(filename)
737 let filename_as_path = match hg_path_to_path_buf(filename)
738 {
738 {
739 Ok(f) => f,
739 Ok(f) => f,
740 Err(_) => {
740 Err(_) => {
741 return (
741 return (
742 Cow::Borrowed(filename),
742 Cow::Borrowed(filename),
743 INVALID_PATH_DISPATCH,
743 INVALID_PATH_DISPATCH,
744 )
744 )
745 }
745 }
746 };
746 };
747 let meta = self
747 let meta = self
748 .root_dir
748 .root_dir
749 .join(filename_as_path)
749 .join(filename_as_path)
750 .symlink_metadata();
750 .symlink_metadata();
751 match meta {
751 match meta {
752 Ok(m)
752 Ok(m)
753 if !(m.file_type().is_file()
753 if !(m.file_type().is_file()
754 || m.file_type().is_symlink()) =>
754 || m.file_type().is_symlink()) =>
755 {
755 {
756 (
756 (
757 Cow::Borrowed(filename),
757 Cow::Borrowed(filename),
758 dispatch_missing(entry.state),
758 dispatch_missing(entry.state),
759 )
759 )
760 }
760 }
761 Ok(m) => (
761 Ok(m) => (
762 Cow::Borrowed(filename),
762 Cow::Borrowed(filename),
763 dispatch_found(
763 dispatch_found(
764 filename,
764 filename,
765 *entry,
765 *entry,
766 HgMetadata::from_metadata(m),
766 HgMetadata::from_metadata(m),
767 &self.dmap.copy_map,
767 &self.dmap.copy_map,
768 self.options,
768 self.options,
769 ),
769 ),
770 ),
770 ),
771 Err(e)
771 Err(e)
772 if e.kind() == ErrorKind::NotFound
772 if e.kind() == ErrorKind::NotFound
773 || e.raw_os_error() == Some(20) =>
773 || e.raw_os_error() == Some(20) =>
774 {
774 {
775 // Rust does not yet have an `ErrorKind` for
775 // Rust does not yet have an `ErrorKind` for
776 // `NotADirectory` (errno 20)
776 // `NotADirectory` (errno 20)
777 // It happens if the dirstate contains `foo/bar`
777 // It happens if the dirstate contains `foo/bar`
778 // and foo is not a
778 // and foo is not a
779 // directory
779 // directory
780 (
780 (
781 Cow::Borrowed(filename),
781 Cow::Borrowed(filename),
782 dispatch_missing(entry.state),
782 dispatch_missing(entry.state),
783 )
783 )
784 }
784 }
785 Err(e) => {
785 Err(e) => {
786 (Cow::Borrowed(filename), dispatch_os_error(&e))
786 (Cow::Borrowed(filename), dispatch_os_error(&e))
787 }
787 }
788 }
788 }
789 }),
789 }),
790 );
790 );
791 }
791 }
792
792
793 /// Checks all files that are in the dirstate but were not found during the
793 /// Checks all files that are in the dirstate but were not found during the
794 /// working directory traversal. This means that the rest must
794 /// working directory traversal. This means that the rest must
795 /// be either ignored, under a symlink or under a new nested repo.
795 /// be either ignored, under a symlink or under a new nested repo.
796 ///
796 ///
797 /// This takes a mutable reference to the results to account for the
797 /// This takes a mutable reference to the results to account for the
798 /// `extend` in timings
798 /// `extend` in timings
799 #[timed]
799 #[timed]
800 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
800 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
801 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
801 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
802 if results.is_empty() && self.matcher.matches_everything() {
802 if results.is_empty() && self.matcher.matches_everything() {
803 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
803 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
804 } else {
804 } else {
805 // Only convert to a hashmap if needed.
805 // Only convert to a hashmap if needed.
806 let old_results: FastHashMap<_, _> =
806 let old_results: FastHashMap<_, _> =
807 results.iter().cloned().collect();
807 results.iter().cloned().collect();
808 self.dmap
808 self.dmap
809 .iter()
809 .iter()
810 .filter_map(move |(f, e)| {
810 .filter_map(move |(f, e)| {
811 if !old_results.contains_key(f.deref())
811 if !old_results.contains_key(f.deref())
812 && self.matcher.matches(f)
812 && self.matcher.matches(f)
813 {
813 {
814 Some((f.deref(), e))
814 Some((f.deref(), e))
815 } else {
815 } else {
816 None
816 None
817 }
817 }
818 })
818 })
819 .collect()
819 .collect()
820 };
820 };
821
821
822 let path_auditor = PathAuditor::new(&self.root_dir);
822 let path_auditor = PathAuditor::new(&self.root_dir);
823
823
824 let new_results = to_visit.into_par_iter().filter_map(
824 let new_results = to_visit.into_par_iter().filter_map(
825 |(filename, entry)| -> Option<_> {
825 |(filename, entry)| -> Option<_> {
826 // Report ignored items in the dmap as long as they are not
826 // Report ignored items in the dmap as long as they are not
827 // under a symlink directory.
827 // under a symlink directory.
828 if path_auditor.check(filename) {
828 if path_auditor.check(filename) {
829 // TODO normalize for case-insensitive filesystems
829 // TODO normalize for case-insensitive filesystems
830 let buf = match hg_path_to_path_buf(filename) {
830 let buf = match hg_path_to_path_buf(filename) {
831 Ok(x) => x,
831 Ok(x) => x,
832 Err(_) => {
832 Err(_) => {
833 return Some((
833 return Some((
834 Cow::Owned(filename.to_owned()),
834 Cow::Owned(filename.to_owned()),
835 INVALID_PATH_DISPATCH,
835 INVALID_PATH_DISPATCH,
836 ));
836 ));
837 }
837 }
838 };
838 };
839 Some((
839 Some((
840 Cow::Owned(filename.to_owned()),
840 Cow::Owned(filename.to_owned()),
841 match self.root_dir.join(&buf).symlink_metadata() {
841 match self.root_dir.join(&buf).symlink_metadata() {
842 // File was just ignored, no links, and exists
842 // File was just ignored, no links, and exists
843 Ok(meta) => {
843 Ok(meta) => {
844 let metadata = HgMetadata::from_metadata(meta);
844 let metadata = HgMetadata::from_metadata(meta);
845 dispatch_found(
845 dispatch_found(
846 filename,
846 filename,
847 *entry,
847 *entry,
848 metadata,
848 metadata,
849 &self.dmap.copy_map,
849 &self.dmap.copy_map,
850 self.options,
850 self.options,
851 )
851 )
852 }
852 }
853 // File doesn't exist
853 // File doesn't exist
854 Err(_) => dispatch_missing(entry.state),
854 Err(_) => dispatch_missing(entry.state),
855 },
855 },
856 ))
856 ))
857 } else {
857 } else {
858 // It's either missing or under a symlink directory which
858 // It's either missing or under a symlink directory which
859 // we, in this case, report as missing.
859 // we, in this case, report as missing.
860 Some((
860 Some((
861 Cow::Owned(filename.to_owned()),
861 Cow::Owned(filename.to_owned()),
862 dispatch_missing(entry.state),
862 dispatch_missing(entry.state),
863 ))
863 ))
864 }
864 }
865 },
865 },
866 );
866 );
867
867
868 results.par_extend(new_results);
868 results.par_extend(new_results);
869 }
869 }
870 }
870 }
871
871
872 #[timed]
872 #[timed]
873 pub fn build_response<'a>(
873 pub fn build_response<'a>(
874 results: impl IntoIterator<Item = DispatchedPath<'a>>,
874 results: impl IntoIterator<Item = DispatchedPath<'a>>,
875 traversed: Vec<HgPathBuf>,
875 traversed: Vec<HgPathBuf>,
876 ) -> DirstateStatus<'a> {
876 ) -> DirstateStatus<'a> {
877 let mut unsure = vec![];
877 let mut unsure = vec![];
878 let mut modified = vec![];
878 let mut modified = vec![];
879 let mut added = vec![];
879 let mut added = vec![];
880 let mut removed = vec![];
880 let mut removed = vec![];
881 let mut deleted = vec![];
881 let mut deleted = vec![];
882 let mut clean = vec![];
882 let mut clean = vec![];
883 let mut ignored = vec![];
883 let mut ignored = vec![];
884 let mut unknown = vec![];
884 let mut unknown = vec![];
885 let mut bad = vec![];
885 let mut bad = vec![];
886
886
887 for (filename, dispatch) in results.into_iter() {
887 for (filename, dispatch) in results.into_iter() {
888 match dispatch {
888 match dispatch {
889 Dispatch::Unknown => unknown.push(filename),
889 Dispatch::Unknown => unknown.push(filename),
890 Dispatch::Unsure => unsure.push(filename),
890 Dispatch::Unsure => unsure.push(filename),
891 Dispatch::Modified => modified.push(filename),
891 Dispatch::Modified => modified.push(filename),
892 Dispatch::Added => added.push(filename),
892 Dispatch::Added => added.push(filename),
893 Dispatch::Removed => removed.push(filename),
893 Dispatch::Removed => removed.push(filename),
894 Dispatch::Deleted => deleted.push(filename),
894 Dispatch::Deleted => deleted.push(filename),
895 Dispatch::Clean => clean.push(filename),
895 Dispatch::Clean => clean.push(filename),
896 Dispatch::Ignored => ignored.push(filename),
896 Dispatch::Ignored => ignored.push(filename),
897 Dispatch::None => {}
897 Dispatch::None => {}
898 Dispatch::Bad(reason) => bad.push((filename, reason)),
898 Dispatch::Bad(reason) => bad.push((filename, reason)),
899 Dispatch::Directory { .. } => {}
899 Dispatch::Directory { .. } => {}
900 }
900 }
901 }
901 }
902
902
903 DirstateStatus {
903 DirstateStatus {
904 modified,
904 modified,
905 added,
905 added,
906 removed,
906 removed,
907 deleted,
907 deleted,
908 clean,
908 clean,
909 ignored,
909 ignored,
910 unknown,
910 unknown,
911 bad,
911 bad,
912 unsure,
912 unsure,
913 traversed,
913 traversed,
914 }
914 }
915 }
915 }
916
916
917 /// Get the status of files in the working directory.
917 /// Get the status of files in the working directory.
918 ///
918 ///
919 /// This is the current entry-point for `hg-core` and is realistically unusable
919 /// This is the current entry-point for `hg-core` and is realistically unusable
920 /// outside of a Python context because its arguments need to provide a lot of
920 /// outside of a Python context because its arguments need to provide a lot of
921 /// information that will not be necessary in the future.
921 /// information that will not be necessary in the future.
922 #[timed]
922 #[timed]
923 pub fn status<'a>(
923 pub fn status<'a>(
924 dmap: &'a DirstateMap,
924 dmap: &'a DirstateMap,
925 matcher: &'a (dyn Matcher + Sync),
925 matcher: &'a (dyn Matcher + Sync),
926 root_dir: PathBuf,
926 root_dir: PathBuf,
927 ignore_files: Vec<PathBuf>,
927 ignore_files: Vec<PathBuf>,
928 options: StatusOptions,
928 options: StatusOptions,
929 ) -> StatusResult<(DirstateStatus<'a>, Vec<PatternFileWarning>)> {
929 ) -> StatusResult<(DirstateStatus<'a>, Vec<PatternFileWarning>)> {
930 let (status, warnings) =
930 let (status, warnings) =
931 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
931 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
932
932
933 Ok((status.run()?, warnings))
933 Ok((status.run()?, warnings))
934 }
934 }
@@ -1,652 +1,656 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use std::path::PathBuf;
2 use std::path::PathBuf;
3 use std::{collections::BTreeMap, convert::TryInto};
3 use std::{collections::BTreeMap, convert::TryInto};
4
4
5 use super::path_with_basename::WithBasename;
5 use super::path_with_basename::WithBasename;
6 use crate::dirstate::parsers::clear_ambiguous_mtime;
6 use crate::dirstate::parsers::clear_ambiguous_mtime;
7 use crate::dirstate::parsers::pack_entry;
7 use crate::dirstate::parsers::pack_entry;
8 use crate::dirstate::parsers::packed_entry_size;
8 use crate::dirstate::parsers::packed_entry_size;
9 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate::parsers::parse_dirstate_entries;
10 use crate::dirstate::parsers::parse_dirstate_parents;
10 use crate::dirstate::parsers::parse_dirstate_parents;
11 use crate::dirstate::parsers::Timestamp;
11 use crate::dirstate::parsers::Timestamp;
12 use crate::matchers::Matcher;
12 use crate::matchers::Matcher;
13 use crate::revlog::node::NULL_NODE;
13 use crate::revlog::node::NULL_NODE;
14 use crate::utils::hg_path::{HgPath, HgPathBuf};
14 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 use crate::CopyMapIter;
15 use crate::CopyMapIter;
16 use crate::DirstateEntry;
16 use crate::DirstateEntry;
17 use crate::DirstateError;
17 use crate::DirstateError;
18 use crate::DirstateMapError;
18 use crate::DirstateMapError;
19 use crate::DirstateParents;
19 use crate::DirstateParents;
20 use crate::DirstateStatus;
20 use crate::DirstateStatus;
21 use crate::EntryState;
21 use crate::EntryState;
22 use crate::PatternFileWarning;
22 use crate::PatternFileWarning;
23 use crate::StateMapIter;
23 use crate::StateMapIter;
24 use crate::StatusError;
24 use crate::StatusError;
25 use crate::StatusOptions;
25 use crate::StatusOptions;
26
26
27 pub struct DirstateMap {
27 pub struct DirstateMap {
28 parents: Option<DirstateParents>,
28 parents: Option<DirstateParents>,
29 dirty_parents: bool,
29 dirty_parents: bool,
30 root: ChildNodes,
30 pub(super) root: ChildNodes,
31
31
32 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
32 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
33 nodes_with_entry_count: usize,
33 nodes_with_entry_count: usize,
34
34
35 /// Number of nodes anywhere in the tree that have
35 /// Number of nodes anywhere in the tree that have
36 /// `.copy_source.is_some()`.
36 /// `.copy_source.is_some()`.
37 nodes_with_copy_source_count: usize,
37 nodes_with_copy_source_count: usize,
38 }
38 }
39
39
40 /// Using a plain `HgPathBuf` of the full path from the repository root as a
40 /// Using a plain `HgPathBuf` of the full path from the repository root as a
41 /// map key would also work: all paths in a given map have the same parent
41 /// map key would also work: all paths in a given map have the same parent
42 /// path, so comparing full paths gives the same result as comparing base
42 /// path, so comparing full paths gives the same result as comparing base
43 /// names. However `BTreeMap` would waste time always re-comparing the same
43 /// names. However `BTreeMap` would waste time always re-comparing the same
44 /// string prefix.
44 /// string prefix.
45 type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
45 pub(super) type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
46
46
47 /// Represents a file or a directory
47 /// Represents a file or a directory
48 #[derive(Default)]
48 #[derive(Default)]
49 struct Node {
49 pub(super) struct Node {
50 /// `None` for directories
50 /// `None` for directories
51 entry: Option<DirstateEntry>,
51 pub(super) entry: Option<DirstateEntry>,
52
52
53 copy_source: Option<HgPathBuf>,
53 pub(super) copy_source: Option<HgPathBuf>,
54
54
55 children: ChildNodes,
55 pub(super) children: ChildNodes,
56
56
57 /// How many (non-inclusive) descendants of this node are tracked files
57 /// How many (non-inclusive) descendants of this node are tracked files
58 tracked_descendants_count: usize,
58 tracked_descendants_count: usize,
59 }
59 }
60
60
61 impl Node {
61 impl Node {
62 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
62 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
63 fn is_tracked_file(&self) -> bool {
63 fn is_tracked_file(&self) -> bool {
64 if let Some(entry) = &self.entry {
64 if let Some(entry) = &self.entry {
65 entry.state.is_tracked()
65 entry.state.is_tracked()
66 } else {
66 } else {
67 false
67 false
68 }
68 }
69 }
69 }
70
71 pub(super) fn state(&self) -> Option<EntryState> {
72 self.entry.as_ref().map(|entry| entry.state)
73 }
70 }
74 }
71
75
72 /// `(full_path, entry, copy_source)`
76 /// `(full_path, entry, copy_source)`
73 type NodeDataMut<'a> = (
77 type NodeDataMut<'a> = (
74 &'a WithBasename<HgPathBuf>,
78 &'a WithBasename<HgPathBuf>,
75 &'a mut Option<DirstateEntry>,
79 &'a mut Option<DirstateEntry>,
76 &'a mut Option<HgPathBuf>,
80 &'a mut Option<HgPathBuf>,
77 );
81 );
78
82
79 impl DirstateMap {
83 impl DirstateMap {
80 pub fn new() -> Self {
84 pub fn new() -> Self {
81 Self {
85 Self {
82 parents: None,
86 parents: None,
83 dirty_parents: false,
87 dirty_parents: false,
84 root: ChildNodes::new(),
88 root: ChildNodes::new(),
85 nodes_with_entry_count: 0,
89 nodes_with_entry_count: 0,
86 nodes_with_copy_source_count: 0,
90 nodes_with_copy_source_count: 0,
87 }
91 }
88 }
92 }
89
93
90 fn get_node(&self, path: &HgPath) -> Option<&Node> {
94 fn get_node(&self, path: &HgPath) -> Option<&Node> {
91 let mut children = &self.root;
95 let mut children = &self.root;
92 let mut components = path.components();
96 let mut components = path.components();
93 let mut component =
97 let mut component =
94 components.next().expect("expected at least one components");
98 components.next().expect("expected at least one components");
95 loop {
99 loop {
96 let child = children.get(component)?;
100 let child = children.get(component)?;
97 if let Some(next_component) = components.next() {
101 if let Some(next_component) = components.next() {
98 component = next_component;
102 component = next_component;
99 children = &child.children;
103 children = &child.children;
100 } else {
104 } else {
101 return Some(child);
105 return Some(child);
102 }
106 }
103 }
107 }
104 }
108 }
105
109
106 /// Returns a mutable reference to the node at `path` if it exists
110 /// Returns a mutable reference to the node at `path` if it exists
107 ///
111 ///
108 /// This takes `root` instead of `&mut self` so that callers can mutate
112 /// This takes `root` instead of `&mut self` so that callers can mutate
109 /// other fields while the returned borrow is still valid
113 /// other fields while the returned borrow is still valid
110 fn get_node_mut<'tree>(
114 fn get_node_mut<'tree>(
111 root: &'tree mut ChildNodes,
115 root: &'tree mut ChildNodes,
112 path: &HgPath,
116 path: &HgPath,
113 ) -> Option<&'tree mut Node> {
117 ) -> Option<&'tree mut Node> {
114 Self::each_and_get(root, path, |_| {})
118 Self::each_and_get(root, path, |_| {})
115 }
119 }
116
120
117 /// Call `each` for each ancestor node of the one at `path` (not including
121 /// Call `each` for each ancestor node of the one at `path` (not including
118 /// that node itself), starting from nearest the root.
122 /// that node itself), starting from nearest the root.
119 ///
123 ///
120 /// Panics (possibly after some calls to `each`) if there is no node at
124 /// Panics (possibly after some calls to `each`) if there is no node at
121 /// `path`.
125 /// `path`.
122 fn for_each_ancestor_node<'tree>(
126 fn for_each_ancestor_node<'tree>(
123 &mut self,
127 &mut self,
124 path: &HgPath,
128 path: &HgPath,
125 each: impl FnMut(&mut Node),
129 each: impl FnMut(&mut Node),
126 ) {
130 ) {
127 let parent = path.parent();
131 let parent = path.parent();
128 if !parent.is_empty() {
132 if !parent.is_empty() {
129 Self::each_and_get(&mut self.root, parent, each)
133 Self::each_and_get(&mut self.root, parent, each)
130 .expect("missing dirstate node");
134 .expect("missing dirstate node");
131 }
135 }
132 }
136 }
133
137
134 /// Common implementation detail of `get_node_mut` and
138 /// Common implementation detail of `get_node_mut` and
135 /// `for_each_ancestor_node`
139 /// `for_each_ancestor_node`
136 fn each_and_get<'tree>(
140 fn each_and_get<'tree>(
137 root: &'tree mut ChildNodes,
141 root: &'tree mut ChildNodes,
138 path: &HgPath,
142 path: &HgPath,
139 mut each: impl FnMut(&mut Node),
143 mut each: impl FnMut(&mut Node),
140 ) -> Option<&'tree mut Node> {
144 ) -> Option<&'tree mut Node> {
141 let mut children = root;
145 let mut children = root;
142 let mut components = path.components();
146 let mut components = path.components();
143 let mut component =
147 let mut component =
144 components.next().expect("expected at least one components");
148 components.next().expect("expected at least one components");
145 loop {
149 loop {
146 let child = children.get_mut(component)?;
150 let child = children.get_mut(component)?;
147 each(child);
151 each(child);
148 if let Some(next_component) = components.next() {
152 if let Some(next_component) = components.next() {
149 component = next_component;
153 component = next_component;
150 children = &mut child.children;
154 children = &mut child.children;
151 } else {
155 } else {
152 return Some(child);
156 return Some(child);
153 }
157 }
154 }
158 }
155 }
159 }
156
160
157 fn get_or_insert_node<'tree>(
161 fn get_or_insert_node<'tree>(
158 root: &'tree mut ChildNodes,
162 root: &'tree mut ChildNodes,
159 path: &HgPath,
163 path: &HgPath,
160 ) -> &'tree mut Node {
164 ) -> &'tree mut Node {
161 let mut child_nodes = root;
165 let mut child_nodes = root;
162 let mut inclusive_ancestor_paths =
166 let mut inclusive_ancestor_paths =
163 WithBasename::inclusive_ancestors_of(path);
167 WithBasename::inclusive_ancestors_of(path);
164 let mut ancestor_path = inclusive_ancestor_paths
168 let mut ancestor_path = inclusive_ancestor_paths
165 .next()
169 .next()
166 .expect("expected at least one inclusive ancestor");
170 .expect("expected at least one inclusive ancestor");
167 loop {
171 loop {
168 // TODO: can we avoid double lookup in all cases without allocating
172 // TODO: can we avoid double lookup in all cases without allocating
169 // an owned key in cases where the map already contains that key?
173 // an owned key in cases where the map already contains that key?
170 let child_node =
174 let child_node =
171 if child_nodes.contains_key(ancestor_path.base_name()) {
175 if child_nodes.contains_key(ancestor_path.base_name()) {
172 child_nodes.get_mut(ancestor_path.base_name()).unwrap()
176 child_nodes.get_mut(ancestor_path.base_name()).unwrap()
173 } else {
177 } else {
174 // This is always a vacant entry, using `.entry()` lets us
178 // This is always a vacant entry, using `.entry()` lets us
175 // return a `&mut Node` of the newly-inserted node without
179 // return a `&mut Node` of the newly-inserted node without
176 // yet another lookup. `BTreeMap::insert` doesn’t do this.
180 // yet another lookup. `BTreeMap::insert` doesn’t do this.
177 child_nodes.entry(ancestor_path.to_owned()).or_default()
181 child_nodes.entry(ancestor_path.to_owned()).or_default()
178 };
182 };
179 if let Some(next) = inclusive_ancestor_paths.next() {
183 if let Some(next) = inclusive_ancestor_paths.next() {
180 ancestor_path = next;
184 ancestor_path = next;
181 child_nodes = &mut child_node.children;
185 child_nodes = &mut child_node.children;
182 } else {
186 } else {
183 return child_node;
187 return child_node;
184 }
188 }
185 }
189 }
186 }
190 }
187
191
188 /// The meaning of `new_copy_source` is:
192 /// The meaning of `new_copy_source` is:
189 ///
193 ///
190 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
194 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
191 /// * `Some(None)`: set `Node::copy_source` to `None`
195 /// * `Some(None)`: set `Node::copy_source` to `None`
192 /// * `None`: leave `Node::copy_source` unchanged
196 /// * `None`: leave `Node::copy_source` unchanged
193 fn add_file_node(
197 fn add_file_node(
194 &mut self,
198 &mut self,
195 path: &HgPath,
199 path: &HgPath,
196 new_entry: DirstateEntry,
200 new_entry: DirstateEntry,
197 new_copy_source: Option<Option<HgPathBuf>>,
201 new_copy_source: Option<Option<HgPathBuf>>,
198 ) {
202 ) {
199 let node = Self::get_or_insert_node(&mut self.root, path);
203 let node = Self::get_or_insert_node(&mut self.root, path);
200 if node.entry.is_none() {
204 if node.entry.is_none() {
201 self.nodes_with_entry_count += 1
205 self.nodes_with_entry_count += 1
202 }
206 }
203 if let Some(source) = &new_copy_source {
207 if let Some(source) = &new_copy_source {
204 if node.copy_source.is_none() && source.is_some() {
208 if node.copy_source.is_none() && source.is_some() {
205 self.nodes_with_copy_source_count += 1
209 self.nodes_with_copy_source_count += 1
206 }
210 }
207 if node.copy_source.is_some() && source.is_none() {
211 if node.copy_source.is_some() && source.is_none() {
208 self.nodes_with_copy_source_count -= 1
212 self.nodes_with_copy_source_count -= 1
209 }
213 }
210 }
214 }
211 let tracked_count_increment =
215 let tracked_count_increment =
212 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
216 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
213 (false, true) => 1,
217 (false, true) => 1,
214 (true, false) => -1,
218 (true, false) => -1,
215 _ => 0,
219 _ => 0,
216 };
220 };
217
221
218 node.entry = Some(new_entry);
222 node.entry = Some(new_entry);
219 if let Some(source) = new_copy_source {
223 if let Some(source) = new_copy_source {
220 node.copy_source = source
224 node.copy_source = source
221 }
225 }
222 // Borrow of `self.root` through `node` ends here
226 // Borrow of `self.root` through `node` ends here
223
227
224 match tracked_count_increment {
228 match tracked_count_increment {
225 1 => self.for_each_ancestor_node(path, |node| {
229 1 => self.for_each_ancestor_node(path, |node| {
226 node.tracked_descendants_count += 1
230 node.tracked_descendants_count += 1
227 }),
231 }),
228 // We can’t use `+= -1` because the counter is unsigned
232 // We can’t use `+= -1` because the counter is unsigned
229 -1 => self.for_each_ancestor_node(path, |node| {
233 -1 => self.for_each_ancestor_node(path, |node| {
230 node.tracked_descendants_count -= 1
234 node.tracked_descendants_count -= 1
231 }),
235 }),
232 _ => {}
236 _ => {}
233 }
237 }
234 }
238 }
235
239
236 fn iter_nodes<'a>(
240 fn iter_nodes<'a>(
237 &'a self,
241 &'a self,
238 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
242 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
239 {
243 {
240 // Depth first tree traversal.
244 // Depth first tree traversal.
241 //
245 //
242 // If we could afford internal iteration and recursion,
246 // If we could afford internal iteration and recursion,
243 // this would look like:
247 // this would look like:
244 //
248 //
245 // ```
249 // ```
246 // fn traverse_children(
250 // fn traverse_children(
247 // children: &ChildNodes,
251 // children: &ChildNodes,
248 // each: &mut impl FnMut(&Node),
252 // each: &mut impl FnMut(&Node),
249 // ) {
253 // ) {
250 // for child in children.values() {
254 // for child in children.values() {
251 // traverse_children(&child.children, each);
255 // traverse_children(&child.children, each);
252 // each(child);
256 // each(child);
253 // }
257 // }
254 // }
258 // }
255 // ```
259 // ```
256 //
260 //
257 // However we want an external iterator and therefore can’t use the
261 // However we want an external iterator and therefore can’t use the
258 // call stack. Use an explicit stack instead:
262 // call stack. Use an explicit stack instead:
259 let mut stack = Vec::new();
263 let mut stack = Vec::new();
260 let mut iter = self.root.iter();
264 let mut iter = self.root.iter();
261 std::iter::from_fn(move || {
265 std::iter::from_fn(move || {
262 while let Some((key, child_node)) = iter.next() {
266 while let Some((key, child_node)) = iter.next() {
263 // Pseudo-recursion
267 // Pseudo-recursion
264 let new_iter = child_node.children.iter();
268 let new_iter = child_node.children.iter();
265 let old_iter = std::mem::replace(&mut iter, new_iter);
269 let old_iter = std::mem::replace(&mut iter, new_iter);
266 stack.push((key, child_node, old_iter));
270 stack.push((key, child_node, old_iter));
267 }
271 }
268 // Found the end of a `children.iter()` iterator.
272 // Found the end of a `children.iter()` iterator.
269 if let Some((key, child_node, next_iter)) = stack.pop() {
273 if let Some((key, child_node, next_iter)) = stack.pop() {
270 // "Return" from pseudo-recursion by restoring state from the
274 // "Return" from pseudo-recursion by restoring state from the
271 // explicit stack
275 // explicit stack
272 iter = next_iter;
276 iter = next_iter;
273
277
274 Some((key, child_node))
278 Some((key, child_node))
275 } else {
279 } else {
276 // Reached the bottom of the stack, we’re done
280 // Reached the bottom of the stack, we’re done
277 None
281 None
278 }
282 }
279 })
283 })
280 }
284 }
281
285
282 /// Mutable iterator for the `(entry, copy source)` of each node.
286 /// Mutable iterator for the `(entry, copy source)` of each node.
283 ///
287 ///
284 /// It would not be safe to yield mutable references to nodes themeselves
288 /// It would not be safe to yield mutable references to nodes themeselves
285 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
289 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
286 /// reachable from their ancestor nodes, potentially creating multiple
290 /// reachable from their ancestor nodes, potentially creating multiple
287 /// `&mut` references to a given node.
291 /// `&mut` references to a given node.
288 fn iter_node_data_mut<'a>(
292 fn iter_node_data_mut<'a>(
289 &'a mut self,
293 &'a mut self,
290 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
294 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
291 // Explict stack for pseudo-recursion, see `iter_nodes` above.
295 // Explict stack for pseudo-recursion, see `iter_nodes` above.
292 let mut stack = Vec::new();
296 let mut stack = Vec::new();
293 let mut iter = self.root.iter_mut();
297 let mut iter = self.root.iter_mut();
294 std::iter::from_fn(move || {
298 std::iter::from_fn(move || {
295 while let Some((key, child_node)) = iter.next() {
299 while let Some((key, child_node)) = iter.next() {
296 // Pseudo-recursion
300 // Pseudo-recursion
297 let data =
301 let data =
298 (key, &mut child_node.entry, &mut child_node.copy_source);
302 (key, &mut child_node.entry, &mut child_node.copy_source);
299 let new_iter = child_node.children.iter_mut();
303 let new_iter = child_node.children.iter_mut();
300 let old_iter = std::mem::replace(&mut iter, new_iter);
304 let old_iter = std::mem::replace(&mut iter, new_iter);
301 stack.push((data, old_iter));
305 stack.push((data, old_iter));
302 }
306 }
303 // Found the end of a `children.values_mut()` iterator.
307 // Found the end of a `children.values_mut()` iterator.
304 if let Some((data, next_iter)) = stack.pop() {
308 if let Some((data, next_iter)) = stack.pop() {
305 // "Return" from pseudo-recursion by restoring state from the
309 // "Return" from pseudo-recursion by restoring state from the
306 // explicit stack
310 // explicit stack
307 iter = next_iter;
311 iter = next_iter;
308
312
309 Some(data)
313 Some(data)
310 } else {
314 } else {
311 // Reached the bottom of the stack, we’re done
315 // Reached the bottom of the stack, we’re done
312 None
316 None
313 }
317 }
314 })
318 })
315 }
319 }
316 }
320 }
317
321
318 impl super::dispatch::DirstateMapMethods for DirstateMap {
322 impl super::dispatch::DirstateMapMethods for DirstateMap {
319 fn clear(&mut self) {
323 fn clear(&mut self) {
320 self.set_parents(&DirstateParents {
324 self.set_parents(&DirstateParents {
321 p1: NULL_NODE,
325 p1: NULL_NODE,
322 p2: NULL_NODE,
326 p2: NULL_NODE,
323 });
327 });
324 self.root.clear();
328 self.root.clear();
325 self.nodes_with_entry_count = 0;
329 self.nodes_with_entry_count = 0;
326 self.nodes_with_copy_source_count = 0;
330 self.nodes_with_copy_source_count = 0;
327 }
331 }
328
332
329 fn add_file(
333 fn add_file(
330 &mut self,
334 &mut self,
331 filename: &HgPath,
335 filename: &HgPath,
332 _old_state: EntryState,
336 _old_state: EntryState,
333 entry: DirstateEntry,
337 entry: DirstateEntry,
334 ) -> Result<(), DirstateMapError> {
338 ) -> Result<(), DirstateMapError> {
335 self.add_file_node(filename, entry, None);
339 self.add_file_node(filename, entry, None);
336 Ok(())
340 Ok(())
337 }
341 }
338
342
339 fn remove_file(
343 fn remove_file(
340 &mut self,
344 &mut self,
341 filename: &HgPath,
345 filename: &HgPath,
342 _old_state: EntryState,
346 _old_state: EntryState,
343 size: i32,
347 size: i32,
344 ) -> Result<(), DirstateMapError> {
348 ) -> Result<(), DirstateMapError> {
345 let entry = DirstateEntry {
349 let entry = DirstateEntry {
346 state: EntryState::Removed,
350 state: EntryState::Removed,
347 mode: 0,
351 mode: 0,
348 size,
352 size,
349 mtime: 0,
353 mtime: 0,
350 };
354 };
351 self.add_file_node(filename, entry, None);
355 self.add_file_node(filename, entry, None);
352 Ok(())
356 Ok(())
353 }
357 }
354
358
355 fn drop_file(
359 fn drop_file(
356 &mut self,
360 &mut self,
357 filename: &HgPath,
361 filename: &HgPath,
358 _old_state: EntryState,
362 _old_state: EntryState,
359 ) -> Result<bool, DirstateMapError> {
363 ) -> Result<bool, DirstateMapError> {
360 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
364 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
361 let was_tracked = node.is_tracked_file();
365 let was_tracked = node.is_tracked_file();
362 let had_entry = node.entry.is_some();
366 let had_entry = node.entry.is_some();
363 let had_copy_source = node.copy_source.is_some();
367 let had_copy_source = node.copy_source.is_some();
364
368
365 // TODO: this leaves in the tree a "non-file" node. Should we
369 // TODO: this leaves in the tree a "non-file" node. Should we
366 // remove the node instead, together with ancestor nodes for
370 // remove the node instead, together with ancestor nodes for
367 // directories that become empty?
371 // directories that become empty?
368 node.entry = None;
372 node.entry = None;
369 node.copy_source = None;
373 node.copy_source = None;
370
374
371 if had_entry {
375 if had_entry {
372 self.nodes_with_entry_count -= 1
376 self.nodes_with_entry_count -= 1
373 }
377 }
374 if had_copy_source {
378 if had_copy_source {
375 self.nodes_with_copy_source_count -= 1
379 self.nodes_with_copy_source_count -= 1
376 }
380 }
377 if was_tracked {
381 if was_tracked {
378 self.for_each_ancestor_node(filename, |node| {
382 self.for_each_ancestor_node(filename, |node| {
379 node.tracked_descendants_count -= 1
383 node.tracked_descendants_count -= 1
380 })
384 })
381 }
385 }
382 Ok(had_entry)
386 Ok(had_entry)
383 } else {
387 } else {
384 Ok(false)
388 Ok(false)
385 }
389 }
386 }
390 }
387
391
388 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
392 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
389 for filename in filenames {
393 for filename in filenames {
390 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
394 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
391 if let Some(entry) = node.entry.as_mut() {
395 if let Some(entry) = node.entry.as_mut() {
392 clear_ambiguous_mtime(entry, now);
396 clear_ambiguous_mtime(entry, now);
393 }
397 }
394 }
398 }
395 }
399 }
396 }
400 }
397
401
398 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
402 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
399 self.get_node(key)
403 self.get_node(key)
400 .and_then(|node| node.entry.as_ref())
404 .and_then(|node| node.entry.as_ref())
401 .map_or(false, DirstateEntry::is_non_normal)
405 .map_or(false, DirstateEntry::is_non_normal)
402 }
406 }
403
407
404 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
408 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
405 // Do nothing, this `DirstateMap` does not have a separate "non normal
409 // Do nothing, this `DirstateMap` does not have a separate "non normal
406 // entries" set that need to be kept up to date
410 // entries" set that need to be kept up to date
407 }
411 }
408
412
409 fn non_normal_or_other_parent_paths(
413 fn non_normal_or_other_parent_paths(
410 &mut self,
414 &mut self,
411 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
415 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
412 Box::new(self.iter_nodes().filter_map(|(path, node)| {
416 Box::new(self.iter_nodes().filter_map(|(path, node)| {
413 node.entry
417 node.entry
414 .as_ref()
418 .as_ref()
415 .filter(|entry| {
419 .filter(|entry| {
416 entry.is_non_normal() || entry.is_from_other_parent()
420 entry.is_non_normal() || entry.is_from_other_parent()
417 })
421 })
418 .map(|_| path.full_path())
422 .map(|_| path.full_path())
419 }))
423 }))
420 }
424 }
421
425
422 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
426 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
423 // Do nothing, this `DirstateMap` does not have a separate "non normal
427 // Do nothing, this `DirstateMap` does not have a separate "non normal
424 // entries" and "from other parent" sets that need to be recomputed
428 // entries" and "from other parent" sets that need to be recomputed
425 }
429 }
426
430
427 fn iter_non_normal_paths(
431 fn iter_non_normal_paths(
428 &mut self,
432 &mut self,
429 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
433 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
430 self.iter_non_normal_paths_panic()
434 self.iter_non_normal_paths_panic()
431 }
435 }
432
436
433 fn iter_non_normal_paths_panic(
437 fn iter_non_normal_paths_panic(
434 &self,
438 &self,
435 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
439 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
436 Box::new(self.iter_nodes().filter_map(|(path, node)| {
440 Box::new(self.iter_nodes().filter_map(|(path, node)| {
437 node.entry
441 node.entry
438 .as_ref()
442 .as_ref()
439 .filter(|entry| entry.is_non_normal())
443 .filter(|entry| entry.is_non_normal())
440 .map(|_| path.full_path())
444 .map(|_| path.full_path())
441 }))
445 }))
442 }
446 }
443
447
444 fn iter_other_parent_paths(
448 fn iter_other_parent_paths(
445 &mut self,
449 &mut self,
446 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
450 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
447 Box::new(self.iter_nodes().filter_map(|(path, node)| {
451 Box::new(self.iter_nodes().filter_map(|(path, node)| {
448 node.entry
452 node.entry
449 .as_ref()
453 .as_ref()
450 .filter(|entry| entry.is_from_other_parent())
454 .filter(|entry| entry.is_from_other_parent())
451 .map(|_| path.full_path())
455 .map(|_| path.full_path())
452 }))
456 }))
453 }
457 }
454
458
455 fn has_tracked_dir(
459 fn has_tracked_dir(
456 &mut self,
460 &mut self,
457 directory: &HgPath,
461 directory: &HgPath,
458 ) -> Result<bool, DirstateMapError> {
462 ) -> Result<bool, DirstateMapError> {
459 if let Some(node) = self.get_node(directory) {
463 if let Some(node) = self.get_node(directory) {
460 // A node without a `DirstateEntry` was created to hold child
464 // A node without a `DirstateEntry` was created to hold child
461 // nodes, and is therefore a directory.
465 // nodes, and is therefore a directory.
462 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
466 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
463 } else {
467 } else {
464 Ok(false)
468 Ok(false)
465 }
469 }
466 }
470 }
467
471
468 fn has_dir(
472 fn has_dir(
469 &mut self,
473 &mut self,
470 directory: &HgPath,
474 directory: &HgPath,
471 ) -> Result<bool, DirstateMapError> {
475 ) -> Result<bool, DirstateMapError> {
472 if let Some(node) = self.get_node(directory) {
476 if let Some(node) = self.get_node(directory) {
473 // A node without a `DirstateEntry` was created to hold child
477 // A node without a `DirstateEntry` was created to hold child
474 // nodes, and is therefore a directory.
478 // nodes, and is therefore a directory.
475 Ok(node.entry.is_none())
479 Ok(node.entry.is_none())
476 } else {
480 } else {
477 Ok(false)
481 Ok(false)
478 }
482 }
479 }
483 }
480
484
481 fn parents(
485 fn parents(
482 &mut self,
486 &mut self,
483 file_contents: &[u8],
487 file_contents: &[u8],
484 ) -> Result<&DirstateParents, DirstateError> {
488 ) -> Result<&DirstateParents, DirstateError> {
485 if self.parents.is_none() {
489 if self.parents.is_none() {
486 let parents = if !file_contents.is_empty() {
490 let parents = if !file_contents.is_empty() {
487 parse_dirstate_parents(file_contents)?.clone()
491 parse_dirstate_parents(file_contents)?.clone()
488 } else {
492 } else {
489 DirstateParents {
493 DirstateParents {
490 p1: NULL_NODE,
494 p1: NULL_NODE,
491 p2: NULL_NODE,
495 p2: NULL_NODE,
492 }
496 }
493 };
497 };
494 self.parents = Some(parents);
498 self.parents = Some(parents);
495 }
499 }
496 Ok(self.parents.as_ref().unwrap())
500 Ok(self.parents.as_ref().unwrap())
497 }
501 }
498
502
499 fn set_parents(&mut self, parents: &DirstateParents) {
503 fn set_parents(&mut self, parents: &DirstateParents) {
500 self.parents = Some(parents.clone());
504 self.parents = Some(parents.clone());
501 self.dirty_parents = true;
505 self.dirty_parents = true;
502 }
506 }
503
507
504 fn read<'a>(
508 fn read<'a>(
505 &mut self,
509 &mut self,
506 file_contents: &'a [u8],
510 file_contents: &'a [u8],
507 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
511 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
508 if file_contents.is_empty() {
512 if file_contents.is_empty() {
509 return Ok(None);
513 return Ok(None);
510 }
514 }
511
515
512 let parents = parse_dirstate_entries(
516 let parents = parse_dirstate_entries(
513 file_contents,
517 file_contents,
514 |path, entry, copy_source| {
518 |path, entry, copy_source| {
515 self.add_file_node(
519 self.add_file_node(
516 path,
520 path,
517 *entry,
521 *entry,
518 Some(copy_source.map(HgPath::to_owned)),
522 Some(copy_source.map(HgPath::to_owned)),
519 )
523 )
520 },
524 },
521 )?;
525 )?;
522
526
523 if !self.dirty_parents {
527 if !self.dirty_parents {
524 self.set_parents(parents);
528 self.set_parents(parents);
525 }
529 }
526
530
527 Ok(Some(parents))
531 Ok(Some(parents))
528 }
532 }
529
533
530 fn pack(
534 fn pack(
531 &mut self,
535 &mut self,
532 parents: DirstateParents,
536 parents: DirstateParents,
533 now: Timestamp,
537 now: Timestamp,
534 ) -> Result<Vec<u8>, DirstateError> {
538 ) -> Result<Vec<u8>, DirstateError> {
535 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
539 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
536 // reallocations
540 // reallocations
537 let mut size = parents.as_bytes().len();
541 let mut size = parents.as_bytes().len();
538 for (path, node) in self.iter_nodes() {
542 for (path, node) in self.iter_nodes() {
539 if node.entry.is_some() {
543 if node.entry.is_some() {
540 size += packed_entry_size(
544 size += packed_entry_size(
541 path.full_path(),
545 path.full_path(),
542 node.copy_source.as_ref(),
546 node.copy_source.as_ref(),
543 )
547 )
544 }
548 }
545 }
549 }
546
550
547 let mut packed = Vec::with_capacity(size);
551 let mut packed = Vec::with_capacity(size);
548 packed.extend(parents.as_bytes());
552 packed.extend(parents.as_bytes());
549
553
550 let now: i32 = now.0.try_into().expect("time overflow");
554 let now: i32 = now.0.try_into().expect("time overflow");
551 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
555 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
552 if let Some(entry) = opt_entry {
556 if let Some(entry) = opt_entry {
553 clear_ambiguous_mtime(entry, now);
557 clear_ambiguous_mtime(entry, now);
554 pack_entry(
558 pack_entry(
555 path.full_path(),
559 path.full_path(),
556 entry,
560 entry,
557 copy_source.as_ref(),
561 copy_source.as_ref(),
558 &mut packed,
562 &mut packed,
559 );
563 );
560 }
564 }
561 }
565 }
562 self.dirty_parents = false;
566 self.dirty_parents = false;
563 Ok(packed)
567 Ok(packed)
564 }
568 }
565
569
566 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
570 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
567 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
571 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
568 // needs to be recomputed
572 // needs to be recomputed
569 Ok(())
573 Ok(())
570 }
574 }
571
575
572 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
576 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
573 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
577 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
574 // to be recomputed
578 // to be recomputed
575 Ok(())
579 Ok(())
576 }
580 }
577
581
578 fn status<'a>(
582 fn status<'a>(
579 &'a mut self,
583 &'a mut self,
580 matcher: &'a (dyn Matcher + Sync),
584 matcher: &'a (dyn Matcher + Sync),
581 root_dir: PathBuf,
585 root_dir: PathBuf,
582 ignore_files: Vec<PathBuf>,
586 ignore_files: Vec<PathBuf>,
583 options: StatusOptions,
587 options: StatusOptions,
584 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
588 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
585 {
589 {
586 super::status::status(self, matcher, root_dir, ignore_files, options)
590 super::status::status(self, matcher, root_dir, ignore_files, options)
587 }
591 }
588
592
589 fn copy_map_len(&self) -> usize {
593 fn copy_map_len(&self) -> usize {
590 self.nodes_with_copy_source_count
594 self.nodes_with_copy_source_count
591 }
595 }
592
596
593 fn copy_map_iter(&self) -> CopyMapIter<'_> {
597 fn copy_map_iter(&self) -> CopyMapIter<'_> {
594 Box::new(self.iter_nodes().filter_map(|(path, node)| {
598 Box::new(self.iter_nodes().filter_map(|(path, node)| {
595 node.copy_source
599 node.copy_source
596 .as_ref()
600 .as_ref()
597 .map(|copy_source| (path.full_path(), copy_source))
601 .map(|copy_source| (path.full_path(), copy_source))
598 }))
602 }))
599 }
603 }
600
604
601 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
605 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
602 if let Some(node) = self.get_node(key) {
606 if let Some(node) = self.get_node(key) {
603 node.copy_source.is_some()
607 node.copy_source.is_some()
604 } else {
608 } else {
605 false
609 false
606 }
610 }
607 }
611 }
608
612
609 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
613 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
610 self.get_node(key)?.copy_source.as_ref()
614 self.get_node(key)?.copy_source.as_ref()
611 }
615 }
612
616
613 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
617 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
614 let count = &mut self.nodes_with_copy_source_count;
618 let count = &mut self.nodes_with_copy_source_count;
615 Self::get_node_mut(&mut self.root, key).and_then(|node| {
619 Self::get_node_mut(&mut self.root, key).and_then(|node| {
616 if node.copy_source.is_some() {
620 if node.copy_source.is_some() {
617 *count -= 1
621 *count -= 1
618 }
622 }
619 node.copy_source.take()
623 node.copy_source.take()
620 })
624 })
621 }
625 }
622
626
623 fn copy_map_insert(
627 fn copy_map_insert(
624 &mut self,
628 &mut self,
625 key: HgPathBuf,
629 key: HgPathBuf,
626 value: HgPathBuf,
630 value: HgPathBuf,
627 ) -> Option<HgPathBuf> {
631 ) -> Option<HgPathBuf> {
628 let node = Self::get_or_insert_node(&mut self.root, &key);
632 let node = Self::get_or_insert_node(&mut self.root, &key);
629 if node.copy_source.is_none() {
633 if node.copy_source.is_none() {
630 self.nodes_with_copy_source_count += 1
634 self.nodes_with_copy_source_count += 1
631 }
635 }
632 node.copy_source.replace(value)
636 node.copy_source.replace(value)
633 }
637 }
634
638
635 fn len(&self) -> usize {
639 fn len(&self) -> usize {
636 self.nodes_with_entry_count
640 self.nodes_with_entry_count
637 }
641 }
638
642
639 fn contains_key(&self, key: &HgPath) -> bool {
643 fn contains_key(&self, key: &HgPath) -> bool {
640 self.get(key).is_some()
644 self.get(key).is_some()
641 }
645 }
642
646
643 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
647 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
644 self.get_node(key)?.entry.as_ref()
648 self.get_node(key)?.entry.as_ref()
645 }
649 }
646
650
647 fn iter(&self) -> StateMapIter<'_> {
651 fn iter(&self) -> StateMapIter<'_> {
648 Box::new(self.iter_nodes().filter_map(|(path, node)| {
652 Box::new(self.iter_nodes().filter_map(|(path, node)| {
649 node.entry.as_ref().map(|entry| (path.full_path(), entry))
653 node.entry.as_ref().map(|entry| (path.full_path(), entry))
650 }))
654 }))
651 }
655 }
652 }
656 }
@@ -1,17 +1,379 b''
1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::ChildNodes;
1 use crate::dirstate_tree::dirstate_map::DirstateMap;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::Node;
5 use crate::matchers::get_ignore_function;
2 use crate::matchers::Matcher;
6 use crate::matchers::Matcher;
7 use crate::utils::files::get_bytes_from_os_string;
8 use crate::utils::hg_path::HgPath;
3 use crate::DirstateStatus;
9 use crate::DirstateStatus;
10 use crate::EntryState;
11 use crate::HgPathBuf;
4 use crate::PatternFileWarning;
12 use crate::PatternFileWarning;
5 use crate::StatusError;
13 use crate::StatusError;
6 use crate::StatusOptions;
14 use crate::StatusOptions;
15 use std::borrow::Cow;
16 use std::io;
17 use std::path::Path;
7 use std::path::PathBuf;
18 use std::path::PathBuf;
8
19
9 pub fn status<'a>(
20 /// Returns the status of the working directory compared to its parent
10 _dmap: &'a mut DirstateMap,
21 /// changeset.
11 _matcher: &'a (dyn Matcher + Sync),
22 ///
12 _root_dir: PathBuf,
23 /// This algorithm is based on traversing the filesystem tree (`fs` in function
13 _ignore_files: Vec<PathBuf>,
24 /// and variable names) and dirstate tree at the same time. The core of this
14 _options: StatusOptions,
25 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
15 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError> {
26 /// and its use of `itertools::merge_join_by`. When reaching a path that only
16 todo!()
27 /// exists in one of the two trees, depending on information requested by
28 /// `options` we may need to traverse the remaining subtree.
29 pub fn status<'tree>(
30 dmap: &'tree mut DirstateMap,
31 matcher: &(dyn Matcher + Sync),
32 root_dir: PathBuf,
33 ignore_files: Vec<PathBuf>,
34 options: StatusOptions,
35 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> {
36 let (ignore_fn, warnings): (IgnoreFnType, _) =
37 if options.list_ignored || options.list_unknown {
38 get_ignore_function(ignore_files, &root_dir)?
39 } else {
40 (Box::new(|&_| true), vec![])
41 };
42
43 let mut common = StatusCommon {
44 options,
45 matcher,
46 ignore_fn,
47 outcome: DirstateStatus::default(),
48 };
49 let is_at_repo_root = true;
50 let hg_path = HgPath::new("");
51 let has_ignored_ancestor = false;
52 common.traverse_fs_directory_and_dirstate(
53 has_ignored_ancestor,
54 &mut dmap.root,
55 hg_path,
56 &root_dir,
57 is_at_repo_root,
58 );
59 Ok((common.outcome, warnings))
60 }
61
62 /// Bag of random things needed by various parts of the algorithm. Reduces the
63 /// number of parameters passed to functions.
64 struct StatusCommon<'tree, 'a> {
65 options: StatusOptions,
66 matcher: &'a (dyn Matcher + Sync),
67 ignore_fn: IgnoreFnType<'a>,
68 outcome: DirstateStatus<'tree>,
17 }
69 }
70
71 impl<'tree, 'a> StatusCommon<'tree, 'a> {
72 fn traverse_fs_directory_and_dirstate(
73 &mut self,
74 has_ignored_ancestor: bool,
75 dirstate_nodes: &'tree mut ChildNodes,
76 directory_hg_path: &HgPath,
77 fs_path: &Path,
78 is_at_repo_root: bool,
79 ) {
80 // TODO: handle I/O errors
81 let mut fs_entries =
82 DirEntry::read_dir(fs_path, is_at_repo_root).unwrap();
83
84 // `merge_join_by` requires both its input iterators to be sorted:
85
86 // * `BTreeMap` iterates according to keys’ ordering by definition
87
88 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
89 // https://github.com/rust-lang/rust/issues/34162
90 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
91
92 for pair in itertools::merge_join_by(
93 dirstate_nodes,
94 &fs_entries,
95 |(full_path, _node), fs_entry| {
96 full_path.base_name().cmp(&fs_entry.base_name)
97 },
98 ) {
99 use itertools::EitherOrBoth::*;
100 match pair {
101 Both((hg_path, dirstate_node), fs_entry) => {
102 self.traverse_fs_and_dirstate(
103 fs_entry,
104 hg_path.full_path(),
105 dirstate_node,
106 has_ignored_ancestor,
107 );
108 }
109 Left((hg_path, dirstate_node)) => self.traverse_dirstate_only(
110 hg_path.full_path(),
111 dirstate_node,
112 ),
113 Right(fs_entry) => self.traverse_fs_only(
114 has_ignored_ancestor,
115 directory_hg_path,
116 fs_entry,
117 ),
118 }
119 }
120 }
121
122 fn traverse_fs_and_dirstate(
123 &mut self,
124 fs_entry: &DirEntry,
125 hg_path: &'tree HgPath,
126 dirstate_node: &'tree mut Node,
127 has_ignored_ancestor: bool,
128 ) {
129 if fs_entry.metadata.is_dir() {
130 if self.options.collect_traversed_dirs {
131 self.outcome.traversed.push(hg_path.into())
132 }
133 // If we previously had a file here, it was removed (with
134 // `hg rm` or similar) or deleted before it could be
135 // replaced by a directory.
136 self.mark_removed_or_deleted_if_file(
137 hg_path,
138 dirstate_node.state(),
139 );
140 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
141 let is_at_repo_root = false;
142 self.traverse_fs_directory_and_dirstate(
143 is_ignored,
144 &mut dirstate_node.children,
145 hg_path,
146 &fs_entry.full_path,
147 is_at_repo_root,
148 );
149 } else {
150 if self.matcher.matches(hg_path) {
151 let full_path = Cow::from(hg_path);
152 if let Some(entry) = &dirstate_node.entry {
153 match entry.state {
154 EntryState::Added => {
155 self.outcome.added.push(full_path)
156 }
157 EntryState::Removed => {
158 self.outcome.removed.push(full_path)
159 }
160 EntryState::Merged => {
161 self.outcome.modified.push(full_path)
162 }
163 EntryState::Normal => {
164 self.handle_normal_file(
165 full_path,
166 dirstate_node,
167 entry,
168 fs_entry,
169 );
170 }
171 // This variant is not used in DirstateMap
172 // nodes
173 EntryState::Unknown => unreachable!(),
174 }
175 } else {
176 // `node.entry.is_none()` indicates a "directory"
177 // node, but the filesystem has a file
178 self.mark_unknown_or_ignored(
179 has_ignored_ancestor,
180 full_path,
181 )
182 }
183 }
184
185 for (child_hg_path, child_node) in &mut dirstate_node.children {
186 self.traverse_dirstate_only(
187 child_hg_path.full_path(),
188 child_node,
189 )
190 }
191 }
192 }
193
194 /// A file with `EntryState::Normal` in the dirstate was found in the
195 /// filesystem
196 fn handle_normal_file(
197 &mut self,
198 full_path: Cow<'tree, HgPath>,
199 dirstate_node: &Node,
200 entry: &crate::DirstateEntry,
201 fs_entry: &DirEntry,
202 ) {
203 // Keep the low 31 bits
204 fn truncate_u64(value: u64) -> i32 {
205 (value & 0x7FFF_FFFF) as i32
206 }
207 fn truncate_i64(value: i64) -> i32 {
208 (value & 0x7FFF_FFFF) as i32
209 }
210
211 let mode_changed = || {
212 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
213 };
214 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
215 if entry.size >= 0
216 && size_changed
217 && fs_entry.metadata.file_type().is_symlink()
218 {
219 // issue6456: Size returned may be longer due to encryption
220 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
221 self.outcome.unsure.push(full_path)
222 } else if dirstate_node.copy_source.is_some()
223 || entry.is_from_other_parent()
224 || (entry.size >= 0 && (size_changed || mode_changed()))
225 {
226 self.outcome.modified.push(full_path)
227 } else {
228 let mtime = mtime_seconds(&fs_entry.metadata);
229 if truncate_i64(mtime) != entry.mtime
230 || mtime == self.options.last_normal_time
231 {
232 self.outcome.unsure.push(full_path)
233 } else if self.options.list_clean {
234 self.outcome.clean.push(full_path)
235 }
236 }
237 }
238
239 /// A node in the dirstate tree has no corresponding filesystem entry
240 fn traverse_dirstate_only(
241 &mut self,
242 hg_path: &'tree HgPath,
243 dirstate_node: &'tree mut Node,
244 ) {
245 self.mark_removed_or_deleted_if_file(hg_path, dirstate_node.state());
246 for (child_hg_path, child_node) in &mut dirstate_node.children {
247 self.traverse_dirstate_only(child_hg_path.full_path(), child_node)
248 }
249 }
250
251 /// A node in the dirstate tree has no corresponding *file* on the
252 /// filesystem
253 ///
254 /// Does nothing on a "directory" node
255 fn mark_removed_or_deleted_if_file(
256 &mut self,
257 hg_path: &'tree HgPath,
258 dirstate_node_state: Option<EntryState>,
259 ) {
260 if let Some(state) = dirstate_node_state {
261 if self.matcher.matches(hg_path) {
262 if let EntryState::Removed = state {
263 self.outcome.removed.push(hg_path.into())
264 } else {
265 self.outcome.deleted.push(hg_path.into())
266 }
267 }
268 }
269 }
270
271 /// Something in the filesystem has no corresponding dirstate node
272 fn traverse_fs_only(
273 &mut self,
274 has_ignored_ancestor: bool,
275 directory_hg_path: &HgPath,
276 fs_entry: &DirEntry,
277 ) {
278 let hg_path = directory_hg_path.join(&fs_entry.base_name);
279 if fs_entry.metadata.is_dir() {
280 let is_ignored =
281 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
282 let traverse_children = if is_ignored {
283 // Descendants of an ignored directory are all ignored
284 self.options.list_ignored
285 } else {
286 // Descendants of an unknown directory may be either unknown or
287 // ignored
288 self.options.list_unknown || self.options.list_ignored
289 };
290 if traverse_children {
291 let is_at_repo_root = false;
292 // TODO: handle I/O errors
293 let children_fs_entries =
294 DirEntry::read_dir(&fs_entry.full_path, is_at_repo_root)
295 .unwrap();
296 for child_fs_entry in children_fs_entries {
297 self.traverse_fs_only(
298 is_ignored,
299 &hg_path,
300 &child_fs_entry,
301 )
302 }
303 }
304 if self.options.collect_traversed_dirs {
305 self.outcome.traversed.push(hg_path.into())
306 }
307 } else if self.matcher.matches(&hg_path) {
308 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
309 }
310 }
311
312 fn mark_unknown_or_ignored(
313 &mut self,
314 has_ignored_ancestor: bool,
315 hg_path: Cow<'tree, HgPath>,
316 ) {
317 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
318 if is_ignored {
319 if self.options.list_ignored {
320 self.outcome.ignored.push(hg_path)
321 }
322 } else {
323 if self.options.list_unknown {
324 self.outcome.unknown.push(hg_path)
325 }
326 }
327 }
328 }
329
330 #[cfg(unix)] // TODO
331 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
332 // Going through `Metadata::modified()` would be portable, but would take
333 // care to construct a `SystemTime` value with sub-second precision just
334 // for us to throw that away here.
335 use std::os::unix::fs::MetadataExt;
336 metadata.mtime()
337 }
338
339 struct DirEntry {
340 base_name: HgPathBuf,
341 full_path: PathBuf,
342 metadata: std::fs::Metadata,
343 }
344
345 impl DirEntry {
346 /// Returns **unsorted** entries in the given directory, with name and
347 /// metadata.
348 ///
349 /// If a `.hg` sub-directory is encountered:
350 ///
351 /// * At the repository root, ignore that sub-directory
352 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
353 /// list instead.
354 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
355 let mut results = Vec::new();
356 for entry in path.read_dir()? {
357 let entry = entry?;
358 let metadata = entry.metadata()?;
359 let name = get_bytes_from_os_string(entry.file_name());
360 // FIXME don't do this when cached
361 if name == b".hg" {
362 if is_at_repo_root {
363 // Skip the repo’s own .hg (might be a symlink)
364 continue;
365 } else if metadata.is_dir() {
366 // A .hg sub-directory at another location means a subrepo,
367 // skip it entirely.
368 return Ok(Vec::new());
369 }
370 }
371 results.push(DirEntry {
372 base_name: name.into(),
373 full_path: entry.path(),
374 metadata,
375 })
376 }
377 Ok(results)
378 }
379 }
@@ -1,463 +1,469 b''
1 // files.rs
1 // files.rs
2 //
2 //
3 // Copyright 2019
3 // Copyright 2019
4 // Raphaël Gomès <rgomes@octobus.net>,
4 // Raphaël Gomès <rgomes@octobus.net>,
5 // Yuya Nishihara <yuya@tcha.org>
5 // Yuya Nishihara <yuya@tcha.org>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Functions for fiddling with files.
10 //! Functions for fiddling with files.
11
11
12 use crate::utils::{
12 use crate::utils::{
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
14 path_auditor::PathAuditor,
14 path_auditor::PathAuditor,
15 replace_slice,
15 replace_slice,
16 };
16 };
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 use same_file::is_same_file;
18 use same_file::is_same_file;
19 use std::borrow::{Cow, ToOwned};
19 use std::borrow::{Cow, ToOwned};
20 use std::ffi::OsStr;
20 use std::ffi::{OsStr, OsString};
21 use std::fs::Metadata;
21 use std::fs::Metadata;
22 use std::iter::FusedIterator;
22 use std::iter::FusedIterator;
23 use std::ops::Deref;
23 use std::ops::Deref;
24 use std::path::{Path, PathBuf};
24 use std::path::{Path, PathBuf};
25
25
26 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
26 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
27 let os_str;
27 let os_str;
28 #[cfg(unix)]
28 #[cfg(unix)]
29 {
29 {
30 use std::os::unix::ffi::OsStrExt;
30 use std::os::unix::ffi::OsStrExt;
31 os_str = std::ffi::OsStr::from_bytes(bytes);
31 os_str = std::ffi::OsStr::from_bytes(bytes);
32 }
32 }
33 // TODO Handle other platforms
33 // TODO Handle other platforms
34 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
34 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
35 // Perhaps, the return type would have to be Result<PathBuf>.
35 // Perhaps, the return type would have to be Result<PathBuf>.
36 os_str
36 os_str
37 }
37 }
38
38
39 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
39 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
40 Path::new(get_os_str_from_bytes(bytes))
40 Path::new(get_os_str_from_bytes(bytes))
41 }
41 }
42
42
43 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
43 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
44 // that's why Vec<u8> is returned.
44 // that's why Vec<u8> is returned.
45 #[cfg(unix)]
45 #[cfg(unix)]
46 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
46 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
47 get_bytes_from_os_str(path.as_ref())
47 get_bytes_from_os_str(path.as_ref())
48 }
48 }
49
49
50 #[cfg(unix)]
50 #[cfg(unix)]
51 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
51 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
52 use std::os::unix::ffi::OsStrExt;
52 use std::os::unix::ffi::OsStrExt;
53 str.as_ref().as_bytes().to_vec()
53 str.as_ref().as_bytes().to_vec()
54 }
54 }
55
55
56 #[cfg(unix)]
57 pub fn get_bytes_from_os_string(str: OsString) -> Vec<u8> {
58 use std::os::unix::ffi::OsStringExt;
59 str.into_vec()
60 }
61
56 /// An iterator over repository path yielding itself and its ancestors.
62 /// An iterator over repository path yielding itself and its ancestors.
57 #[derive(Copy, Clone, Debug)]
63 #[derive(Copy, Clone, Debug)]
58 pub struct Ancestors<'a> {
64 pub struct Ancestors<'a> {
59 next: Option<&'a HgPath>,
65 next: Option<&'a HgPath>,
60 }
66 }
61
67
62 impl<'a> Iterator for Ancestors<'a> {
68 impl<'a> Iterator for Ancestors<'a> {
63 type Item = &'a HgPath;
69 type Item = &'a HgPath;
64
70
65 fn next(&mut self) -> Option<Self::Item> {
71 fn next(&mut self) -> Option<Self::Item> {
66 let next = self.next;
72 let next = self.next;
67 self.next = match self.next {
73 self.next = match self.next {
68 Some(s) if s.is_empty() => None,
74 Some(s) if s.is_empty() => None,
69 Some(s) => {
75 Some(s) => {
70 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
76 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
71 Some(HgPath::new(&s.as_bytes()[..p]))
77 Some(HgPath::new(&s.as_bytes()[..p]))
72 }
78 }
73 None => None,
79 None => None,
74 };
80 };
75 next
81 next
76 }
82 }
77 }
83 }
78
84
79 impl<'a> FusedIterator for Ancestors<'a> {}
85 impl<'a> FusedIterator for Ancestors<'a> {}
80
86
81 /// An iterator over repository path yielding itself and its ancestors.
87 /// An iterator over repository path yielding itself and its ancestors.
82 #[derive(Copy, Clone, Debug)]
88 #[derive(Copy, Clone, Debug)]
83 pub(crate) struct AncestorsWithBase<'a> {
89 pub(crate) struct AncestorsWithBase<'a> {
84 next: Option<(&'a HgPath, &'a HgPath)>,
90 next: Option<(&'a HgPath, &'a HgPath)>,
85 }
91 }
86
92
87 impl<'a> Iterator for AncestorsWithBase<'a> {
93 impl<'a> Iterator for AncestorsWithBase<'a> {
88 type Item = (&'a HgPath, &'a HgPath);
94 type Item = (&'a HgPath, &'a HgPath);
89
95
90 fn next(&mut self) -> Option<Self::Item> {
96 fn next(&mut self) -> Option<Self::Item> {
91 let next = self.next;
97 let next = self.next;
92 self.next = match self.next {
98 self.next = match self.next {
93 Some((s, _)) if s.is_empty() => None,
99 Some((s, _)) if s.is_empty() => None,
94 Some((s, _)) => Some(s.split_filename()),
100 Some((s, _)) => Some(s.split_filename()),
95 None => None,
101 None => None,
96 };
102 };
97 next
103 next
98 }
104 }
99 }
105 }
100
106
101 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
107 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
102
108
103 /// Returns an iterator yielding ancestor directories of the given repository
109 /// Returns an iterator yielding ancestor directories of the given repository
104 /// path.
110 /// path.
105 ///
111 ///
106 /// The path is separated by '/', and must not start with '/'.
112 /// The path is separated by '/', and must not start with '/'.
107 ///
113 ///
108 /// The path itself isn't included unless it is b"" (meaning the root
114 /// The path itself isn't included unless it is b"" (meaning the root
109 /// directory.)
115 /// directory.)
110 pub fn find_dirs(path: &HgPath) -> Ancestors {
116 pub fn find_dirs(path: &HgPath) -> Ancestors {
111 let mut dirs = Ancestors { next: Some(path) };
117 let mut dirs = Ancestors { next: Some(path) };
112 if !path.is_empty() {
118 if !path.is_empty() {
113 dirs.next(); // skip itself
119 dirs.next(); // skip itself
114 }
120 }
115 dirs
121 dirs
116 }
122 }
117
123
118 /// Returns an iterator yielding ancestor directories of the given repository
124 /// Returns an iterator yielding ancestor directories of the given repository
119 /// path.
125 /// path.
120 ///
126 ///
121 /// The path is separated by '/', and must not start with '/'.
127 /// The path is separated by '/', and must not start with '/'.
122 ///
128 ///
123 /// The path itself isn't included unless it is b"" (meaning the root
129 /// The path itself isn't included unless it is b"" (meaning the root
124 /// directory.)
130 /// directory.)
125 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
131 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
126 let mut dirs = AncestorsWithBase {
132 let mut dirs = AncestorsWithBase {
127 next: Some((path, HgPath::new(b""))),
133 next: Some((path, HgPath::new(b""))),
128 };
134 };
129 if !path.is_empty() {
135 if !path.is_empty() {
130 dirs.next(); // skip itself
136 dirs.next(); // skip itself
131 }
137 }
132 dirs
138 dirs
133 }
139 }
134
140
135 /// TODO more than ASCII?
141 /// TODO more than ASCII?
136 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
142 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
137 #[cfg(windows)] // NTFS compares via upper()
143 #[cfg(windows)] // NTFS compares via upper()
138 return path.to_ascii_uppercase();
144 return path.to_ascii_uppercase();
139 #[cfg(unix)]
145 #[cfg(unix)]
140 path.to_ascii_lowercase()
146 path.to_ascii_lowercase()
141 }
147 }
142
148
143 lazy_static! {
149 lazy_static! {
144 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
150 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
145 [
151 [
146 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
152 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
147 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
153 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
148 ]
154 ]
149 .iter()
155 .iter()
150 .map(|code| {
156 .map(|code| {
151 std::char::from_u32(*code)
157 std::char::from_u32(*code)
152 .unwrap()
158 .unwrap()
153 .encode_utf8(&mut [0; 3])
159 .encode_utf8(&mut [0; 3])
154 .bytes()
160 .bytes()
155 .collect()
161 .collect()
156 })
162 })
157 .collect()
163 .collect()
158 };
164 };
159 }
165 }
160
166
161 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
167 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
162 let mut buf = bytes.to_owned();
168 let mut buf = bytes.to_owned();
163 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
169 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
164 if needs_escaping {
170 if needs_escaping {
165 for forbidden in IGNORED_CHARS.iter() {
171 for forbidden in IGNORED_CHARS.iter() {
166 replace_slice(&mut buf, forbidden, &[])
172 replace_slice(&mut buf, forbidden, &[])
167 }
173 }
168 buf
174 buf
169 } else {
175 } else {
170 buf
176 buf
171 }
177 }
172 }
178 }
173
179
174 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
180 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
175 hfs_ignore_clean(&bytes.to_ascii_lowercase())
181 hfs_ignore_clean(&bytes.to_ascii_lowercase())
176 }
182 }
177
183
178 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
184 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
179 pub struct HgMetadata {
185 pub struct HgMetadata {
180 pub st_dev: u64,
186 pub st_dev: u64,
181 pub st_mode: u32,
187 pub st_mode: u32,
182 pub st_nlink: u64,
188 pub st_nlink: u64,
183 pub st_size: u64,
189 pub st_size: u64,
184 pub st_mtime: i64,
190 pub st_mtime: i64,
185 pub st_ctime: i64,
191 pub st_ctime: i64,
186 }
192 }
187
193
188 // TODO support other plaforms
194 // TODO support other plaforms
189 #[cfg(unix)]
195 #[cfg(unix)]
190 impl HgMetadata {
196 impl HgMetadata {
191 pub fn from_metadata(metadata: Metadata) -> Self {
197 pub fn from_metadata(metadata: Metadata) -> Self {
192 use std::os::unix::fs::MetadataExt;
198 use std::os::unix::fs::MetadataExt;
193 Self {
199 Self {
194 st_dev: metadata.dev(),
200 st_dev: metadata.dev(),
195 st_mode: metadata.mode(),
201 st_mode: metadata.mode(),
196 st_nlink: metadata.nlink(),
202 st_nlink: metadata.nlink(),
197 st_size: metadata.size(),
203 st_size: metadata.size(),
198 st_mtime: metadata.mtime(),
204 st_mtime: metadata.mtime(),
199 st_ctime: metadata.ctime(),
205 st_ctime: metadata.ctime(),
200 }
206 }
201 }
207 }
202
208
203 pub fn is_symlink(&self) -> bool {
209 pub fn is_symlink(&self) -> bool {
204 // This is way too manual, but `HgMetadata` will go away in the
210 // This is way too manual, but `HgMetadata` will go away in the
205 // near-future dirstate rewrite anyway.
211 // near-future dirstate rewrite anyway.
206 self.st_mode & 0170000 == 0120000
212 self.st_mode & 0170000 == 0120000
207 }
213 }
208 }
214 }
209
215
210 /// Returns the canonical path of `name`, given `cwd` and `root`
216 /// Returns the canonical path of `name`, given `cwd` and `root`
211 pub fn canonical_path(
217 pub fn canonical_path(
212 root: impl AsRef<Path>,
218 root: impl AsRef<Path>,
213 cwd: impl AsRef<Path>,
219 cwd: impl AsRef<Path>,
214 name: impl AsRef<Path>,
220 name: impl AsRef<Path>,
215 ) -> Result<PathBuf, HgPathError> {
221 ) -> Result<PathBuf, HgPathError> {
216 // TODO add missing normalization for other platforms
222 // TODO add missing normalization for other platforms
217 let root = root.as_ref();
223 let root = root.as_ref();
218 let cwd = cwd.as_ref();
224 let cwd = cwd.as_ref();
219 let name = name.as_ref();
225 let name = name.as_ref();
220
226
221 let name = if !name.is_absolute() {
227 let name = if !name.is_absolute() {
222 root.join(&cwd).join(&name)
228 root.join(&cwd).join(&name)
223 } else {
229 } else {
224 name.to_owned()
230 name.to_owned()
225 };
231 };
226 let auditor = PathAuditor::new(&root);
232 let auditor = PathAuditor::new(&root);
227 if name != root && name.starts_with(&root) {
233 if name != root && name.starts_with(&root) {
228 let name = name.strip_prefix(&root).unwrap();
234 let name = name.strip_prefix(&root).unwrap();
229 auditor.audit_path(path_to_hg_path_buf(name)?)?;
235 auditor.audit_path(path_to_hg_path_buf(name)?)?;
230 Ok(name.to_owned())
236 Ok(name.to_owned())
231 } else if name == root {
237 } else if name == root {
232 Ok("".into())
238 Ok("".into())
233 } else {
239 } else {
234 // Determine whether `name' is in the hierarchy at or beneath `root',
240 // Determine whether `name' is in the hierarchy at or beneath `root',
235 // by iterating name=name.parent() until it returns `None` (can't
241 // by iterating name=name.parent() until it returns `None` (can't
236 // check name == '/', because that doesn't work on windows).
242 // check name == '/', because that doesn't work on windows).
237 let mut name = name.deref();
243 let mut name = name.deref();
238 let original_name = name.to_owned();
244 let original_name = name.to_owned();
239 loop {
245 loop {
240 let same = is_same_file(&name, &root).unwrap_or(false);
246 let same = is_same_file(&name, &root).unwrap_or(false);
241 if same {
247 if same {
242 if name == original_name {
248 if name == original_name {
243 // `name` was actually the same as root (maybe a symlink)
249 // `name` was actually the same as root (maybe a symlink)
244 return Ok("".into());
250 return Ok("".into());
245 }
251 }
246 // `name` is a symlink to root, so `original_name` is under
252 // `name` is a symlink to root, so `original_name` is under
247 // root
253 // root
248 let rel_path = original_name.strip_prefix(&name).unwrap();
254 let rel_path = original_name.strip_prefix(&name).unwrap();
249 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
255 auditor.audit_path(path_to_hg_path_buf(&rel_path)?)?;
250 return Ok(rel_path.to_owned());
256 return Ok(rel_path.to_owned());
251 }
257 }
252 name = match name.parent() {
258 name = match name.parent() {
253 None => break,
259 None => break,
254 Some(p) => p,
260 Some(p) => p,
255 };
261 };
256 }
262 }
257 // TODO hint to the user about using --cwd
263 // TODO hint to the user about using --cwd
258 // Bubble up the responsibility to Python for now
264 // Bubble up the responsibility to Python for now
259 Err(HgPathError::NotUnderRoot {
265 Err(HgPathError::NotUnderRoot {
260 path: original_name.to_owned(),
266 path: original_name.to_owned(),
261 root: root.to_owned(),
267 root: root.to_owned(),
262 })
268 })
263 }
269 }
264 }
270 }
265
271
266 /// Returns the representation of the path relative to the current working
272 /// Returns the representation of the path relative to the current working
267 /// directory for display purposes.
273 /// directory for display purposes.
268 ///
274 ///
269 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
275 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
270 /// of the repository.
276 /// of the repository.
271 ///
277 ///
272 /// # Examples
278 /// # Examples
273 ///
279 ///
274 /// ```
280 /// ```
275 /// use hg::utils::hg_path::HgPath;
281 /// use hg::utils::hg_path::HgPath;
276 /// use hg::utils::files::relativize_path;
282 /// use hg::utils::files::relativize_path;
277 /// use std::borrow::Cow;
283 /// use std::borrow::Cow;
278 ///
284 ///
279 /// let file = HgPath::new(b"nested/file");
285 /// let file = HgPath::new(b"nested/file");
280 /// let cwd = HgPath::new(b"");
286 /// let cwd = HgPath::new(b"");
281 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
287 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
282 ///
288 ///
283 /// let cwd = HgPath::new(b"nested");
289 /// let cwd = HgPath::new(b"nested");
284 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
290 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
285 ///
291 ///
286 /// let cwd = HgPath::new(b"other");
292 /// let cwd = HgPath::new(b"other");
287 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
293 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
288 /// ```
294 /// ```
289 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
295 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
290 if cwd.as_ref().is_empty() {
296 if cwd.as_ref().is_empty() {
291 Cow::Borrowed(path.as_bytes())
297 Cow::Borrowed(path.as_bytes())
292 } else {
298 } else {
293 // This is not all accurate as to how large `res` will actually be, but
299 // This is not all accurate as to how large `res` will actually be, but
294 // profiling `rhg files` on a large-ish repo shows it’s better than
300 // profiling `rhg files` on a large-ish repo shows it’s better than
295 // starting from a zero-capacity `Vec` and letting `extend` reallocate
301 // starting from a zero-capacity `Vec` and letting `extend` reallocate
296 // repeatedly.
302 // repeatedly.
297 let guesstimate = path.as_bytes().len();
303 let guesstimate = path.as_bytes().len();
298
304
299 let mut res: Vec<u8> = Vec::with_capacity(guesstimate);
305 let mut res: Vec<u8> = Vec::with_capacity(guesstimate);
300 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
306 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
301 let mut cwd_iter =
307 let mut cwd_iter =
302 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
308 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
303 loop {
309 loop {
304 match (path_iter.peek(), cwd_iter.peek()) {
310 match (path_iter.peek(), cwd_iter.peek()) {
305 (Some(a), Some(b)) if a == b => (),
311 (Some(a), Some(b)) if a == b => (),
306 _ => break,
312 _ => break,
307 }
313 }
308 path_iter.next();
314 path_iter.next();
309 cwd_iter.next();
315 cwd_iter.next();
310 }
316 }
311 let mut need_sep = false;
317 let mut need_sep = false;
312 for _ in cwd_iter {
318 for _ in cwd_iter {
313 if need_sep {
319 if need_sep {
314 res.extend(b"/")
320 res.extend(b"/")
315 } else {
321 } else {
316 need_sep = true
322 need_sep = true
317 };
323 };
318 res.extend(b"..");
324 res.extend(b"..");
319 }
325 }
320 for c in path_iter {
326 for c in path_iter {
321 if need_sep {
327 if need_sep {
322 res.extend(b"/")
328 res.extend(b"/")
323 } else {
329 } else {
324 need_sep = true
330 need_sep = true
325 };
331 };
326 res.extend(c);
332 res.extend(c);
327 }
333 }
328 Cow::Owned(res)
334 Cow::Owned(res)
329 }
335 }
330 }
336 }
331
337
332 #[cfg(test)]
338 #[cfg(test)]
333 mod tests {
339 mod tests {
334 use super::*;
340 use super::*;
335 use pretty_assertions::assert_eq;
341 use pretty_assertions::assert_eq;
336
342
337 #[test]
343 #[test]
338 fn find_dirs_some() {
344 fn find_dirs_some() {
339 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
345 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
340 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
346 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
341 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
347 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
342 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
348 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
343 assert_eq!(dirs.next(), None);
349 assert_eq!(dirs.next(), None);
344 assert_eq!(dirs.next(), None);
350 assert_eq!(dirs.next(), None);
345 }
351 }
346
352
347 #[test]
353 #[test]
348 fn find_dirs_empty() {
354 fn find_dirs_empty() {
349 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
355 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
350 let mut dirs = super::find_dirs(HgPath::new(b""));
356 let mut dirs = super::find_dirs(HgPath::new(b""));
351 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
357 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
352 assert_eq!(dirs.next(), None);
358 assert_eq!(dirs.next(), None);
353 assert_eq!(dirs.next(), None);
359 assert_eq!(dirs.next(), None);
354 }
360 }
355
361
356 #[test]
362 #[test]
357 fn test_find_dirs_with_base_some() {
363 fn test_find_dirs_with_base_some() {
358 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
364 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
359 assert_eq!(
365 assert_eq!(
360 dirs.next(),
366 dirs.next(),
361 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
367 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
362 );
368 );
363 assert_eq!(
369 assert_eq!(
364 dirs.next(),
370 dirs.next(),
365 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
371 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
366 );
372 );
367 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
373 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
368 assert_eq!(dirs.next(), None);
374 assert_eq!(dirs.next(), None);
369 assert_eq!(dirs.next(), None);
375 assert_eq!(dirs.next(), None);
370 }
376 }
371
377
372 #[test]
378 #[test]
373 fn test_find_dirs_with_base_empty() {
379 fn test_find_dirs_with_base_empty() {
374 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
380 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
375 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
381 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
376 assert_eq!(dirs.next(), None);
382 assert_eq!(dirs.next(), None);
377 assert_eq!(dirs.next(), None);
383 assert_eq!(dirs.next(), None);
378 }
384 }
379
385
380 #[test]
386 #[test]
381 fn test_canonical_path() {
387 fn test_canonical_path() {
382 let root = Path::new("/repo");
388 let root = Path::new("/repo");
383 let cwd = Path::new("/dir");
389 let cwd = Path::new("/dir");
384 let name = Path::new("filename");
390 let name = Path::new("filename");
385 assert_eq!(
391 assert_eq!(
386 canonical_path(root, cwd, name),
392 canonical_path(root, cwd, name),
387 Err(HgPathError::NotUnderRoot {
393 Err(HgPathError::NotUnderRoot {
388 path: PathBuf::from("/dir/filename"),
394 path: PathBuf::from("/dir/filename"),
389 root: root.to_path_buf()
395 root: root.to_path_buf()
390 })
396 })
391 );
397 );
392
398
393 let root = Path::new("/repo");
399 let root = Path::new("/repo");
394 let cwd = Path::new("/");
400 let cwd = Path::new("/");
395 let name = Path::new("filename");
401 let name = Path::new("filename");
396 assert_eq!(
402 assert_eq!(
397 canonical_path(root, cwd, name),
403 canonical_path(root, cwd, name),
398 Err(HgPathError::NotUnderRoot {
404 Err(HgPathError::NotUnderRoot {
399 path: PathBuf::from("/filename"),
405 path: PathBuf::from("/filename"),
400 root: root.to_path_buf()
406 root: root.to_path_buf()
401 })
407 })
402 );
408 );
403
409
404 let root = Path::new("/repo");
410 let root = Path::new("/repo");
405 let cwd = Path::new("/");
411 let cwd = Path::new("/");
406 let name = Path::new("repo/filename");
412 let name = Path::new("repo/filename");
407 assert_eq!(
413 assert_eq!(
408 canonical_path(root, cwd, name),
414 canonical_path(root, cwd, name),
409 Ok(PathBuf::from("filename"))
415 Ok(PathBuf::from("filename"))
410 );
416 );
411
417
412 let root = Path::new("/repo");
418 let root = Path::new("/repo");
413 let cwd = Path::new("/repo");
419 let cwd = Path::new("/repo");
414 let name = Path::new("filename");
420 let name = Path::new("filename");
415 assert_eq!(
421 assert_eq!(
416 canonical_path(root, cwd, name),
422 canonical_path(root, cwd, name),
417 Ok(PathBuf::from("filename"))
423 Ok(PathBuf::from("filename"))
418 );
424 );
419
425
420 let root = Path::new("/repo");
426 let root = Path::new("/repo");
421 let cwd = Path::new("/repo/subdir");
427 let cwd = Path::new("/repo/subdir");
422 let name = Path::new("filename");
428 let name = Path::new("filename");
423 assert_eq!(
429 assert_eq!(
424 canonical_path(root, cwd, name),
430 canonical_path(root, cwd, name),
425 Ok(PathBuf::from("subdir/filename"))
431 Ok(PathBuf::from("subdir/filename"))
426 );
432 );
427 }
433 }
428
434
429 #[test]
435 #[test]
430 fn test_canonical_path_not_rooted() {
436 fn test_canonical_path_not_rooted() {
431 use std::fs::create_dir;
437 use std::fs::create_dir;
432 use tempfile::tempdir;
438 use tempfile::tempdir;
433
439
434 let base_dir = tempdir().unwrap();
440 let base_dir = tempdir().unwrap();
435 let base_dir_path = base_dir.path();
441 let base_dir_path = base_dir.path();
436 let beneath_repo = base_dir_path.join("a");
442 let beneath_repo = base_dir_path.join("a");
437 let root = base_dir_path.join("a/b");
443 let root = base_dir_path.join("a/b");
438 let out_of_repo = base_dir_path.join("c");
444 let out_of_repo = base_dir_path.join("c");
439 let under_repo_symlink = out_of_repo.join("d");
445 let under_repo_symlink = out_of_repo.join("d");
440
446
441 create_dir(&beneath_repo).unwrap();
447 create_dir(&beneath_repo).unwrap();
442 create_dir(&root).unwrap();
448 create_dir(&root).unwrap();
443
449
444 // TODO make portable
450 // TODO make portable
445 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
451 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
446
452
447 assert_eq!(
453 assert_eq!(
448 canonical_path(&root, Path::new(""), out_of_repo),
454 canonical_path(&root, Path::new(""), out_of_repo),
449 Ok(PathBuf::from(""))
455 Ok(PathBuf::from(""))
450 );
456 );
451 assert_eq!(
457 assert_eq!(
452 canonical_path(&root, Path::new(""), &beneath_repo),
458 canonical_path(&root, Path::new(""), &beneath_repo),
453 Err(HgPathError::NotUnderRoot {
459 Err(HgPathError::NotUnderRoot {
454 path: beneath_repo.to_owned(),
460 path: beneath_repo.to_owned(),
455 root: root.to_owned()
461 root: root.to_owned()
456 })
462 })
457 );
463 );
458 assert_eq!(
464 assert_eq!(
459 canonical_path(&root, Path::new(""), &under_repo_symlink),
465 canonical_path(&root, Path::new(""), &under_repo_symlink),
460 Ok(PathBuf::from("d"))
466 Ok(PathBuf::from("d"))
461 );
467 );
462 }
468 }
463 }
469 }
@@ -1,785 +1,804 b''
1 // hg_path.rs
1 // hg_path.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use std::borrow::Borrow;
8 use std::borrow::Borrow;
9 use std::borrow::Cow;
9 use std::convert::TryFrom;
10 use std::convert::TryFrom;
10 use std::ffi::{OsStr, OsString};
11 use std::ffi::{OsStr, OsString};
11 use std::fmt;
12 use std::fmt;
12 use std::ops::Deref;
13 use std::ops::Deref;
13 use std::path::{Path, PathBuf};
14 use std::path::{Path, PathBuf};
14
15
15 #[derive(Debug, Eq, PartialEq)]
16 #[derive(Debug, Eq, PartialEq)]
16 pub enum HgPathError {
17 pub enum HgPathError {
17 /// Bytes from the invalid `HgPath`
18 /// Bytes from the invalid `HgPath`
18 LeadingSlash(Vec<u8>),
19 LeadingSlash(Vec<u8>),
19 ConsecutiveSlashes {
20 ConsecutiveSlashes {
20 bytes: Vec<u8>,
21 bytes: Vec<u8>,
21 second_slash_index: usize,
22 second_slash_index: usize,
22 },
23 },
23 ContainsNullByte {
24 ContainsNullByte {
24 bytes: Vec<u8>,
25 bytes: Vec<u8>,
25 null_byte_index: usize,
26 null_byte_index: usize,
26 },
27 },
27 /// Bytes
28 /// Bytes
28 DecodeError(Vec<u8>),
29 DecodeError(Vec<u8>),
29 /// The rest come from audit errors
30 /// The rest come from audit errors
30 EndsWithSlash(HgPathBuf),
31 EndsWithSlash(HgPathBuf),
31 ContainsIllegalComponent(HgPathBuf),
32 ContainsIllegalComponent(HgPathBuf),
32 /// Path is inside the `.hg` folder
33 /// Path is inside the `.hg` folder
33 InsideDotHg(HgPathBuf),
34 InsideDotHg(HgPathBuf),
34 IsInsideNestedRepo {
35 IsInsideNestedRepo {
35 path: HgPathBuf,
36 path: HgPathBuf,
36 nested_repo: HgPathBuf,
37 nested_repo: HgPathBuf,
37 },
38 },
38 TraversesSymbolicLink {
39 TraversesSymbolicLink {
39 path: HgPathBuf,
40 path: HgPathBuf,
40 symlink: HgPathBuf,
41 symlink: HgPathBuf,
41 },
42 },
42 NotFsCompliant(HgPathBuf),
43 NotFsCompliant(HgPathBuf),
43 /// `path` is the smallest invalid path
44 /// `path` is the smallest invalid path
44 NotUnderRoot {
45 NotUnderRoot {
45 path: PathBuf,
46 path: PathBuf,
46 root: PathBuf,
47 root: PathBuf,
47 },
48 },
48 }
49 }
49
50
50 impl fmt::Display for HgPathError {
51 impl fmt::Display for HgPathError {
51 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 match self {
53 match self {
53 HgPathError::LeadingSlash(bytes) => {
54 HgPathError::LeadingSlash(bytes) => {
54 write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes)
55 write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes)
55 }
56 }
56 HgPathError::ConsecutiveSlashes {
57 HgPathError::ConsecutiveSlashes {
57 bytes,
58 bytes,
58 second_slash_index: pos,
59 second_slash_index: pos,
59 } => write!(
60 } => write!(
60 f,
61 f,
61 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
62 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
62 bytes, pos
63 bytes, pos
63 ),
64 ),
64 HgPathError::ContainsNullByte {
65 HgPathError::ContainsNullByte {
65 bytes,
66 bytes,
66 null_byte_index: pos,
67 null_byte_index: pos,
67 } => write!(
68 } => write!(
68 f,
69 f,
69 "Invalid HgPath '{:?}': contains null byte at pos {}.",
70 "Invalid HgPath '{:?}': contains null byte at pos {}.",
70 bytes, pos
71 bytes, pos
71 ),
72 ),
72 HgPathError::DecodeError(bytes) => write!(
73 HgPathError::DecodeError(bytes) => write!(
73 f,
74 f,
74 "Invalid HgPath '{:?}': could not be decoded.",
75 "Invalid HgPath '{:?}': could not be decoded.",
75 bytes
76 bytes
76 ),
77 ),
77 HgPathError::EndsWithSlash(path) => {
78 HgPathError::EndsWithSlash(path) => {
78 write!(f, "Audit failed for '{}': ends with a slash.", path)
79 write!(f, "Audit failed for '{}': ends with a slash.", path)
79 }
80 }
80 HgPathError::ContainsIllegalComponent(path) => write!(
81 HgPathError::ContainsIllegalComponent(path) => write!(
81 f,
82 f,
82 "Audit failed for '{}': contains an illegal component.",
83 "Audit failed for '{}': contains an illegal component.",
83 path
84 path
84 ),
85 ),
85 HgPathError::InsideDotHg(path) => write!(
86 HgPathError::InsideDotHg(path) => write!(
86 f,
87 f,
87 "Audit failed for '{}': is inside the '.hg' folder.",
88 "Audit failed for '{}': is inside the '.hg' folder.",
88 path
89 path
89 ),
90 ),
90 HgPathError::IsInsideNestedRepo {
91 HgPathError::IsInsideNestedRepo {
91 path,
92 path,
92 nested_repo: nested,
93 nested_repo: nested,
93 } => {
94 } => {
94 write!(f,
95 write!(f,
95 "Audit failed for '{}': is inside a nested repository '{}'.",
96 "Audit failed for '{}': is inside a nested repository '{}'.",
96 path, nested
97 path, nested
97 )
98 )
98 }
99 }
99 HgPathError::TraversesSymbolicLink { path, symlink } => write!(
100 HgPathError::TraversesSymbolicLink { path, symlink } => write!(
100 f,
101 f,
101 "Audit failed for '{}': traverses symbolic link '{}'.",
102 "Audit failed for '{}': traverses symbolic link '{}'.",
102 path, symlink
103 path, symlink
103 ),
104 ),
104 HgPathError::NotFsCompliant(path) => write!(
105 HgPathError::NotFsCompliant(path) => write!(
105 f,
106 f,
106 "Audit failed for '{}': cannot be turned into a \
107 "Audit failed for '{}': cannot be turned into a \
107 filesystem path.",
108 filesystem path.",
108 path
109 path
109 ),
110 ),
110 HgPathError::NotUnderRoot { path, root } => write!(
111 HgPathError::NotUnderRoot { path, root } => write!(
111 f,
112 f,
112 "Audit failed for '{}': not under root {}.",
113 "Audit failed for '{}': not under root {}.",
113 path.display(),
114 path.display(),
114 root.display()
115 root.display()
115 ),
116 ),
116 }
117 }
117 }
118 }
118 }
119 }
119
120
120 impl From<HgPathError> for std::io::Error {
121 impl From<HgPathError> for std::io::Error {
121 fn from(e: HgPathError) -> Self {
122 fn from(e: HgPathError) -> Self {
122 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
123 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
123 }
124 }
124 }
125 }
125
126
126 /// This is a repository-relative path (or canonical path):
127 /// This is a repository-relative path (or canonical path):
127 /// - no null characters
128 /// - no null characters
128 /// - `/` separates directories
129 /// - `/` separates directories
129 /// - no consecutive slashes
130 /// - no consecutive slashes
130 /// - no leading slash,
131 /// - no leading slash,
131 /// - no `.` nor `..` of special meaning
132 /// - no `.` nor `..` of special meaning
132 /// - stored in repository and shared across platforms
133 /// - stored in repository and shared across platforms
133 ///
134 ///
134 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
135 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
135 /// in its lifetime for performance reasons and to ease ergonomics. It is
136 /// in its lifetime for performance reasons and to ease ergonomics. It is
136 /// however checked using the `check_state` method before any file-system
137 /// however checked using the `check_state` method before any file-system
137 /// operation.
138 /// operation.
138 ///
139 ///
139 /// This allows us to be encoding-transparent as much as possible, until really
140 /// This allows us to be encoding-transparent as much as possible, until really
140 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
141 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
141 /// or `Path`) whenever more complex operations are needed:
142 /// or `Path`) whenever more complex operations are needed:
142 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
143 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
143 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
144 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
144 /// character encoding will be determined on a per-repository basis.
145 /// character encoding will be determined on a per-repository basis.
145 //
146 //
146 // FIXME: (adapted from a comment in the stdlib)
147 // FIXME: (adapted from a comment in the stdlib)
147 // `HgPath::new()` current implementation relies on `Slice` being
148 // `HgPath::new()` current implementation relies on `Slice` being
148 // layout-compatible with `[u8]`.
149 // layout-compatible with `[u8]`.
149 // When attribute privacy is implemented, `Slice` should be annotated as
150 // When attribute privacy is implemented, `Slice` should be annotated as
150 // `#[repr(transparent)]`.
151 // `#[repr(transparent)]`.
151 // Anyway, `Slice` representation and layout are considered implementation
152 // Anyway, `Slice` representation and layout are considered implementation
152 // detail, are not documented and must not be relied upon.
153 // detail, are not documented and must not be relied upon.
153 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
154 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
154 pub struct HgPath {
155 pub struct HgPath {
155 inner: [u8],
156 inner: [u8],
156 }
157 }
157
158
158 impl HgPath {
159 impl HgPath {
159 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
160 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
160 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
161 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
161 }
162 }
162 pub fn is_empty(&self) -> bool {
163 pub fn is_empty(&self) -> bool {
163 self.inner.is_empty()
164 self.inner.is_empty()
164 }
165 }
165 pub fn len(&self) -> usize {
166 pub fn len(&self) -> usize {
166 self.inner.len()
167 self.inner.len()
167 }
168 }
168 fn to_hg_path_buf(&self) -> HgPathBuf {
169 fn to_hg_path_buf(&self) -> HgPathBuf {
169 HgPathBuf {
170 HgPathBuf {
170 inner: self.inner.to_owned(),
171 inner: self.inner.to_owned(),
171 }
172 }
172 }
173 }
173 pub fn bytes(&self) -> std::slice::Iter<u8> {
174 pub fn bytes(&self) -> std::slice::Iter<u8> {
174 self.inner.iter()
175 self.inner.iter()
175 }
176 }
176 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
177 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
177 HgPathBuf::from(self.inner.to_ascii_uppercase())
178 HgPathBuf::from(self.inner.to_ascii_uppercase())
178 }
179 }
179 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
180 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
180 HgPathBuf::from(self.inner.to_ascii_lowercase())
181 HgPathBuf::from(self.inner.to_ascii_lowercase())
181 }
182 }
182 pub fn as_bytes(&self) -> &[u8] {
183 pub fn as_bytes(&self) -> &[u8] {
183 &self.inner
184 &self.inner
184 }
185 }
185 pub fn contains(&self, other: u8) -> bool {
186 pub fn contains(&self, other: u8) -> bool {
186 self.inner.contains(&other)
187 self.inner.contains(&other)
187 }
188 }
188 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
189 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
189 self.inner.starts_with(needle.as_ref().as_bytes())
190 self.inner.starts_with(needle.as_ref().as_bytes())
190 }
191 }
191 pub fn trim_trailing_slash(&self) -> &Self {
192 pub fn trim_trailing_slash(&self) -> &Self {
192 Self::new(if self.inner.last() == Some(&b'/') {
193 Self::new(if self.inner.last() == Some(&b'/') {
193 &self.inner[..self.inner.len() - 1]
194 &self.inner[..self.inner.len() - 1]
194 } else {
195 } else {
195 &self.inner[..]
196 &self.inner[..]
196 })
197 })
197 }
198 }
198 /// Returns a tuple of slices `(base, filename)` resulting from the split
199 /// Returns a tuple of slices `(base, filename)` resulting from the split
199 /// at the rightmost `/`, if any.
200 /// at the rightmost `/`, if any.
200 ///
201 ///
201 /// # Examples:
202 /// # Examples:
202 ///
203 ///
203 /// ```
204 /// ```
204 /// use hg::utils::hg_path::HgPath;
205 /// use hg::utils::hg_path::HgPath;
205 ///
206 ///
206 /// let path = HgPath::new(b"cool/hg/path").split_filename();
207 /// let path = HgPath::new(b"cool/hg/path").split_filename();
207 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
208 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
208 ///
209 ///
209 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
210 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
210 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
211 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
211 /// ```
212 /// ```
212 pub fn split_filename(&self) -> (&Self, &Self) {
213 pub fn split_filename(&self) -> (&Self, &Self) {
213 match &self.inner.iter().rposition(|c| *c == b'/') {
214 match &self.inner.iter().rposition(|c| *c == b'/') {
214 None => (HgPath::new(""), &self),
215 None => (HgPath::new(""), &self),
215 Some(size) => (
216 Some(size) => (
216 HgPath::new(&self.inner[..*size]),
217 HgPath::new(&self.inner[..*size]),
217 HgPath::new(&self.inner[*size + 1..]),
218 HgPath::new(&self.inner[*size + 1..]),
218 ),
219 ),
219 }
220 }
220 }
221 }
221 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
222 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
222 let mut inner = self.inner.to_owned();
223 let mut inner = self.inner.to_owned();
223 if !inner.is_empty() && inner.last() != Some(&b'/') {
224 if !inner.is_empty() && inner.last() != Some(&b'/') {
224 inner.push(b'/');
225 inner.push(b'/');
225 }
226 }
226 inner.extend(other.as_ref().bytes());
227 inner.extend(other.as_ref().bytes());
227 HgPathBuf::from_bytes(&inner)
228 HgPathBuf::from_bytes(&inner)
228 }
229 }
229
230
230 pub fn components(&self) -> impl Iterator<Item = &HgPath> {
231 pub fn components(&self) -> impl Iterator<Item = &HgPath> {
231 self.inner.split(|&byte| byte == b'/').map(HgPath::new)
232 self.inner.split(|&byte| byte == b'/').map(HgPath::new)
232 }
233 }
233
234
234 pub fn parent(&self) -> &Self {
235 pub fn parent(&self) -> &Self {
235 let inner = self.as_bytes();
236 let inner = self.as_bytes();
236 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
237 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
237 Some(pos) => &inner[..pos],
238 Some(pos) => &inner[..pos],
238 None => &[],
239 None => &[],
239 })
240 })
240 }
241 }
241 /// Given a base directory, returns the slice of `self` relative to the
242 /// Given a base directory, returns the slice of `self` relative to the
242 /// base directory. If `base` is not a directory (does not end with a
243 /// base directory. If `base` is not a directory (does not end with a
243 /// `b'/'`), returns `None`.
244 /// `b'/'`), returns `None`.
244 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
245 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
245 let base = base.as_ref();
246 let base = base.as_ref();
246 if base.is_empty() {
247 if base.is_empty() {
247 return Some(self);
248 return Some(self);
248 }
249 }
249 let is_dir = base.as_bytes().ends_with(b"/");
250 let is_dir = base.as_bytes().ends_with(b"/");
250 if is_dir && self.starts_with(base) {
251 if is_dir && self.starts_with(base) {
251 Some(Self::new(&self.inner[base.len()..]))
252 Some(Self::new(&self.inner[base.len()..]))
252 } else {
253 } else {
253 None
254 None
254 }
255 }
255 }
256 }
256
257
257 #[cfg(windows)]
258 #[cfg(windows)]
258 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
259 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
259 ///
260 ///
260 /// Split a pathname into drive/UNC sharepoint and relative path
261 /// Split a pathname into drive/UNC sharepoint and relative path
261 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
262 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
262 /// be empty.
263 /// be empty.
263 ///
264 ///
264 /// If you assign
265 /// If you assign
265 /// result = split_drive(p)
266 /// result = split_drive(p)
266 /// It is always true that:
267 /// It is always true that:
267 /// result[0] + result[1] == p
268 /// result[0] + result[1] == p
268 ///
269 ///
269 /// If the path contained a drive letter, drive_or_unc will contain
270 /// If the path contained a drive letter, drive_or_unc will contain
270 /// everything up to and including the colon.
271 /// everything up to and including the colon.
271 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
272 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
272 ///
273 ///
273 /// If the path contained a UNC path, the drive_or_unc will contain the
274 /// If the path contained a UNC path, the drive_or_unc will contain the
274 /// host name and share up to but not including the fourth directory
275 /// host name and share up to but not including the fourth directory
275 /// separator character.
276 /// separator character.
276 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
277 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
277 /// "/dir")
278 /// "/dir")
278 ///
279 ///
279 /// Paths cannot contain both a drive letter and a UNC path.
280 /// Paths cannot contain both a drive letter and a UNC path.
280 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
281 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
281 let bytes = self.as_bytes();
282 let bytes = self.as_bytes();
282 let is_sep = |b| std::path::is_separator(b as char);
283 let is_sep = |b| std::path::is_separator(b as char);
283
284
284 if self.len() < 2 {
285 if self.len() < 2 {
285 (HgPath::new(b""), &self)
286 (HgPath::new(b""), &self)
286 } else if is_sep(bytes[0])
287 } else if is_sep(bytes[0])
287 && is_sep(bytes[1])
288 && is_sep(bytes[1])
288 && (self.len() == 2 || !is_sep(bytes[2]))
289 && (self.len() == 2 || !is_sep(bytes[2]))
289 {
290 {
290 // Is a UNC path:
291 // Is a UNC path:
291 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
292 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
292 // \\machine\mountpoint\directory\etc\...
293 // \\machine\mountpoint\directory\etc\...
293 // directory ^^^^^^^^^^^^^^^
294 // directory ^^^^^^^^^^^^^^^
294
295
295 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
296 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
296 let mountpoint_start_index = if let Some(i) = machine_end_index {
297 let mountpoint_start_index = if let Some(i) = machine_end_index {
297 i + 2
298 i + 2
298 } else {
299 } else {
299 return (HgPath::new(b""), &self);
300 return (HgPath::new(b""), &self);
300 };
301 };
301
302
302 match bytes[mountpoint_start_index + 1..]
303 match bytes[mountpoint_start_index + 1..]
303 .iter()
304 .iter()
304 .position(|b| is_sep(*b))
305 .position(|b| is_sep(*b))
305 {
306 {
306 // A UNC path can't have two slashes in a row
307 // A UNC path can't have two slashes in a row
307 // (after the initial two)
308 // (after the initial two)
308 Some(0) => (HgPath::new(b""), &self),
309 Some(0) => (HgPath::new(b""), &self),
309 Some(i) => {
310 Some(i) => {
310 let (a, b) =
311 let (a, b) =
311 bytes.split_at(mountpoint_start_index + 1 + i);
312 bytes.split_at(mountpoint_start_index + 1 + i);
312 (HgPath::new(a), HgPath::new(b))
313 (HgPath::new(a), HgPath::new(b))
313 }
314 }
314 None => (&self, HgPath::new(b"")),
315 None => (&self, HgPath::new(b"")),
315 }
316 }
316 } else if bytes[1] == b':' {
317 } else if bytes[1] == b':' {
317 // Drive path c:\directory
318 // Drive path c:\directory
318 let (a, b) = bytes.split_at(2);
319 let (a, b) = bytes.split_at(2);
319 (HgPath::new(a), HgPath::new(b))
320 (HgPath::new(a), HgPath::new(b))
320 } else {
321 } else {
321 (HgPath::new(b""), &self)
322 (HgPath::new(b""), &self)
322 }
323 }
323 }
324 }
324
325
325 #[cfg(unix)]
326 #[cfg(unix)]
326 /// Split a pathname into drive and path. On Posix, drive is always empty.
327 /// Split a pathname into drive and path. On Posix, drive is always empty.
327 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
328 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
328 (HgPath::new(b""), &self)
329 (HgPath::new(b""), &self)
329 }
330 }
330
331
331 /// Checks for errors in the path, short-circuiting at the first one.
332 /// Checks for errors in the path, short-circuiting at the first one.
332 /// This generates fine-grained errors useful for debugging.
333 /// This generates fine-grained errors useful for debugging.
333 /// To simply check if the path is valid during tests, use `is_valid`.
334 /// To simply check if the path is valid during tests, use `is_valid`.
334 pub fn check_state(&self) -> Result<(), HgPathError> {
335 pub fn check_state(&self) -> Result<(), HgPathError> {
335 if self.is_empty() {
336 if self.is_empty() {
336 return Ok(());
337 return Ok(());
337 }
338 }
338 let bytes = self.as_bytes();
339 let bytes = self.as_bytes();
339 let mut previous_byte = None;
340 let mut previous_byte = None;
340
341
341 if bytes[0] == b'/' {
342 if bytes[0] == b'/' {
342 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
343 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
343 }
344 }
344 for (index, byte) in bytes.iter().enumerate() {
345 for (index, byte) in bytes.iter().enumerate() {
345 match byte {
346 match byte {
346 0 => {
347 0 => {
347 return Err(HgPathError::ContainsNullByte {
348 return Err(HgPathError::ContainsNullByte {
348 bytes: bytes.to_vec(),
349 bytes: bytes.to_vec(),
349 null_byte_index: index,
350 null_byte_index: index,
350 })
351 })
351 }
352 }
352 b'/' => {
353 b'/' => {
353 if previous_byte.is_some() && previous_byte == Some(b'/') {
354 if previous_byte.is_some() && previous_byte == Some(b'/') {
354 return Err(HgPathError::ConsecutiveSlashes {
355 return Err(HgPathError::ConsecutiveSlashes {
355 bytes: bytes.to_vec(),
356 bytes: bytes.to_vec(),
356 second_slash_index: index,
357 second_slash_index: index,
357 });
358 });
358 }
359 }
359 }
360 }
360 _ => (),
361 _ => (),
361 };
362 };
362 previous_byte = Some(*byte);
363 previous_byte = Some(*byte);
363 }
364 }
364 Ok(())
365 Ok(())
365 }
366 }
366
367
367 #[cfg(test)]
368 #[cfg(test)]
368 /// Only usable during tests to force developers to handle invalid states
369 /// Only usable during tests to force developers to handle invalid states
369 fn is_valid(&self) -> bool {
370 fn is_valid(&self) -> bool {
370 self.check_state().is_ok()
371 self.check_state().is_ok()
371 }
372 }
372 }
373 }
373
374
374 impl fmt::Debug for HgPath {
375 impl fmt::Debug for HgPath {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
377 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
377 }
378 }
378 }
379 }
379
380
380 impl fmt::Display for HgPath {
381 impl fmt::Display for HgPath {
381 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382 write!(f, "{}", String::from_utf8_lossy(&self.inner))
383 write!(f, "{}", String::from_utf8_lossy(&self.inner))
383 }
384 }
384 }
385 }
385
386
386 #[derive(
387 #[derive(
387 Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From,
388 Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From,
388 )]
389 )]
389 pub struct HgPathBuf {
390 pub struct HgPathBuf {
390 inner: Vec<u8>,
391 inner: Vec<u8>,
391 }
392 }
392
393
393 impl HgPathBuf {
394 impl HgPathBuf {
394 pub fn new() -> Self {
395 pub fn new() -> Self {
395 Default::default()
396 Default::default()
396 }
397 }
397 pub fn push(&mut self, byte: u8) {
398 pub fn push(&mut self, byte: u8) {
398 self.inner.push(byte);
399 self.inner.push(byte);
399 }
400 }
400 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
401 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
401 HgPath::new(s).to_owned()
402 HgPath::new(s).to_owned()
402 }
403 }
403 pub fn into_vec(self) -> Vec<u8> {
404 pub fn into_vec(self) -> Vec<u8> {
404 self.inner
405 self.inner
405 }
406 }
406 }
407 }
407
408
408 impl fmt::Debug for HgPathBuf {
409 impl fmt::Debug for HgPathBuf {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
411 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
411 }
412 }
412 }
413 }
413
414
414 impl fmt::Display for HgPathBuf {
415 impl fmt::Display for HgPathBuf {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 write!(f, "{}", String::from_utf8_lossy(&self.inner))
417 write!(f, "{}", String::from_utf8_lossy(&self.inner))
417 }
418 }
418 }
419 }
419
420
420 impl Deref for HgPathBuf {
421 impl Deref for HgPathBuf {
421 type Target = HgPath;
422 type Target = HgPath;
422
423
423 #[inline]
424 #[inline]
424 fn deref(&self) -> &HgPath {
425 fn deref(&self) -> &HgPath {
425 &HgPath::new(&self.inner)
426 &HgPath::new(&self.inner)
426 }
427 }
427 }
428 }
428
429
429 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
430 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
430 fn from(s: &T) -> HgPathBuf {
431 fn from(s: &T) -> HgPathBuf {
431 s.as_ref().to_owned()
432 s.as_ref().to_owned()
432 }
433 }
433 }
434 }
434
435
435 impl Into<Vec<u8>> for HgPathBuf {
436 impl Into<Vec<u8>> for HgPathBuf {
436 fn into(self) -> Vec<u8> {
437 fn into(self) -> Vec<u8> {
437 self.inner
438 self.inner
438 }
439 }
439 }
440 }
440
441
441 impl Borrow<HgPath> for HgPathBuf {
442 impl Borrow<HgPath> for HgPathBuf {
442 fn borrow(&self) -> &HgPath {
443 fn borrow(&self) -> &HgPath {
443 &HgPath::new(self.as_bytes())
444 &HgPath::new(self.as_bytes())
444 }
445 }
445 }
446 }
446
447
447 impl ToOwned for HgPath {
448 impl ToOwned for HgPath {
448 type Owned = HgPathBuf;
449 type Owned = HgPathBuf;
449
450
450 fn to_owned(&self) -> HgPathBuf {
451 fn to_owned(&self) -> HgPathBuf {
451 self.to_hg_path_buf()
452 self.to_hg_path_buf()
452 }
453 }
453 }
454 }
454
455
455 impl AsRef<HgPath> for HgPath {
456 impl AsRef<HgPath> for HgPath {
456 fn as_ref(&self) -> &HgPath {
457 fn as_ref(&self) -> &HgPath {
457 self
458 self
458 }
459 }
459 }
460 }
460
461
461 impl AsRef<HgPath> for HgPathBuf {
462 impl AsRef<HgPath> for HgPathBuf {
462 fn as_ref(&self) -> &HgPath {
463 fn as_ref(&self) -> &HgPath {
463 self
464 self
464 }
465 }
465 }
466 }
466
467
467 impl Extend<u8> for HgPathBuf {
468 impl Extend<u8> for HgPathBuf {
468 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
469 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
469 self.inner.extend(iter);
470 self.inner.extend(iter);
470 }
471 }
471 }
472 }
472
473
473 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
474 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
474 /// implemented, these conversion utils will have to work differently depending
475 /// implemented, these conversion utils will have to work differently depending
475 /// on the repository encoding: either `UTF-8` or `MBCS`.
476 /// on the repository encoding: either `UTF-8` or `MBCS`.
476
477
477 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
478 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
478 hg_path: P,
479 hg_path: P,
479 ) -> Result<OsString, HgPathError> {
480 ) -> Result<OsString, HgPathError> {
480 hg_path.as_ref().check_state()?;
481 hg_path.as_ref().check_state()?;
481 let os_str;
482 let os_str;
482 #[cfg(unix)]
483 #[cfg(unix)]
483 {
484 {
484 use std::os::unix::ffi::OsStrExt;
485 use std::os::unix::ffi::OsStrExt;
485 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
486 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
486 }
487 }
487 // TODO Handle other platforms
488 // TODO Handle other platforms
488 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
489 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
489 Ok(os_str.to_os_string())
490 Ok(os_str.to_os_string())
490 }
491 }
491
492
492 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
493 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
493 hg_path: P,
494 hg_path: P,
494 ) -> Result<PathBuf, HgPathError> {
495 ) -> Result<PathBuf, HgPathError> {
495 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
496 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
496 }
497 }
497
498
498 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
499 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
499 os_string: S,
500 os_string: S,
500 ) -> Result<HgPathBuf, HgPathError> {
501 ) -> Result<HgPathBuf, HgPathError> {
501 let buf;
502 let buf;
502 #[cfg(unix)]
503 #[cfg(unix)]
503 {
504 {
504 use std::os::unix::ffi::OsStrExt;
505 use std::os::unix::ffi::OsStrExt;
505 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
506 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
506 }
507 }
507 // TODO Handle other platforms
508 // TODO Handle other platforms
508 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
509 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
509
510
510 buf.check_state()?;
511 buf.check_state()?;
511 Ok(buf)
512 Ok(buf)
512 }
513 }
513
514
514 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
515 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
515 path: P,
516 path: P,
516 ) -> Result<HgPathBuf, HgPathError> {
517 ) -> Result<HgPathBuf, HgPathError> {
517 let buf;
518 let buf;
518 let os_str = path.as_ref().as_os_str();
519 let os_str = path.as_ref().as_os_str();
519 #[cfg(unix)]
520 #[cfg(unix)]
520 {
521 {
521 use std::os::unix::ffi::OsStrExt;
522 use std::os::unix::ffi::OsStrExt;
522 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
523 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
523 }
524 }
524 // TODO Handle other platforms
525 // TODO Handle other platforms
525 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
526 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
526
527
527 buf.check_state()?;
528 buf.check_state()?;
528 Ok(buf)
529 Ok(buf)
529 }
530 }
530
531
531 impl TryFrom<PathBuf> for HgPathBuf {
532 impl TryFrom<PathBuf> for HgPathBuf {
532 type Error = HgPathError;
533 type Error = HgPathError;
533 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
534 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
534 path_to_hg_path_buf(path)
535 path_to_hg_path_buf(path)
535 }
536 }
536 }
537 }
537
538
539 impl From<HgPathBuf> for Cow<'_, HgPath> {
540 fn from(path: HgPathBuf) -> Self {
541 Cow::Owned(path)
542 }
543 }
544
545 impl<'a> From<&'a HgPath> for Cow<'a, HgPath> {
546 fn from(path: &'a HgPath) -> Self {
547 Cow::Borrowed(path)
548 }
549 }
550
551 impl<'a> From<&'a HgPathBuf> for Cow<'a, HgPath> {
552 fn from(path: &'a HgPathBuf) -> Self {
553 Cow::Borrowed(&**path)
554 }
555 }
556
538 #[cfg(test)]
557 #[cfg(test)]
539 mod tests {
558 mod tests {
540 use super::*;
559 use super::*;
541 use pretty_assertions::assert_eq;
560 use pretty_assertions::assert_eq;
542
561
543 #[test]
562 #[test]
544 fn test_path_states() {
563 fn test_path_states() {
545 assert_eq!(
564 assert_eq!(
546 Err(HgPathError::LeadingSlash(b"/".to_vec())),
565 Err(HgPathError::LeadingSlash(b"/".to_vec())),
547 HgPath::new(b"/").check_state()
566 HgPath::new(b"/").check_state()
548 );
567 );
549 assert_eq!(
568 assert_eq!(
550 Err(HgPathError::ConsecutiveSlashes {
569 Err(HgPathError::ConsecutiveSlashes {
551 bytes: b"a/b//c".to_vec(),
570 bytes: b"a/b//c".to_vec(),
552 second_slash_index: 4
571 second_slash_index: 4
553 }),
572 }),
554 HgPath::new(b"a/b//c").check_state()
573 HgPath::new(b"a/b//c").check_state()
555 );
574 );
556 assert_eq!(
575 assert_eq!(
557 Err(HgPathError::ContainsNullByte {
576 Err(HgPathError::ContainsNullByte {
558 bytes: b"a/b/\0c".to_vec(),
577 bytes: b"a/b/\0c".to_vec(),
559 null_byte_index: 4
578 null_byte_index: 4
560 }),
579 }),
561 HgPath::new(b"a/b/\0c").check_state()
580 HgPath::new(b"a/b/\0c").check_state()
562 );
581 );
563 // TODO test HgPathError::DecodeError for the Windows implementation.
582 // TODO test HgPathError::DecodeError for the Windows implementation.
564 assert_eq!(true, HgPath::new(b"").is_valid());
583 assert_eq!(true, HgPath::new(b"").is_valid());
565 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
584 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
566 // Backslashes in paths are not significant, but allowed
585 // Backslashes in paths are not significant, but allowed
567 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
586 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
568 // Dots in paths are not significant, but allowed
587 // Dots in paths are not significant, but allowed
569 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
588 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
570 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
589 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
571 }
590 }
572
591
573 #[test]
592 #[test]
574 fn test_iter() {
593 fn test_iter() {
575 let path = HgPath::new(b"a");
594 let path = HgPath::new(b"a");
576 let mut iter = path.bytes();
595 let mut iter = path.bytes();
577 assert_eq!(Some(&b'a'), iter.next());
596 assert_eq!(Some(&b'a'), iter.next());
578 assert_eq!(None, iter.next_back());
597 assert_eq!(None, iter.next_back());
579 assert_eq!(None, iter.next());
598 assert_eq!(None, iter.next());
580
599
581 let path = HgPath::new(b"a");
600 let path = HgPath::new(b"a");
582 let mut iter = path.bytes();
601 let mut iter = path.bytes();
583 assert_eq!(Some(&b'a'), iter.next_back());
602 assert_eq!(Some(&b'a'), iter.next_back());
584 assert_eq!(None, iter.next_back());
603 assert_eq!(None, iter.next_back());
585 assert_eq!(None, iter.next());
604 assert_eq!(None, iter.next());
586
605
587 let path = HgPath::new(b"abc");
606 let path = HgPath::new(b"abc");
588 let mut iter = path.bytes();
607 let mut iter = path.bytes();
589 assert_eq!(Some(&b'a'), iter.next());
608 assert_eq!(Some(&b'a'), iter.next());
590 assert_eq!(Some(&b'c'), iter.next_back());
609 assert_eq!(Some(&b'c'), iter.next_back());
591 assert_eq!(Some(&b'b'), iter.next_back());
610 assert_eq!(Some(&b'b'), iter.next_back());
592 assert_eq!(None, iter.next_back());
611 assert_eq!(None, iter.next_back());
593 assert_eq!(None, iter.next());
612 assert_eq!(None, iter.next());
594
613
595 let path = HgPath::new(b"abc");
614 let path = HgPath::new(b"abc");
596 let mut iter = path.bytes();
615 let mut iter = path.bytes();
597 assert_eq!(Some(&b'a'), iter.next());
616 assert_eq!(Some(&b'a'), iter.next());
598 assert_eq!(Some(&b'b'), iter.next());
617 assert_eq!(Some(&b'b'), iter.next());
599 assert_eq!(Some(&b'c'), iter.next());
618 assert_eq!(Some(&b'c'), iter.next());
600 assert_eq!(None, iter.next_back());
619 assert_eq!(None, iter.next_back());
601 assert_eq!(None, iter.next());
620 assert_eq!(None, iter.next());
602
621
603 let path = HgPath::new(b"abc");
622 let path = HgPath::new(b"abc");
604 let iter = path.bytes();
623 let iter = path.bytes();
605 let mut vec = Vec::new();
624 let mut vec = Vec::new();
606 vec.extend(iter);
625 vec.extend(iter);
607 assert_eq!(vec![b'a', b'b', b'c'], vec);
626 assert_eq!(vec![b'a', b'b', b'c'], vec);
608
627
609 let path = HgPath::new(b"abc");
628 let path = HgPath::new(b"abc");
610 let mut iter = path.bytes();
629 let mut iter = path.bytes();
611 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
630 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
612
631
613 let path = HgPath::new(b"abc");
632 let path = HgPath::new(b"abc");
614 let mut iter = path.bytes();
633 let mut iter = path.bytes();
615 assert_eq!(None, iter.rposition(|c| *c == b'd'));
634 assert_eq!(None, iter.rposition(|c| *c == b'd'));
616 }
635 }
617
636
618 #[test]
637 #[test]
619 fn test_join() {
638 fn test_join() {
620 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
639 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
621 assert_eq!(b"a/b", path.as_bytes());
640 assert_eq!(b"a/b", path.as_bytes());
622
641
623 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
642 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
624 assert_eq!(b"a/b/c", path.as_bytes());
643 assert_eq!(b"a/b/c", path.as_bytes());
625
644
626 // No leading slash if empty before join
645 // No leading slash if empty before join
627 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
646 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
628 assert_eq!(b"b/c", path.as_bytes());
647 assert_eq!(b"b/c", path.as_bytes());
629
648
630 // The leading slash is an invalid representation of an `HgPath`, but
649 // The leading slash is an invalid representation of an `HgPath`, but
631 // it can happen. This creates another invalid representation of
650 // it can happen. This creates another invalid representation of
632 // consecutive bytes.
651 // consecutive bytes.
633 // TODO What should be done in this case? Should we silently remove
652 // TODO What should be done in this case? Should we silently remove
634 // the extra slash? Should we change the signature to a problematic
653 // the extra slash? Should we change the signature to a problematic
635 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
654 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
636 // let the error happen upon filesystem interaction?
655 // let the error happen upon filesystem interaction?
637 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
656 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
638 assert_eq!(b"a//b", path.as_bytes());
657 assert_eq!(b"a//b", path.as_bytes());
639 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
658 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
640 assert_eq!(b"a//b", path.as_bytes());
659 assert_eq!(b"a//b", path.as_bytes());
641 }
660 }
642
661
643 #[test]
662 #[test]
644 fn test_relative_to() {
663 fn test_relative_to() {
645 let path = HgPath::new(b"");
664 let path = HgPath::new(b"");
646 let base = HgPath::new(b"");
665 let base = HgPath::new(b"");
647 assert_eq!(Some(path), path.relative_to(base));
666 assert_eq!(Some(path), path.relative_to(base));
648
667
649 let path = HgPath::new(b"path");
668 let path = HgPath::new(b"path");
650 let base = HgPath::new(b"");
669 let base = HgPath::new(b"");
651 assert_eq!(Some(path), path.relative_to(base));
670 assert_eq!(Some(path), path.relative_to(base));
652
671
653 let path = HgPath::new(b"a");
672 let path = HgPath::new(b"a");
654 let base = HgPath::new(b"b");
673 let base = HgPath::new(b"b");
655 assert_eq!(None, path.relative_to(base));
674 assert_eq!(None, path.relative_to(base));
656
675
657 let path = HgPath::new(b"a/b");
676 let path = HgPath::new(b"a/b");
658 let base = HgPath::new(b"a");
677 let base = HgPath::new(b"a");
659 assert_eq!(None, path.relative_to(base));
678 assert_eq!(None, path.relative_to(base));
660
679
661 let path = HgPath::new(b"a/b");
680 let path = HgPath::new(b"a/b");
662 let base = HgPath::new(b"a/");
681 let base = HgPath::new(b"a/");
663 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
682 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
664
683
665 let path = HgPath::new(b"nested/path/to/b");
684 let path = HgPath::new(b"nested/path/to/b");
666 let base = HgPath::new(b"nested/path/");
685 let base = HgPath::new(b"nested/path/");
667 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
686 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
668
687
669 let path = HgPath::new(b"ends/with/dir/");
688 let path = HgPath::new(b"ends/with/dir/");
670 let base = HgPath::new(b"ends/");
689 let base = HgPath::new(b"ends/");
671 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
690 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
672 }
691 }
673
692
674 #[test]
693 #[test]
675 #[cfg(unix)]
694 #[cfg(unix)]
676 fn test_split_drive() {
695 fn test_split_drive() {
677 // Taken from the Python stdlib's tests
696 // Taken from the Python stdlib's tests
678 assert_eq!(
697 assert_eq!(
679 HgPath::new(br"/foo/bar").split_drive(),
698 HgPath::new(br"/foo/bar").split_drive(),
680 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
699 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
681 );
700 );
682 assert_eq!(
701 assert_eq!(
683 HgPath::new(br"foo:bar").split_drive(),
702 HgPath::new(br"foo:bar").split_drive(),
684 (HgPath::new(b""), HgPath::new(br"foo:bar"))
703 (HgPath::new(b""), HgPath::new(br"foo:bar"))
685 );
704 );
686 assert_eq!(
705 assert_eq!(
687 HgPath::new(br":foo:bar").split_drive(),
706 HgPath::new(br":foo:bar").split_drive(),
688 (HgPath::new(b""), HgPath::new(br":foo:bar"))
707 (HgPath::new(b""), HgPath::new(br":foo:bar"))
689 );
708 );
690 // Also try NT paths; should not split them
709 // Also try NT paths; should not split them
691 assert_eq!(
710 assert_eq!(
692 HgPath::new(br"c:\foo\bar").split_drive(),
711 HgPath::new(br"c:\foo\bar").split_drive(),
693 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
712 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
694 );
713 );
695 assert_eq!(
714 assert_eq!(
696 HgPath::new(b"c:/foo/bar").split_drive(),
715 HgPath::new(b"c:/foo/bar").split_drive(),
697 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
716 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
698 );
717 );
699 assert_eq!(
718 assert_eq!(
700 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
719 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
701 (
720 (
702 HgPath::new(b""),
721 HgPath::new(b""),
703 HgPath::new(br"\\conky\mountpoint\foo\bar")
722 HgPath::new(br"\\conky\mountpoint\foo\bar")
704 )
723 )
705 );
724 );
706 }
725 }
707
726
708 #[test]
727 #[test]
709 #[cfg(windows)]
728 #[cfg(windows)]
710 fn test_split_drive() {
729 fn test_split_drive() {
711 assert_eq!(
730 assert_eq!(
712 HgPath::new(br"c:\foo\bar").split_drive(),
731 HgPath::new(br"c:\foo\bar").split_drive(),
713 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
732 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
714 );
733 );
715 assert_eq!(
734 assert_eq!(
716 HgPath::new(b"c:/foo/bar").split_drive(),
735 HgPath::new(b"c:/foo/bar").split_drive(),
717 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
736 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
718 );
737 );
719 assert_eq!(
738 assert_eq!(
720 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
739 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
721 (
740 (
722 HgPath::new(br"\\conky\mountpoint"),
741 HgPath::new(br"\\conky\mountpoint"),
723 HgPath::new(br"\foo\bar")
742 HgPath::new(br"\foo\bar")
724 )
743 )
725 );
744 );
726 assert_eq!(
745 assert_eq!(
727 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
746 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
728 (
747 (
729 HgPath::new(br"//conky/mountpoint"),
748 HgPath::new(br"//conky/mountpoint"),
730 HgPath::new(br"/foo/bar")
749 HgPath::new(br"/foo/bar")
731 )
750 )
732 );
751 );
733 assert_eq!(
752 assert_eq!(
734 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
753 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
735 (
754 (
736 HgPath::new(br""),
755 HgPath::new(br""),
737 HgPath::new(br"\\\conky\mountpoint\foo\bar")
756 HgPath::new(br"\\\conky\mountpoint\foo\bar")
738 )
757 )
739 );
758 );
740 assert_eq!(
759 assert_eq!(
741 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
760 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
742 (
761 (
743 HgPath::new(br""),
762 HgPath::new(br""),
744 HgPath::new(br"///conky/mountpoint/foo/bar")
763 HgPath::new(br"///conky/mountpoint/foo/bar")
745 )
764 )
746 );
765 );
747 assert_eq!(
766 assert_eq!(
748 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
767 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
749 (
768 (
750 HgPath::new(br""),
769 HgPath::new(br""),
751 HgPath::new(br"\\conky\\mountpoint\foo\bar")
770 HgPath::new(br"\\conky\\mountpoint\foo\bar")
752 )
771 )
753 );
772 );
754 assert_eq!(
773 assert_eq!(
755 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
774 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
756 (
775 (
757 HgPath::new(br""),
776 HgPath::new(br""),
758 HgPath::new(br"//conky//mountpoint/foo/bar")
777 HgPath::new(br"//conky//mountpoint/foo/bar")
759 )
778 )
760 );
779 );
761 // UNC part containing U+0130
780 // UNC part containing U+0130
762 assert_eq!(
781 assert_eq!(
763 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
782 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
764 (
783 (
765 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
784 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
766 HgPath::new(br"/foo/bar")
785 HgPath::new(br"/foo/bar")
767 )
786 )
768 );
787 );
769 }
788 }
770
789
771 #[test]
790 #[test]
772 fn test_parent() {
791 fn test_parent() {
773 let path = HgPath::new(b"");
792 let path = HgPath::new(b"");
774 assert_eq!(path.parent(), path);
793 assert_eq!(path.parent(), path);
775
794
776 let path = HgPath::new(b"a");
795 let path = HgPath::new(b"a");
777 assert_eq!(path.parent(), HgPath::new(b""));
796 assert_eq!(path.parent(), HgPath::new(b""));
778
797
779 let path = HgPath::new(b"a/b");
798 let path = HgPath::new(b"a/b");
780 assert_eq!(path.parent(), HgPath::new(b"a"));
799 assert_eq!(path.parent(), HgPath::new(b"a"));
781
800
782 let path = HgPath::new(b"a/other/b");
801 let path = HgPath::new(b"a/other/b");
783 assert_eq!(path.parent(), HgPath::new(b"a/other"));
802 assert_eq!(path.parent(), HgPath::new(b"a/other"));
784 }
803 }
785 }
804 }
General Comments 0
You need to be logged in to leave comments. Login now