##// END OF EJS Templates
rust: use `logging_timer` instead of `micro_timer`...
Raphaël Gomès -
r50808:c15b415d default
parent child Browse files
Show More
@@ -1,1455 +1,1455 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 version = 3
3 version = 3
4
4
5 [[package]]
5 [[package]]
6 name = "Inflector"
6 name = "Inflector"
7 version = "0.11.4"
7 version = "0.11.4"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10
10
11 [[package]]
11 [[package]]
12 name = "adler"
12 name = "adler"
13 version = "1.0.2"
13 version = "1.0.2"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
15 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
15 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
16
16
17 [[package]]
17 [[package]]
18 name = "ahash"
18 name = "ahash"
19 version = "0.8.2"
19 version = "0.8.2"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
21 checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
21 checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
22 dependencies = [
22 dependencies = [
23 "cfg-if",
23 "cfg-if",
24 "once_cell",
24 "once_cell",
25 "version_check",
25 "version_check",
26 ]
26 ]
27
27
28 [[package]]
28 [[package]]
29 name = "aho-corasick"
29 name = "aho-corasick"
30 version = "0.7.19"
30 version = "0.7.19"
31 source = "registry+https://github.com/rust-lang/crates.io-index"
31 source = "registry+https://github.com/rust-lang/crates.io-index"
32 checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
32 checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
33 dependencies = [
33 dependencies = [
34 "memchr",
34 "memchr",
35 ]
35 ]
36
36
37 [[package]]
37 [[package]]
38 name = "aliasable"
38 name = "aliasable"
39 version = "0.1.3"
39 version = "0.1.3"
40 source = "registry+https://github.com/rust-lang/crates.io-index"
40 source = "registry+https://github.com/rust-lang/crates.io-index"
41 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
41 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
42
42
43 [[package]]
43 [[package]]
44 name = "android_system_properties"
44 name = "android_system_properties"
45 version = "0.1.5"
45 version = "0.1.5"
46 source = "registry+https://github.com/rust-lang/crates.io-index"
46 source = "registry+https://github.com/rust-lang/crates.io-index"
47 checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
47 checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
48 dependencies = [
48 dependencies = [
49 "libc",
49 "libc",
50 ]
50 ]
51
51
52 [[package]]
52 [[package]]
53 name = "atty"
53 name = "atty"
54 version = "0.2.14"
54 version = "0.2.14"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
56 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
56 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
57 dependencies = [
57 dependencies = [
58 "hermit-abi",
58 "hermit-abi",
59 "libc",
59 "libc",
60 "winapi",
60 "winapi",
61 ]
61 ]
62
62
63 [[package]]
63 [[package]]
64 name = "autocfg"
64 name = "autocfg"
65 version = "1.1.0"
65 version = "1.1.0"
66 source = "registry+https://github.com/rust-lang/crates.io-index"
66 source = "registry+https://github.com/rust-lang/crates.io-index"
67 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
67 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
68
68
69 [[package]]
69 [[package]]
70 name = "bitflags"
70 name = "bitflags"
71 version = "1.3.2"
71 version = "1.3.2"
72 source = "registry+https://github.com/rust-lang/crates.io-index"
72 source = "registry+https://github.com/rust-lang/crates.io-index"
73 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
73 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
74
74
75 [[package]]
75 [[package]]
76 name = "bitmaps"
76 name = "bitmaps"
77 version = "2.1.0"
77 version = "2.1.0"
78 source = "registry+https://github.com/rust-lang/crates.io-index"
78 source = "registry+https://github.com/rust-lang/crates.io-index"
79 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
79 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
80 dependencies = [
80 dependencies = [
81 "typenum",
81 "typenum",
82 ]
82 ]
83
83
84 [[package]]
84 [[package]]
85 name = "block-buffer"
85 name = "block-buffer"
86 version = "0.9.0"
86 version = "0.9.0"
87 source = "registry+https://github.com/rust-lang/crates.io-index"
87 source = "registry+https://github.com/rust-lang/crates.io-index"
88 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
88 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
89 dependencies = [
89 dependencies = [
90 "generic-array",
90 "generic-array",
91 ]
91 ]
92
92
93 [[package]]
93 [[package]]
94 name = "block-buffer"
94 name = "block-buffer"
95 version = "0.10.3"
95 version = "0.10.3"
96 source = "registry+https://github.com/rust-lang/crates.io-index"
96 source = "registry+https://github.com/rust-lang/crates.io-index"
97 checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
97 checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
98 dependencies = [
98 dependencies = [
99 "generic-array",
99 "generic-array",
100 ]
100 ]
101
101
102 [[package]]
102 [[package]]
103 name = "bumpalo"
103 name = "bumpalo"
104 version = "3.11.1"
104 version = "3.11.1"
105 source = "registry+https://github.com/rust-lang/crates.io-index"
105 source = "registry+https://github.com/rust-lang/crates.io-index"
106 checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
106 checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
107
107
108 [[package]]
108 [[package]]
109 name = "byteorder"
109 name = "byteorder"
110 version = "1.4.3"
110 version = "1.4.3"
111 source = "registry+https://github.com/rust-lang/crates.io-index"
111 source = "registry+https://github.com/rust-lang/crates.io-index"
112 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
112 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
113
113
114 [[package]]
114 [[package]]
115 name = "bytes-cast"
115 name = "bytes-cast"
116 version = "0.2.0"
116 version = "0.2.0"
117 source = "registry+https://github.com/rust-lang/crates.io-index"
117 source = "registry+https://github.com/rust-lang/crates.io-index"
118 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
118 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
119 dependencies = [
119 dependencies = [
120 "bytes-cast-derive",
120 "bytes-cast-derive",
121 ]
121 ]
122
122
123 [[package]]
123 [[package]]
124 name = "bytes-cast-derive"
124 name = "bytes-cast-derive"
125 version = "0.1.1"
125 version = "0.1.1"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
127 checksum = "b13e0e8ffc91021ba28dc98b2ea82099ba4ec07655279c21bfa3313ed96708fc"
127 checksum = "b13e0e8ffc91021ba28dc98b2ea82099ba4ec07655279c21bfa3313ed96708fc"
128 dependencies = [
128 dependencies = [
129 "proc-macro2",
129 "proc-macro2",
130 "quote",
130 "quote",
131 "syn",
131 "syn",
132 ]
132 ]
133
133
134 [[package]]
134 [[package]]
135 name = "cc"
135 name = "cc"
136 version = "1.0.76"
136 version = "1.0.76"
137 source = "registry+https://github.com/rust-lang/crates.io-index"
137 source = "registry+https://github.com/rust-lang/crates.io-index"
138 checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
138 checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
139 dependencies = [
139 dependencies = [
140 "jobserver",
140 "jobserver",
141 ]
141 ]
142
142
143 [[package]]
143 [[package]]
144 name = "cfg-if"
144 name = "cfg-if"
145 version = "1.0.0"
145 version = "1.0.0"
146 source = "registry+https://github.com/rust-lang/crates.io-index"
146 source = "registry+https://github.com/rust-lang/crates.io-index"
147 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
147 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
148
148
149 [[package]]
149 [[package]]
150 name = "chrono"
150 name = "chrono"
151 version = "0.4.23"
151 version = "0.4.23"
152 source = "registry+https://github.com/rust-lang/crates.io-index"
152 source = "registry+https://github.com/rust-lang/crates.io-index"
153 checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
153 checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
154 dependencies = [
154 dependencies = [
155 "iana-time-zone",
155 "iana-time-zone",
156 "js-sys",
156 "js-sys",
157 "num-integer",
157 "num-integer",
158 "num-traits",
158 "num-traits",
159 "time",
159 "time",
160 "wasm-bindgen",
160 "wasm-bindgen",
161 "winapi",
161 "winapi",
162 ]
162 ]
163
163
164 [[package]]
164 [[package]]
165 name = "clap"
165 name = "clap"
166 version = "4.0.24"
166 version = "4.0.24"
167 source = "registry+https://github.com/rust-lang/crates.io-index"
167 source = "registry+https://github.com/rust-lang/crates.io-index"
168 checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
168 checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
169 dependencies = [
169 dependencies = [
170 "atty",
170 "atty",
171 "bitflags",
171 "bitflags",
172 "clap_derive",
172 "clap_derive",
173 "clap_lex",
173 "clap_lex",
174 "once_cell",
174 "once_cell",
175 "strsim",
175 "strsim",
176 "termcolor",
176 "termcolor",
177 ]
177 ]
178
178
179 [[package]]
179 [[package]]
180 name = "clap_derive"
180 name = "clap_derive"
181 version = "4.0.21"
181 version = "4.0.21"
182 source = "registry+https://github.com/rust-lang/crates.io-index"
182 source = "registry+https://github.com/rust-lang/crates.io-index"
183 checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
183 checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
184 dependencies = [
184 dependencies = [
185 "heck",
185 "heck",
186 "proc-macro-error",
186 "proc-macro-error",
187 "proc-macro2",
187 "proc-macro2",
188 "quote",
188 "quote",
189 "syn",
189 "syn",
190 ]
190 ]
191
191
192 [[package]]
192 [[package]]
193 name = "clap_lex"
193 name = "clap_lex"
194 version = "0.3.0"
194 version = "0.3.0"
195 source = "registry+https://github.com/rust-lang/crates.io-index"
195 source = "registry+https://github.com/rust-lang/crates.io-index"
196 checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
196 checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
197 dependencies = [
197 dependencies = [
198 "os_str_bytes",
198 "os_str_bytes",
199 ]
199 ]
200
200
201 [[package]]
201 [[package]]
202 name = "codespan-reporting"
202 name = "codespan-reporting"
203 version = "0.11.1"
203 version = "0.11.1"
204 source = "registry+https://github.com/rust-lang/crates.io-index"
204 source = "registry+https://github.com/rust-lang/crates.io-index"
205 checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
205 checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
206 dependencies = [
206 dependencies = [
207 "termcolor",
207 "termcolor",
208 "unicode-width",
208 "unicode-width",
209 ]
209 ]
210
210
211 [[package]]
211 [[package]]
212 name = "convert_case"
212 name = "convert_case"
213 version = "0.4.0"
213 version = "0.4.0"
214 source = "registry+https://github.com/rust-lang/crates.io-index"
214 source = "registry+https://github.com/rust-lang/crates.io-index"
215 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
215 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
216
216
217 [[package]]
217 [[package]]
218 name = "core-foundation-sys"
218 name = "core-foundation-sys"
219 version = "0.8.3"
219 version = "0.8.3"
220 source = "registry+https://github.com/rust-lang/crates.io-index"
220 source = "registry+https://github.com/rust-lang/crates.io-index"
221 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
221 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
222
222
223 [[package]]
223 [[package]]
224 name = "cpufeatures"
224 name = "cpufeatures"
225 version = "0.2.5"
225 version = "0.2.5"
226 source = "registry+https://github.com/rust-lang/crates.io-index"
226 source = "registry+https://github.com/rust-lang/crates.io-index"
227 checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
227 checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
228 dependencies = [
228 dependencies = [
229 "libc",
229 "libc",
230 ]
230 ]
231
231
232 [[package]]
232 [[package]]
233 name = "cpython"
233 name = "cpython"
234 version = "0.7.1"
234 version = "0.7.1"
235 source = "registry+https://github.com/rust-lang/crates.io-index"
235 source = "registry+https://github.com/rust-lang/crates.io-index"
236 checksum = "3052106c29da7390237bc2310c1928335733b286287754ea85e6093d2495280e"
236 checksum = "3052106c29da7390237bc2310c1928335733b286287754ea85e6093d2495280e"
237 dependencies = [
237 dependencies = [
238 "libc",
238 "libc",
239 "num-traits",
239 "num-traits",
240 "paste",
240 "paste",
241 "python3-sys",
241 "python3-sys",
242 ]
242 ]
243
243
244 [[package]]
244 [[package]]
245 name = "crc32fast"
245 name = "crc32fast"
246 version = "1.3.2"
246 version = "1.3.2"
247 source = "registry+https://github.com/rust-lang/crates.io-index"
247 source = "registry+https://github.com/rust-lang/crates.io-index"
248 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
248 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
249 dependencies = [
249 dependencies = [
250 "cfg-if",
250 "cfg-if",
251 ]
251 ]
252
252
253 [[package]]
253 [[package]]
254 name = "crossbeam-channel"
254 name = "crossbeam-channel"
255 version = "0.5.6"
255 version = "0.5.6"
256 source = "registry+https://github.com/rust-lang/crates.io-index"
256 source = "registry+https://github.com/rust-lang/crates.io-index"
257 checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
257 checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
258 dependencies = [
258 dependencies = [
259 "cfg-if",
259 "cfg-if",
260 "crossbeam-utils",
260 "crossbeam-utils",
261 ]
261 ]
262
262
263 [[package]]
263 [[package]]
264 name = "crossbeam-deque"
264 name = "crossbeam-deque"
265 version = "0.8.2"
265 version = "0.8.2"
266 source = "registry+https://github.com/rust-lang/crates.io-index"
266 source = "registry+https://github.com/rust-lang/crates.io-index"
267 checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
267 checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
268 dependencies = [
268 dependencies = [
269 "cfg-if",
269 "cfg-if",
270 "crossbeam-epoch",
270 "crossbeam-epoch",
271 "crossbeam-utils",
271 "crossbeam-utils",
272 ]
272 ]
273
273
274 [[package]]
274 [[package]]
275 name = "crossbeam-epoch"
275 name = "crossbeam-epoch"
276 version = "0.9.11"
276 version = "0.9.11"
277 source = "registry+https://github.com/rust-lang/crates.io-index"
277 source = "registry+https://github.com/rust-lang/crates.io-index"
278 checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
278 checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
279 dependencies = [
279 dependencies = [
280 "autocfg",
280 "autocfg",
281 "cfg-if",
281 "cfg-if",
282 "crossbeam-utils",
282 "crossbeam-utils",
283 "memoffset",
283 "memoffset",
284 "scopeguard",
284 "scopeguard",
285 ]
285 ]
286
286
287 [[package]]
287 [[package]]
288 name = "crossbeam-utils"
288 name = "crossbeam-utils"
289 version = "0.8.12"
289 version = "0.8.12"
290 source = "registry+https://github.com/rust-lang/crates.io-index"
290 source = "registry+https://github.com/rust-lang/crates.io-index"
291 checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
291 checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
292 dependencies = [
292 dependencies = [
293 "cfg-if",
293 "cfg-if",
294 ]
294 ]
295
295
296 [[package]]
296 [[package]]
297 name = "crypto-common"
297 name = "crypto-common"
298 version = "0.1.6"
298 version = "0.1.6"
299 source = "registry+https://github.com/rust-lang/crates.io-index"
299 source = "registry+https://github.com/rust-lang/crates.io-index"
300 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
300 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
301 dependencies = [
301 dependencies = [
302 "generic-array",
302 "generic-array",
303 "typenum",
303 "typenum",
304 ]
304 ]
305
305
306 [[package]]
306 [[package]]
307 name = "ctor"
307 name = "ctor"
308 version = "0.1.26"
308 version = "0.1.26"
309 source = "registry+https://github.com/rust-lang/crates.io-index"
309 source = "registry+https://github.com/rust-lang/crates.io-index"
310 checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
310 checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
311 dependencies = [
311 dependencies = [
312 "quote",
312 "quote",
313 "syn",
313 "syn",
314 ]
314 ]
315
315
316 [[package]]
316 [[package]]
317 name = "cxx"
317 name = "cxx"
318 version = "1.0.81"
318 version = "1.0.81"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
320 checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
320 checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
321 dependencies = [
321 dependencies = [
322 "cc",
322 "cc",
323 "cxxbridge-flags",
323 "cxxbridge-flags",
324 "cxxbridge-macro",
324 "cxxbridge-macro",
325 "link-cplusplus",
325 "link-cplusplus",
326 ]
326 ]
327
327
328 [[package]]
328 [[package]]
329 name = "cxx-build"
329 name = "cxx-build"
330 version = "1.0.81"
330 version = "1.0.81"
331 source = "registry+https://github.com/rust-lang/crates.io-index"
331 source = "registry+https://github.com/rust-lang/crates.io-index"
332 checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
332 checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
333 dependencies = [
333 dependencies = [
334 "cc",
334 "cc",
335 "codespan-reporting",
335 "codespan-reporting",
336 "once_cell",
336 "once_cell",
337 "proc-macro2",
337 "proc-macro2",
338 "quote",
338 "quote",
339 "scratch",
339 "scratch",
340 "syn",
340 "syn",
341 ]
341 ]
342
342
343 [[package]]
343 [[package]]
344 name = "cxxbridge-flags"
344 name = "cxxbridge-flags"
345 version = "1.0.81"
345 version = "1.0.81"
346 source = "registry+https://github.com/rust-lang/crates.io-index"
346 source = "registry+https://github.com/rust-lang/crates.io-index"
347 checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
347 checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
348
348
349 [[package]]
349 [[package]]
350 name = "cxxbridge-macro"
350 name = "cxxbridge-macro"
351 version = "1.0.81"
351 version = "1.0.81"
352 source = "registry+https://github.com/rust-lang/crates.io-index"
352 source = "registry+https://github.com/rust-lang/crates.io-index"
353 checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
353 checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
354 dependencies = [
354 dependencies = [
355 "proc-macro2",
355 "proc-macro2",
356 "quote",
356 "quote",
357 "syn",
357 "syn",
358 ]
358 ]
359
359
360 [[package]]
360 [[package]]
361 name = "derive_more"
361 name = "derive_more"
362 version = "0.99.17"
362 version = "0.99.17"
363 source = "registry+https://github.com/rust-lang/crates.io-index"
363 source = "registry+https://github.com/rust-lang/crates.io-index"
364 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
364 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
365 dependencies = [
365 dependencies = [
366 "convert_case",
366 "convert_case",
367 "proc-macro2",
367 "proc-macro2",
368 "quote",
368 "quote",
369 "rustc_version",
369 "rustc_version",
370 "syn",
370 "syn",
371 ]
371 ]
372
372
373 [[package]]
373 [[package]]
374 name = "diff"
374 name = "diff"
375 version = "0.1.13"
375 version = "0.1.13"
376 source = "registry+https://github.com/rust-lang/crates.io-index"
376 source = "registry+https://github.com/rust-lang/crates.io-index"
377 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
377 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
378
378
379 [[package]]
379 [[package]]
380 name = "digest"
380 name = "digest"
381 version = "0.9.0"
381 version = "0.9.0"
382 source = "registry+https://github.com/rust-lang/crates.io-index"
382 source = "registry+https://github.com/rust-lang/crates.io-index"
383 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
383 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
384 dependencies = [
384 dependencies = [
385 "generic-array",
385 "generic-array",
386 ]
386 ]
387
387
388 [[package]]
388 [[package]]
389 name = "digest"
389 name = "digest"
390 version = "0.10.5"
390 version = "0.10.5"
391 source = "registry+https://github.com/rust-lang/crates.io-index"
391 source = "registry+https://github.com/rust-lang/crates.io-index"
392 checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
392 checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
393 dependencies = [
393 dependencies = [
394 "block-buffer 0.10.3",
394 "block-buffer 0.10.3",
395 "crypto-common",
395 "crypto-common",
396 ]
396 ]
397
397
398 [[package]]
398 [[package]]
399 name = "either"
399 name = "either"
400 version = "1.8.0"
400 version = "1.8.0"
401 source = "registry+https://github.com/rust-lang/crates.io-index"
401 source = "registry+https://github.com/rust-lang/crates.io-index"
402 checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
402 checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
403
403
404 [[package]]
404 [[package]]
405 name = "env_logger"
405 name = "env_logger"
406 version = "0.9.3"
406 version = "0.9.3"
407 source = "registry+https://github.com/rust-lang/crates.io-index"
407 source = "registry+https://github.com/rust-lang/crates.io-index"
408 checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
408 checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
409 dependencies = [
409 dependencies = [
410 "atty",
410 "atty",
411 "humantime",
411 "humantime",
412 "log",
412 "log",
413 "regex",
413 "regex",
414 "termcolor",
414 "termcolor",
415 ]
415 ]
416
416
417 [[package]]
417 [[package]]
418 name = "fastrand"
418 name = "fastrand"
419 version = "1.8.0"
419 version = "1.8.0"
420 source = "registry+https://github.com/rust-lang/crates.io-index"
420 source = "registry+https://github.com/rust-lang/crates.io-index"
421 checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
421 checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
422 dependencies = [
422 dependencies = [
423 "instant",
423 "instant",
424 ]
424 ]
425
425
426 [[package]]
426 [[package]]
427 name = "flate2"
427 name = "flate2"
428 version = "1.0.24"
428 version = "1.0.24"
429 source = "registry+https://github.com/rust-lang/crates.io-index"
429 source = "registry+https://github.com/rust-lang/crates.io-index"
430 checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
430 checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
431 dependencies = [
431 dependencies = [
432 "crc32fast",
432 "crc32fast",
433 "libz-sys",
433 "libz-sys",
434 "miniz_oxide",
434 "miniz_oxide",
435 ]
435 ]
436
436
437 [[package]]
437 [[package]]
438 name = "format-bytes"
438 name = "format-bytes"
439 version = "0.3.0"
439 version = "0.3.0"
440 source = "registry+https://github.com/rust-lang/crates.io-index"
440 source = "registry+https://github.com/rust-lang/crates.io-index"
441 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
441 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
442 dependencies = [
442 dependencies = [
443 "format-bytes-macros",
443 "format-bytes-macros",
444 ]
444 ]
445
445
446 [[package]]
446 [[package]]
447 name = "format-bytes-macros"
447 name = "format-bytes-macros"
448 version = "0.4.0"
448 version = "0.4.0"
449 source = "registry+https://github.com/rust-lang/crates.io-index"
449 source = "registry+https://github.com/rust-lang/crates.io-index"
450 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
450 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
451 dependencies = [
451 dependencies = [
452 "proc-macro2",
452 "proc-macro2",
453 "quote",
453 "quote",
454 "syn",
454 "syn",
455 ]
455 ]
456
456
457 [[package]]
457 [[package]]
458 name = "generic-array"
458 name = "generic-array"
459 version = "0.14.6"
459 version = "0.14.6"
460 source = "registry+https://github.com/rust-lang/crates.io-index"
460 source = "registry+https://github.com/rust-lang/crates.io-index"
461 checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
461 checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
462 dependencies = [
462 dependencies = [
463 "typenum",
463 "typenum",
464 "version_check",
464 "version_check",
465 ]
465 ]
466
466
467 [[package]]
467 [[package]]
468 name = "getrandom"
468 name = "getrandom"
469 version = "0.1.16"
469 version = "0.1.16"
470 source = "registry+https://github.com/rust-lang/crates.io-index"
470 source = "registry+https://github.com/rust-lang/crates.io-index"
471 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
471 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
472 dependencies = [
472 dependencies = [
473 "cfg-if",
473 "cfg-if",
474 "libc",
474 "libc",
475 "wasi 0.9.0+wasi-snapshot-preview1",
475 "wasi 0.9.0+wasi-snapshot-preview1",
476 ]
476 ]
477
477
478 [[package]]
478 [[package]]
479 name = "getrandom"
479 name = "getrandom"
480 version = "0.2.8"
480 version = "0.2.8"
481 source = "registry+https://github.com/rust-lang/crates.io-index"
481 source = "registry+https://github.com/rust-lang/crates.io-index"
482 checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
482 checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
483 dependencies = [
483 dependencies = [
484 "cfg-if",
484 "cfg-if",
485 "libc",
485 "libc",
486 "wasi 0.11.0+wasi-snapshot-preview1",
486 "wasi 0.11.0+wasi-snapshot-preview1",
487 ]
487 ]
488
488
489 [[package]]
489 [[package]]
490 name = "hashbrown"
490 name = "hashbrown"
491 version = "0.13.1"
491 version = "0.13.1"
492 source = "registry+https://github.com/rust-lang/crates.io-index"
492 source = "registry+https://github.com/rust-lang/crates.io-index"
493 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
493 checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
494 dependencies = [
494 dependencies = [
495 "ahash",
495 "ahash",
496 "rayon",
496 "rayon",
497 ]
497 ]
498
498
499 [[package]]
499 [[package]]
500 name = "heck"
500 name = "heck"
501 version = "0.4.0"
501 version = "0.4.0"
502 source = "registry+https://github.com/rust-lang/crates.io-index"
502 source = "registry+https://github.com/rust-lang/crates.io-index"
503 checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
503 checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
504
504
505 [[package]]
505 [[package]]
506 name = "hermit-abi"
506 name = "hermit-abi"
507 version = "0.1.19"
507 version = "0.1.19"
508 source = "registry+https://github.com/rust-lang/crates.io-index"
508 source = "registry+https://github.com/rust-lang/crates.io-index"
509 checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
509 checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
510 dependencies = [
510 dependencies = [
511 "libc",
511 "libc",
512 ]
512 ]
513
513
514 [[package]]
514 [[package]]
515 name = "hex"
515 name = "hex"
516 version = "0.4.3"
516 version = "0.4.3"
517 source = "registry+https://github.com/rust-lang/crates.io-index"
517 source = "registry+https://github.com/rust-lang/crates.io-index"
518 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
518 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
519
519
520 [[package]]
520 [[package]]
521 name = "hg-core"
521 name = "hg-core"
522 version = "0.1.0"
522 version = "0.1.0"
523 dependencies = [
523 dependencies = [
524 "bitflags",
524 "bitflags",
525 "byteorder",
525 "byteorder",
526 "bytes-cast",
526 "bytes-cast",
527 "clap",
527 "clap",
528 "crossbeam-channel",
528 "crossbeam-channel",
529 "derive_more",
529 "derive_more",
530 "flate2",
530 "flate2",
531 "format-bytes",
531 "format-bytes",
532 "hashbrown",
532 "hashbrown",
533 "home",
533 "home",
534 "im-rc",
534 "im-rc",
535 "itertools",
535 "itertools",
536 "lazy_static",
536 "lazy_static",
537 "libc",
537 "libc",
538 "log",
538 "log",
539 "logging_timer",
539 "memmap2",
540 "memmap2",
540 "micro-timer",
541 "once_cell",
541 "once_cell",
542 "ouroboros",
542 "ouroboros",
543 "pretty_assertions",
543 "pretty_assertions",
544 "rand 0.8.5",
544 "rand 0.8.5",
545 "rand_distr",
545 "rand_distr",
546 "rand_pcg",
546 "rand_pcg",
547 "rayon",
547 "rayon",
548 "regex",
548 "regex",
549 "same-file",
549 "same-file",
550 "sha-1 0.10.0",
550 "sha-1 0.10.0",
551 "tempfile",
551 "tempfile",
552 "thread_local",
552 "thread_local",
553 "twox-hash",
553 "twox-hash",
554 "zstd",
554 "zstd",
555 ]
555 ]
556
556
557 [[package]]
557 [[package]]
558 name = "hg-cpython"
558 name = "hg-cpython"
559 version = "0.1.0"
559 version = "0.1.0"
560 dependencies = [
560 dependencies = [
561 "cpython",
561 "cpython",
562 "crossbeam-channel",
562 "crossbeam-channel",
563 "env_logger",
563 "env_logger",
564 "hg-core",
564 "hg-core",
565 "libc",
565 "libc",
566 "log",
566 "log",
567 "stable_deref_trait",
567 "stable_deref_trait",
568 "vcsgraph",
568 "vcsgraph",
569 ]
569 ]
570
570
571 [[package]]
571 [[package]]
572 name = "home"
572 name = "home"
573 version = "0.5.4"
573 version = "0.5.4"
574 source = "registry+https://github.com/rust-lang/crates.io-index"
574 source = "registry+https://github.com/rust-lang/crates.io-index"
575 checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
575 checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
576 dependencies = [
576 dependencies = [
577 "winapi",
577 "winapi",
578 ]
578 ]
579
579
580 [[package]]
580 [[package]]
581 name = "humantime"
581 name = "humantime"
582 version = "2.1.0"
582 version = "2.1.0"
583 source = "registry+https://github.com/rust-lang/crates.io-index"
583 source = "registry+https://github.com/rust-lang/crates.io-index"
584 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
584 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
585
585
586 [[package]]
586 [[package]]
587 name = "iana-time-zone"
587 name = "iana-time-zone"
588 version = "0.1.53"
588 version = "0.1.53"
589 source = "registry+https://github.com/rust-lang/crates.io-index"
589 source = "registry+https://github.com/rust-lang/crates.io-index"
590 checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
590 checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
591 dependencies = [
591 dependencies = [
592 "android_system_properties",
592 "android_system_properties",
593 "core-foundation-sys",
593 "core-foundation-sys",
594 "iana-time-zone-haiku",
594 "iana-time-zone-haiku",
595 "js-sys",
595 "js-sys",
596 "wasm-bindgen",
596 "wasm-bindgen",
597 "winapi",
597 "winapi",
598 ]
598 ]
599
599
600 [[package]]
600 [[package]]
601 name = "iana-time-zone-haiku"
601 name = "iana-time-zone-haiku"
602 version = "0.1.1"
602 version = "0.1.1"
603 source = "registry+https://github.com/rust-lang/crates.io-index"
603 source = "registry+https://github.com/rust-lang/crates.io-index"
604 checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
604 checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
605 dependencies = [
605 dependencies = [
606 "cxx",
606 "cxx",
607 "cxx-build",
607 "cxx-build",
608 ]
608 ]
609
609
610 [[package]]
610 [[package]]
611 name = "im-rc"
611 name = "im-rc"
612 version = "15.1.0"
612 version = "15.1.0"
613 source = "registry+https://github.com/rust-lang/crates.io-index"
613 source = "registry+https://github.com/rust-lang/crates.io-index"
614 checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
614 checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
615 dependencies = [
615 dependencies = [
616 "bitmaps",
616 "bitmaps",
617 "rand_core 0.6.4",
617 "rand_core 0.6.4",
618 "rand_xoshiro",
618 "rand_xoshiro",
619 "sized-chunks",
619 "sized-chunks",
620 "typenum",
620 "typenum",
621 "version_check",
621 "version_check",
622 ]
622 ]
623
623
624 [[package]]
624 [[package]]
625 name = "instant"
625 name = "instant"
626 version = "0.1.12"
626 version = "0.1.12"
627 source = "registry+https://github.com/rust-lang/crates.io-index"
627 source = "registry+https://github.com/rust-lang/crates.io-index"
628 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
628 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
629 dependencies = [
629 dependencies = [
630 "cfg-if",
630 "cfg-if",
631 ]
631 ]
632
632
633 [[package]]
633 [[package]]
634 name = "itertools"
634 name = "itertools"
635 version = "0.10.5"
635 version = "0.10.5"
636 source = "registry+https://github.com/rust-lang/crates.io-index"
636 source = "registry+https://github.com/rust-lang/crates.io-index"
637 checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
637 checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
638 dependencies = [
638 dependencies = [
639 "either",
639 "either",
640 ]
640 ]
641
641
642 [[package]]
642 [[package]]
643 name = "jobserver"
643 name = "jobserver"
644 version = "0.1.25"
644 version = "0.1.25"
645 source = "registry+https://github.com/rust-lang/crates.io-index"
645 source = "registry+https://github.com/rust-lang/crates.io-index"
646 checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
646 checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
647 dependencies = [
647 dependencies = [
648 "libc",
648 "libc",
649 ]
649 ]
650
650
651 [[package]]
651 [[package]]
652 name = "js-sys"
652 name = "js-sys"
653 version = "0.3.60"
653 version = "0.3.60"
654 source = "registry+https://github.com/rust-lang/crates.io-index"
654 source = "registry+https://github.com/rust-lang/crates.io-index"
655 checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
655 checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
656 dependencies = [
656 dependencies = [
657 "wasm-bindgen",
657 "wasm-bindgen",
658 ]
658 ]
659
659
660 [[package]]
660 [[package]]
661 name = "lazy_static"
661 name = "lazy_static"
662 version = "1.4.0"
662 version = "1.4.0"
663 source = "registry+https://github.com/rust-lang/crates.io-index"
663 source = "registry+https://github.com/rust-lang/crates.io-index"
664 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
664 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
665
665
666 [[package]]
666 [[package]]
667 name = "libc"
667 name = "libc"
668 version = "0.2.137"
668 version = "0.2.137"
669 source = "registry+https://github.com/rust-lang/crates.io-index"
669 source = "registry+https://github.com/rust-lang/crates.io-index"
670 checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
670 checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
671
671
672 [[package]]
672 [[package]]
673 name = "libm"
673 name = "libm"
674 version = "0.2.6"
674 version = "0.2.6"
675 source = "registry+https://github.com/rust-lang/crates.io-index"
675 source = "registry+https://github.com/rust-lang/crates.io-index"
676 checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
676 checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
677
677
678 [[package]]
678 [[package]]
679 name = "libz-sys"
679 name = "libz-sys"
680 version = "1.1.8"
680 version = "1.1.8"
681 source = "registry+https://github.com/rust-lang/crates.io-index"
681 source = "registry+https://github.com/rust-lang/crates.io-index"
682 checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
682 checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
683 dependencies = [
683 dependencies = [
684 "cc",
684 "cc",
685 "pkg-config",
685 "pkg-config",
686 "vcpkg",
686 "vcpkg",
687 ]
687 ]
688
688
689 [[package]]
689 [[package]]
690 name = "link-cplusplus"
690 name = "link-cplusplus"
691 version = "1.0.7"
691 version = "1.0.7"
692 source = "registry+https://github.com/rust-lang/crates.io-index"
692 source = "registry+https://github.com/rust-lang/crates.io-index"
693 checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
693 checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
694 dependencies = [
694 dependencies = [
695 "cc",
695 "cc",
696 ]
696 ]
697
697
698 [[package]]
698 [[package]]
699 name = "log"
699 name = "log"
700 version = "0.4.17"
700 version = "0.4.17"
701 source = "registry+https://github.com/rust-lang/crates.io-index"
701 source = "registry+https://github.com/rust-lang/crates.io-index"
702 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
702 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
703 dependencies = [
703 dependencies = [
704 "cfg-if",
704 "cfg-if",
705 ]
705 ]
706
706
707 [[package]]
707 [[package]]
708 name = "logging_timer"
709 version = "1.1.0"
710 source = "registry+https://github.com/rust-lang/crates.io-index"
711 checksum = "64e96f261d684b7089aa576bb74e823241dccd994b27d30fabf1dcb3af284fe9"
712 dependencies = [
713 "log",
714 "logging_timer_proc_macros",
715 ]
716
717 [[package]]
718 name = "logging_timer_proc_macros"
719 version = "1.1.0"
720 source = "registry+https://github.com/rust-lang/crates.io-index"
721 checksum = "10a9062912d7952c5588cc474795e0b9ee008e7e6781127945b85413d4b99d81"
722 dependencies = [
723 "log",
724 "proc-macro2",
725 "quote",
726 "syn",
727 ]
728
729 [[package]]
708 name = "memchr"
730 name = "memchr"
709 version = "2.5.0"
731 version = "2.5.0"
710 source = "registry+https://github.com/rust-lang/crates.io-index"
732 source = "registry+https://github.com/rust-lang/crates.io-index"
711 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
733 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
712
734
713 [[package]]
735 [[package]]
714 name = "memmap2"
736 name = "memmap2"
715 version = "0.5.8"
737 version = "0.5.8"
716 source = "registry+https://github.com/rust-lang/crates.io-index"
738 source = "registry+https://github.com/rust-lang/crates.io-index"
717 checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
739 checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
718 dependencies = [
740 dependencies = [
719 "libc",
741 "libc",
720 "stable_deref_trait",
742 "stable_deref_trait",
721 ]
743 ]
722
744
723 [[package]]
745 [[package]]
724 name = "memoffset"
746 name = "memoffset"
725 version = "0.6.5"
747 version = "0.6.5"
726 source = "registry+https://github.com/rust-lang/crates.io-index"
748 source = "registry+https://github.com/rust-lang/crates.io-index"
727 checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
749 checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
728 dependencies = [
750 dependencies = [
729 "autocfg",
751 "autocfg",
730 ]
752 ]
731
753
732 [[package]]
754 [[package]]
733 name = "micro-timer"
734 version = "0.4.0"
735 source = "registry+https://github.com/rust-lang/crates.io-index"
736 checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405"
737 dependencies = [
738 "micro-timer-macros",
739 "scopeguard",
740 ]
741
742 [[package]]
743 name = "micro-timer-macros"
744 version = "0.4.0"
745 source = "registry+https://github.com/rust-lang/crates.io-index"
746 checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1"
747 dependencies = [
748 "proc-macro2",
749 "quote",
750 "scopeguard",
751 "syn",
752 ]
753
754 [[package]]
755 name = "miniz_oxide"
755 name = "miniz_oxide"
756 version = "0.5.4"
756 version = "0.5.4"
757 source = "registry+https://github.com/rust-lang/crates.io-index"
757 source = "registry+https://github.com/rust-lang/crates.io-index"
758 checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
758 checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
759 dependencies = [
759 dependencies = [
760 "adler",
760 "adler",
761 ]
761 ]
762
762
763 [[package]]
763 [[package]]
764 name = "num-integer"
764 name = "num-integer"
765 version = "0.1.45"
765 version = "0.1.45"
766 source = "registry+https://github.com/rust-lang/crates.io-index"
766 source = "registry+https://github.com/rust-lang/crates.io-index"
767 checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
767 checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
768 dependencies = [
768 dependencies = [
769 "autocfg",
769 "autocfg",
770 "num-traits",
770 "num-traits",
771 ]
771 ]
772
772
773 [[package]]
773 [[package]]
774 name = "num-traits"
774 name = "num-traits"
775 version = "0.2.15"
775 version = "0.2.15"
776 source = "registry+https://github.com/rust-lang/crates.io-index"
776 source = "registry+https://github.com/rust-lang/crates.io-index"
777 checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
777 checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
778 dependencies = [
778 dependencies = [
779 "autocfg",
779 "autocfg",
780 "libm",
780 "libm",
781 ]
781 ]
782
782
783 [[package]]
783 [[package]]
784 name = "num_cpus"
784 name = "num_cpus"
785 version = "1.14.0"
785 version = "1.14.0"
786 source = "registry+https://github.com/rust-lang/crates.io-index"
786 source = "registry+https://github.com/rust-lang/crates.io-index"
787 checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
787 checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
788 dependencies = [
788 dependencies = [
789 "hermit-abi",
789 "hermit-abi",
790 "libc",
790 "libc",
791 ]
791 ]
792
792
793 [[package]]
793 [[package]]
794 name = "once_cell"
794 name = "once_cell"
795 version = "1.16.0"
795 version = "1.16.0"
796 source = "registry+https://github.com/rust-lang/crates.io-index"
796 source = "registry+https://github.com/rust-lang/crates.io-index"
797 checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
797 checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
798
798
799 [[package]]
799 [[package]]
800 name = "opaque-debug"
800 name = "opaque-debug"
801 version = "0.3.0"
801 version = "0.3.0"
802 source = "registry+https://github.com/rust-lang/crates.io-index"
802 source = "registry+https://github.com/rust-lang/crates.io-index"
803 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
803 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
804
804
805 [[package]]
805 [[package]]
806 name = "os_str_bytes"
806 name = "os_str_bytes"
807 version = "6.4.0"
807 version = "6.4.0"
808 source = "registry+https://github.com/rust-lang/crates.io-index"
808 source = "registry+https://github.com/rust-lang/crates.io-index"
809 checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
809 checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
810
810
811 [[package]]
811 [[package]]
812 name = "ouroboros"
812 name = "ouroboros"
813 version = "0.15.5"
813 version = "0.15.5"
814 source = "registry+https://github.com/rust-lang/crates.io-index"
814 source = "registry+https://github.com/rust-lang/crates.io-index"
815 checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
815 checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
816 dependencies = [
816 dependencies = [
817 "aliasable",
817 "aliasable",
818 "ouroboros_macro",
818 "ouroboros_macro",
819 ]
819 ]
820
820
821 [[package]]
821 [[package]]
822 name = "ouroboros_macro"
822 name = "ouroboros_macro"
823 version = "0.15.5"
823 version = "0.15.5"
824 source = "registry+https://github.com/rust-lang/crates.io-index"
824 source = "registry+https://github.com/rust-lang/crates.io-index"
825 checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
825 checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
826 dependencies = [
826 dependencies = [
827 "Inflector",
827 "Inflector",
828 "proc-macro-error",
828 "proc-macro-error",
829 "proc-macro2",
829 "proc-macro2",
830 "quote",
830 "quote",
831 "syn",
831 "syn",
832 ]
832 ]
833
833
834 [[package]]
834 [[package]]
835 name = "output_vt100"
835 name = "output_vt100"
836 version = "0.1.3"
836 version = "0.1.3"
837 source = "registry+https://github.com/rust-lang/crates.io-index"
837 source = "registry+https://github.com/rust-lang/crates.io-index"
838 checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
838 checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
839 dependencies = [
839 dependencies = [
840 "winapi",
840 "winapi",
841 ]
841 ]
842
842
843 [[package]]
843 [[package]]
844 name = "paste"
844 name = "paste"
845 version = "1.0.9"
845 version = "1.0.9"
846 source = "registry+https://github.com/rust-lang/crates.io-index"
846 source = "registry+https://github.com/rust-lang/crates.io-index"
847 checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
847 checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
848
848
849 [[package]]
849 [[package]]
850 name = "pkg-config"
850 name = "pkg-config"
851 version = "0.3.26"
851 version = "0.3.26"
852 source = "registry+https://github.com/rust-lang/crates.io-index"
852 source = "registry+https://github.com/rust-lang/crates.io-index"
853 checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
853 checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
854
854
855 [[package]]
855 [[package]]
856 name = "ppv-lite86"
856 name = "ppv-lite86"
857 version = "0.2.17"
857 version = "0.2.17"
858 source = "registry+https://github.com/rust-lang/crates.io-index"
858 source = "registry+https://github.com/rust-lang/crates.io-index"
859 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
859 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
860
860
861 [[package]]
861 [[package]]
862 name = "pretty_assertions"
862 name = "pretty_assertions"
863 version = "1.3.0"
863 version = "1.3.0"
864 source = "registry+https://github.com/rust-lang/crates.io-index"
864 source = "registry+https://github.com/rust-lang/crates.io-index"
865 checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
865 checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
866 dependencies = [
866 dependencies = [
867 "ctor",
867 "ctor",
868 "diff",
868 "diff",
869 "output_vt100",
869 "output_vt100",
870 "yansi",
870 "yansi",
871 ]
871 ]
872
872
873 [[package]]
873 [[package]]
874 name = "proc-macro-error"
874 name = "proc-macro-error"
875 version = "1.0.4"
875 version = "1.0.4"
876 source = "registry+https://github.com/rust-lang/crates.io-index"
876 source = "registry+https://github.com/rust-lang/crates.io-index"
877 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
877 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
878 dependencies = [
878 dependencies = [
879 "proc-macro-error-attr",
879 "proc-macro-error-attr",
880 "proc-macro2",
880 "proc-macro2",
881 "quote",
881 "quote",
882 "syn",
882 "syn",
883 "version_check",
883 "version_check",
884 ]
884 ]
885
885
886 [[package]]
886 [[package]]
887 name = "proc-macro-error-attr"
887 name = "proc-macro-error-attr"
888 version = "1.0.4"
888 version = "1.0.4"
889 source = "registry+https://github.com/rust-lang/crates.io-index"
889 source = "registry+https://github.com/rust-lang/crates.io-index"
890 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
890 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
891 dependencies = [
891 dependencies = [
892 "proc-macro2",
892 "proc-macro2",
893 "quote",
893 "quote",
894 "version_check",
894 "version_check",
895 ]
895 ]
896
896
897 [[package]]
897 [[package]]
898 name = "proc-macro2"
898 name = "proc-macro2"
899 version = "1.0.47"
899 version = "1.0.47"
900 source = "registry+https://github.com/rust-lang/crates.io-index"
900 source = "registry+https://github.com/rust-lang/crates.io-index"
901 checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
901 checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
902 dependencies = [
902 dependencies = [
903 "unicode-ident",
903 "unicode-ident",
904 ]
904 ]
905
905
906 [[package]]
906 [[package]]
907 name = "python3-sys"
907 name = "python3-sys"
908 version = "0.7.1"
908 version = "0.7.1"
909 source = "registry+https://github.com/rust-lang/crates.io-index"
909 source = "registry+https://github.com/rust-lang/crates.io-index"
910 checksum = "49f8b50d72fb3015735aa403eebf19bbd72c093bfeeae24ee798be5f2f1aab52"
910 checksum = "49f8b50d72fb3015735aa403eebf19bbd72c093bfeeae24ee798be5f2f1aab52"
911 dependencies = [
911 dependencies = [
912 "libc",
912 "libc",
913 "regex",
913 "regex",
914 ]
914 ]
915
915
916 [[package]]
916 [[package]]
917 name = "quote"
917 name = "quote"
918 version = "1.0.21"
918 version = "1.0.21"
919 source = "registry+https://github.com/rust-lang/crates.io-index"
919 source = "registry+https://github.com/rust-lang/crates.io-index"
920 checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
920 checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
921 dependencies = [
921 dependencies = [
922 "proc-macro2",
922 "proc-macro2",
923 ]
923 ]
924
924
925 [[package]]
925 [[package]]
926 name = "rand"
926 name = "rand"
927 version = "0.7.3"
927 version = "0.7.3"
928 source = "registry+https://github.com/rust-lang/crates.io-index"
928 source = "registry+https://github.com/rust-lang/crates.io-index"
929 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
929 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
930 dependencies = [
930 dependencies = [
931 "getrandom 0.1.16",
931 "getrandom 0.1.16",
932 "libc",
932 "libc",
933 "rand_chacha 0.2.2",
933 "rand_chacha 0.2.2",
934 "rand_core 0.5.1",
934 "rand_core 0.5.1",
935 "rand_hc",
935 "rand_hc",
936 ]
936 ]
937
937
938 [[package]]
938 [[package]]
939 name = "rand"
939 name = "rand"
940 version = "0.8.5"
940 version = "0.8.5"
941 source = "registry+https://github.com/rust-lang/crates.io-index"
941 source = "registry+https://github.com/rust-lang/crates.io-index"
942 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
942 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
943 dependencies = [
943 dependencies = [
944 "libc",
944 "libc",
945 "rand_chacha 0.3.1",
945 "rand_chacha 0.3.1",
946 "rand_core 0.6.4",
946 "rand_core 0.6.4",
947 ]
947 ]
948
948
949 [[package]]
949 [[package]]
950 name = "rand_chacha"
950 name = "rand_chacha"
951 version = "0.2.2"
951 version = "0.2.2"
952 source = "registry+https://github.com/rust-lang/crates.io-index"
952 source = "registry+https://github.com/rust-lang/crates.io-index"
953 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
953 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
954 dependencies = [
954 dependencies = [
955 "ppv-lite86",
955 "ppv-lite86",
956 "rand_core 0.5.1",
956 "rand_core 0.5.1",
957 ]
957 ]
958
958
959 [[package]]
959 [[package]]
960 name = "rand_chacha"
960 name = "rand_chacha"
961 version = "0.3.1"
961 version = "0.3.1"
962 source = "registry+https://github.com/rust-lang/crates.io-index"
962 source = "registry+https://github.com/rust-lang/crates.io-index"
963 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
963 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
964 dependencies = [
964 dependencies = [
965 "ppv-lite86",
965 "ppv-lite86",
966 "rand_core 0.6.4",
966 "rand_core 0.6.4",
967 ]
967 ]
968
968
969 [[package]]
969 [[package]]
970 name = "rand_core"
970 name = "rand_core"
971 version = "0.5.1"
971 version = "0.5.1"
972 source = "registry+https://github.com/rust-lang/crates.io-index"
972 source = "registry+https://github.com/rust-lang/crates.io-index"
973 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
973 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
974 dependencies = [
974 dependencies = [
975 "getrandom 0.1.16",
975 "getrandom 0.1.16",
976 ]
976 ]
977
977
978 [[package]]
978 [[package]]
979 name = "rand_core"
979 name = "rand_core"
980 version = "0.6.4"
980 version = "0.6.4"
981 source = "registry+https://github.com/rust-lang/crates.io-index"
981 source = "registry+https://github.com/rust-lang/crates.io-index"
982 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
982 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
983 dependencies = [
983 dependencies = [
984 "getrandom 0.2.8",
984 "getrandom 0.2.8",
985 ]
985 ]
986
986
987 [[package]]
987 [[package]]
988 name = "rand_distr"
988 name = "rand_distr"
989 version = "0.4.3"
989 version = "0.4.3"
990 source = "registry+https://github.com/rust-lang/crates.io-index"
990 source = "registry+https://github.com/rust-lang/crates.io-index"
991 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
991 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
992 dependencies = [
992 dependencies = [
993 "num-traits",
993 "num-traits",
994 "rand 0.8.5",
994 "rand 0.8.5",
995 ]
995 ]
996
996
997 [[package]]
997 [[package]]
998 name = "rand_hc"
998 name = "rand_hc"
999 version = "0.2.0"
999 version = "0.2.0"
1000 source = "registry+https://github.com/rust-lang/crates.io-index"
1000 source = "registry+https://github.com/rust-lang/crates.io-index"
1001 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1001 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1002 dependencies = [
1002 dependencies = [
1003 "rand_core 0.5.1",
1003 "rand_core 0.5.1",
1004 ]
1004 ]
1005
1005
1006 [[package]]
1006 [[package]]
1007 name = "rand_pcg"
1007 name = "rand_pcg"
1008 version = "0.3.1"
1008 version = "0.3.1"
1009 source = "registry+https://github.com/rust-lang/crates.io-index"
1009 source = "registry+https://github.com/rust-lang/crates.io-index"
1010 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
1010 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
1011 dependencies = [
1011 dependencies = [
1012 "rand_core 0.6.4",
1012 "rand_core 0.6.4",
1013 ]
1013 ]
1014
1014
1015 [[package]]
1015 [[package]]
1016 name = "rand_xoshiro"
1016 name = "rand_xoshiro"
1017 version = "0.6.0"
1017 version = "0.6.0"
1018 source = "registry+https://github.com/rust-lang/crates.io-index"
1018 source = "registry+https://github.com/rust-lang/crates.io-index"
1019 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
1019 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
1020 dependencies = [
1020 dependencies = [
1021 "rand_core 0.6.4",
1021 "rand_core 0.6.4",
1022 ]
1022 ]
1023
1023
1024 [[package]]
1024 [[package]]
1025 name = "rayon"
1025 name = "rayon"
1026 version = "1.5.3"
1026 version = "1.5.3"
1027 source = "registry+https://github.com/rust-lang/crates.io-index"
1027 source = "registry+https://github.com/rust-lang/crates.io-index"
1028 checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
1028 checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
1029 dependencies = [
1029 dependencies = [
1030 "autocfg",
1030 "autocfg",
1031 "crossbeam-deque",
1031 "crossbeam-deque",
1032 "either",
1032 "either",
1033 "rayon-core",
1033 "rayon-core",
1034 ]
1034 ]
1035
1035
1036 [[package]]
1036 [[package]]
1037 name = "rayon-core"
1037 name = "rayon-core"
1038 version = "1.9.3"
1038 version = "1.9.3"
1039 source = "registry+https://github.com/rust-lang/crates.io-index"
1039 source = "registry+https://github.com/rust-lang/crates.io-index"
1040 checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
1040 checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
1041 dependencies = [
1041 dependencies = [
1042 "crossbeam-channel",
1042 "crossbeam-channel",
1043 "crossbeam-deque",
1043 "crossbeam-deque",
1044 "crossbeam-utils",
1044 "crossbeam-utils",
1045 "num_cpus",
1045 "num_cpus",
1046 ]
1046 ]
1047
1047
1048 [[package]]
1048 [[package]]
1049 name = "redox_syscall"
1049 name = "redox_syscall"
1050 version = "0.2.16"
1050 version = "0.2.16"
1051 source = "registry+https://github.com/rust-lang/crates.io-index"
1051 source = "registry+https://github.com/rust-lang/crates.io-index"
1052 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1052 checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1053 dependencies = [
1053 dependencies = [
1054 "bitflags",
1054 "bitflags",
1055 ]
1055 ]
1056
1056
1057 [[package]]
1057 [[package]]
1058 name = "regex"
1058 name = "regex"
1059 version = "1.7.0"
1059 version = "1.7.0"
1060 source = "registry+https://github.com/rust-lang/crates.io-index"
1060 source = "registry+https://github.com/rust-lang/crates.io-index"
1061 checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
1061 checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
1062 dependencies = [
1062 dependencies = [
1063 "aho-corasick",
1063 "aho-corasick",
1064 "memchr",
1064 "memchr",
1065 "regex-syntax",
1065 "regex-syntax",
1066 ]
1066 ]
1067
1067
1068 [[package]]
1068 [[package]]
1069 name = "regex-syntax"
1069 name = "regex-syntax"
1070 version = "0.6.28"
1070 version = "0.6.28"
1071 source = "registry+https://github.com/rust-lang/crates.io-index"
1071 source = "registry+https://github.com/rust-lang/crates.io-index"
1072 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
1072 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
1073
1073
1074 [[package]]
1074 [[package]]
1075 name = "remove_dir_all"
1075 name = "remove_dir_all"
1076 version = "0.5.3"
1076 version = "0.5.3"
1077 source = "registry+https://github.com/rust-lang/crates.io-index"
1077 source = "registry+https://github.com/rust-lang/crates.io-index"
1078 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1078 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1079 dependencies = [
1079 dependencies = [
1080 "winapi",
1080 "winapi",
1081 ]
1081 ]
1082
1082
1083 [[package]]
1083 [[package]]
1084 name = "rhg"
1084 name = "rhg"
1085 version = "0.1.0"
1085 version = "0.1.0"
1086 dependencies = [
1086 dependencies = [
1087 "atty",
1087 "atty",
1088 "chrono",
1088 "chrono",
1089 "clap",
1089 "clap",
1090 "derive_more",
1090 "derive_more",
1091 "env_logger",
1091 "env_logger",
1092 "format-bytes",
1092 "format-bytes",
1093 "hg-core",
1093 "hg-core",
1094 "home",
1094 "home",
1095 "lazy_static",
1095 "lazy_static",
1096 "log",
1096 "log",
1097 "micro-timer",
1097 "logging_timer",
1098 "rayon",
1098 "rayon",
1099 "regex",
1099 "regex",
1100 "users",
1100 "users",
1101 "which",
1101 "which",
1102 ]
1102 ]
1103
1103
1104 [[package]]
1104 [[package]]
1105 name = "rustc_version"
1105 name = "rustc_version"
1106 version = "0.4.0"
1106 version = "0.4.0"
1107 source = "registry+https://github.com/rust-lang/crates.io-index"
1107 source = "registry+https://github.com/rust-lang/crates.io-index"
1108 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1108 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1109 dependencies = [
1109 dependencies = [
1110 "semver",
1110 "semver",
1111 ]
1111 ]
1112
1112
1113 [[package]]
1113 [[package]]
1114 name = "same-file"
1114 name = "same-file"
1115 version = "1.0.6"
1115 version = "1.0.6"
1116 source = "registry+https://github.com/rust-lang/crates.io-index"
1116 source = "registry+https://github.com/rust-lang/crates.io-index"
1117 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1117 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1118 dependencies = [
1118 dependencies = [
1119 "winapi-util",
1119 "winapi-util",
1120 ]
1120 ]
1121
1121
1122 [[package]]
1122 [[package]]
1123 name = "scopeguard"
1123 name = "scopeguard"
1124 version = "1.1.0"
1124 version = "1.1.0"
1125 source = "registry+https://github.com/rust-lang/crates.io-index"
1125 source = "registry+https://github.com/rust-lang/crates.io-index"
1126 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1126 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1127
1127
1128 [[package]]
1128 [[package]]
1129 name = "scratch"
1129 name = "scratch"
1130 version = "1.0.2"
1130 version = "1.0.2"
1131 source = "registry+https://github.com/rust-lang/crates.io-index"
1131 source = "registry+https://github.com/rust-lang/crates.io-index"
1132 checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
1132 checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
1133
1133
1134 [[package]]
1134 [[package]]
1135 name = "semver"
1135 name = "semver"
1136 version = "1.0.14"
1136 version = "1.0.14"
1137 source = "registry+https://github.com/rust-lang/crates.io-index"
1137 source = "registry+https://github.com/rust-lang/crates.io-index"
1138 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1138 checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
1139
1139
1140 [[package]]
1140 [[package]]
1141 name = "sha-1"
1141 name = "sha-1"
1142 version = "0.9.8"
1142 version = "0.9.8"
1143 source = "registry+https://github.com/rust-lang/crates.io-index"
1143 source = "registry+https://github.com/rust-lang/crates.io-index"
1144 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
1144 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
1145 dependencies = [
1145 dependencies = [
1146 "block-buffer 0.9.0",
1146 "block-buffer 0.9.0",
1147 "cfg-if",
1147 "cfg-if",
1148 "cpufeatures",
1148 "cpufeatures",
1149 "digest 0.9.0",
1149 "digest 0.9.0",
1150 "opaque-debug",
1150 "opaque-debug",
1151 ]
1151 ]
1152
1152
1153 [[package]]
1153 [[package]]
1154 name = "sha-1"
1154 name = "sha-1"
1155 version = "0.10.0"
1155 version = "0.10.0"
1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1157 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1157 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1158 dependencies = [
1158 dependencies = [
1159 "cfg-if",
1159 "cfg-if",
1160 "cpufeatures",
1160 "cpufeatures",
1161 "digest 0.10.5",
1161 "digest 0.10.5",
1162 ]
1162 ]
1163
1163
1164 [[package]]
1164 [[package]]
1165 name = "sized-chunks"
1165 name = "sized-chunks"
1166 version = "0.6.5"
1166 version = "0.6.5"
1167 source = "registry+https://github.com/rust-lang/crates.io-index"
1167 source = "registry+https://github.com/rust-lang/crates.io-index"
1168 checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
1168 checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
1169 dependencies = [
1169 dependencies = [
1170 "bitmaps",
1170 "bitmaps",
1171 "typenum",
1171 "typenum",
1172 ]
1172 ]
1173
1173
1174 [[package]]
1174 [[package]]
1175 name = "stable_deref_trait"
1175 name = "stable_deref_trait"
1176 version = "1.2.0"
1176 version = "1.2.0"
1177 source = "registry+https://github.com/rust-lang/crates.io-index"
1177 source = "registry+https://github.com/rust-lang/crates.io-index"
1178 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1178 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1179
1179
1180 [[package]]
1180 [[package]]
1181 name = "static_assertions"
1181 name = "static_assertions"
1182 version = "1.1.0"
1182 version = "1.1.0"
1183 source = "registry+https://github.com/rust-lang/crates.io-index"
1183 source = "registry+https://github.com/rust-lang/crates.io-index"
1184 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1184 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1185
1185
1186 [[package]]
1186 [[package]]
1187 name = "strsim"
1187 name = "strsim"
1188 version = "0.10.0"
1188 version = "0.10.0"
1189 source = "registry+https://github.com/rust-lang/crates.io-index"
1189 source = "registry+https://github.com/rust-lang/crates.io-index"
1190 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1190 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1191
1191
1192 [[package]]
1192 [[package]]
1193 name = "syn"
1193 name = "syn"
1194 version = "1.0.103"
1194 version = "1.0.103"
1195 source = "registry+https://github.com/rust-lang/crates.io-index"
1195 source = "registry+https://github.com/rust-lang/crates.io-index"
1196 checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
1196 checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
1197 dependencies = [
1197 dependencies = [
1198 "proc-macro2",
1198 "proc-macro2",
1199 "quote",
1199 "quote",
1200 "unicode-ident",
1200 "unicode-ident",
1201 ]
1201 ]
1202
1202
1203 [[package]]
1203 [[package]]
1204 name = "tempfile"
1204 name = "tempfile"
1205 version = "3.3.0"
1205 version = "3.3.0"
1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1207 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1207 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1208 dependencies = [
1208 dependencies = [
1209 "cfg-if",
1209 "cfg-if",
1210 "fastrand",
1210 "fastrand",
1211 "libc",
1211 "libc",
1212 "redox_syscall",
1212 "redox_syscall",
1213 "remove_dir_all",
1213 "remove_dir_all",
1214 "winapi",
1214 "winapi",
1215 ]
1215 ]
1216
1216
1217 [[package]]
1217 [[package]]
1218 name = "termcolor"
1218 name = "termcolor"
1219 version = "1.1.3"
1219 version = "1.1.3"
1220 source = "registry+https://github.com/rust-lang/crates.io-index"
1220 source = "registry+https://github.com/rust-lang/crates.io-index"
1221 checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1221 checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1222 dependencies = [
1222 dependencies = [
1223 "winapi-util",
1223 "winapi-util",
1224 ]
1224 ]
1225
1225
1226 [[package]]
1226 [[package]]
1227 name = "thread_local"
1227 name = "thread_local"
1228 version = "1.1.4"
1228 version = "1.1.4"
1229 source = "registry+https://github.com/rust-lang/crates.io-index"
1229 source = "registry+https://github.com/rust-lang/crates.io-index"
1230 checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
1230 checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
1231 dependencies = [
1231 dependencies = [
1232 "once_cell",
1232 "once_cell",
1233 ]
1233 ]
1234
1234
1235 [[package]]
1235 [[package]]
1236 name = "time"
1236 name = "time"
1237 version = "0.1.44"
1237 version = "0.1.44"
1238 source = "registry+https://github.com/rust-lang/crates.io-index"
1238 source = "registry+https://github.com/rust-lang/crates.io-index"
1239 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1239 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1240 dependencies = [
1240 dependencies = [
1241 "libc",
1241 "libc",
1242 "wasi 0.10.0+wasi-snapshot-preview1",
1242 "wasi 0.10.0+wasi-snapshot-preview1",
1243 "winapi",
1243 "winapi",
1244 ]
1244 ]
1245
1245
1246 [[package]]
1246 [[package]]
1247 name = "twox-hash"
1247 name = "twox-hash"
1248 version = "1.6.3"
1248 version = "1.6.3"
1249 source = "registry+https://github.com/rust-lang/crates.io-index"
1249 source = "registry+https://github.com/rust-lang/crates.io-index"
1250 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
1250 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
1251 dependencies = [
1251 dependencies = [
1252 "cfg-if",
1252 "cfg-if",
1253 "rand 0.8.5",
1253 "rand 0.8.5",
1254 "static_assertions",
1254 "static_assertions",
1255 ]
1255 ]
1256
1256
1257 [[package]]
1257 [[package]]
1258 name = "typenum"
1258 name = "typenum"
1259 version = "1.15.0"
1259 version = "1.15.0"
1260 source = "registry+https://github.com/rust-lang/crates.io-index"
1260 source = "registry+https://github.com/rust-lang/crates.io-index"
1261 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
1261 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
1262
1262
1263 [[package]]
1263 [[package]]
1264 name = "unicode-ident"
1264 name = "unicode-ident"
1265 version = "1.0.5"
1265 version = "1.0.5"
1266 source = "registry+https://github.com/rust-lang/crates.io-index"
1266 source = "registry+https://github.com/rust-lang/crates.io-index"
1267 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
1267 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
1268
1268
1269 [[package]]
1269 [[package]]
1270 name = "unicode-width"
1270 name = "unicode-width"
1271 version = "0.1.10"
1271 version = "0.1.10"
1272 source = "registry+https://github.com/rust-lang/crates.io-index"
1272 source = "registry+https://github.com/rust-lang/crates.io-index"
1273 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
1273 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
1274
1274
1275 [[package]]
1275 [[package]]
1276 name = "users"
1276 name = "users"
1277 version = "0.11.0"
1277 version = "0.11.0"
1278 source = "registry+https://github.com/rust-lang/crates.io-index"
1278 source = "registry+https://github.com/rust-lang/crates.io-index"
1279 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1279 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1280 dependencies = [
1280 dependencies = [
1281 "libc",
1281 "libc",
1282 "log",
1282 "log",
1283 ]
1283 ]
1284
1284
1285 [[package]]
1285 [[package]]
1286 name = "vcpkg"
1286 name = "vcpkg"
1287 version = "0.2.15"
1287 version = "0.2.15"
1288 source = "registry+https://github.com/rust-lang/crates.io-index"
1288 source = "registry+https://github.com/rust-lang/crates.io-index"
1289 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1289 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1290
1290
1291 [[package]]
1291 [[package]]
1292 name = "vcsgraph"
1292 name = "vcsgraph"
1293 version = "0.2.0"
1293 version = "0.2.0"
1294 source = "registry+https://github.com/rust-lang/crates.io-index"
1294 source = "registry+https://github.com/rust-lang/crates.io-index"
1295 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1295 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1296 dependencies = [
1296 dependencies = [
1297 "hex",
1297 "hex",
1298 "rand 0.7.3",
1298 "rand 0.7.3",
1299 "sha-1 0.9.8",
1299 "sha-1 0.9.8",
1300 ]
1300 ]
1301
1301
1302 [[package]]
1302 [[package]]
1303 name = "version_check"
1303 name = "version_check"
1304 version = "0.9.4"
1304 version = "0.9.4"
1305 source = "registry+https://github.com/rust-lang/crates.io-index"
1305 source = "registry+https://github.com/rust-lang/crates.io-index"
1306 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
1306 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
1307
1307
1308 [[package]]
1308 [[package]]
1309 name = "wasi"
1309 name = "wasi"
1310 version = "0.9.0+wasi-snapshot-preview1"
1310 version = "0.9.0+wasi-snapshot-preview1"
1311 source = "registry+https://github.com/rust-lang/crates.io-index"
1311 source = "registry+https://github.com/rust-lang/crates.io-index"
1312 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1312 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1313
1313
1314 [[package]]
1314 [[package]]
1315 name = "wasi"
1315 name = "wasi"
1316 version = "0.10.0+wasi-snapshot-preview1"
1316 version = "0.10.0+wasi-snapshot-preview1"
1317 source = "registry+https://github.com/rust-lang/crates.io-index"
1317 source = "registry+https://github.com/rust-lang/crates.io-index"
1318 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1318 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1319
1319
1320 [[package]]
1320 [[package]]
1321 name = "wasi"
1321 name = "wasi"
1322 version = "0.11.0+wasi-snapshot-preview1"
1322 version = "0.11.0+wasi-snapshot-preview1"
1323 source = "registry+https://github.com/rust-lang/crates.io-index"
1323 source = "registry+https://github.com/rust-lang/crates.io-index"
1324 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1324 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1325
1325
1326 [[package]]
1326 [[package]]
1327 name = "wasm-bindgen"
1327 name = "wasm-bindgen"
1328 version = "0.2.83"
1328 version = "0.2.83"
1329 source = "registry+https://github.com/rust-lang/crates.io-index"
1329 source = "registry+https://github.com/rust-lang/crates.io-index"
1330 checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
1330 checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
1331 dependencies = [
1331 dependencies = [
1332 "cfg-if",
1332 "cfg-if",
1333 "wasm-bindgen-macro",
1333 "wasm-bindgen-macro",
1334 ]
1334 ]
1335
1335
1336 [[package]]
1336 [[package]]
1337 name = "wasm-bindgen-backend"
1337 name = "wasm-bindgen-backend"
1338 version = "0.2.83"
1338 version = "0.2.83"
1339 source = "registry+https://github.com/rust-lang/crates.io-index"
1339 source = "registry+https://github.com/rust-lang/crates.io-index"
1340 checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
1340 checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
1341 dependencies = [
1341 dependencies = [
1342 "bumpalo",
1342 "bumpalo",
1343 "log",
1343 "log",
1344 "once_cell",
1344 "once_cell",
1345 "proc-macro2",
1345 "proc-macro2",
1346 "quote",
1346 "quote",
1347 "syn",
1347 "syn",
1348 "wasm-bindgen-shared",
1348 "wasm-bindgen-shared",
1349 ]
1349 ]
1350
1350
1351 [[package]]
1351 [[package]]
1352 name = "wasm-bindgen-macro"
1352 name = "wasm-bindgen-macro"
1353 version = "0.2.83"
1353 version = "0.2.83"
1354 source = "registry+https://github.com/rust-lang/crates.io-index"
1354 source = "registry+https://github.com/rust-lang/crates.io-index"
1355 checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
1355 checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
1356 dependencies = [
1356 dependencies = [
1357 "quote",
1357 "quote",
1358 "wasm-bindgen-macro-support",
1358 "wasm-bindgen-macro-support",
1359 ]
1359 ]
1360
1360
1361 [[package]]
1361 [[package]]
1362 name = "wasm-bindgen-macro-support"
1362 name = "wasm-bindgen-macro-support"
1363 version = "0.2.83"
1363 version = "0.2.83"
1364 source = "registry+https://github.com/rust-lang/crates.io-index"
1364 source = "registry+https://github.com/rust-lang/crates.io-index"
1365 checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
1365 checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
1366 dependencies = [
1366 dependencies = [
1367 "proc-macro2",
1367 "proc-macro2",
1368 "quote",
1368 "quote",
1369 "syn",
1369 "syn",
1370 "wasm-bindgen-backend",
1370 "wasm-bindgen-backend",
1371 "wasm-bindgen-shared",
1371 "wasm-bindgen-shared",
1372 ]
1372 ]
1373
1373
1374 [[package]]
1374 [[package]]
1375 name = "wasm-bindgen-shared"
1375 name = "wasm-bindgen-shared"
1376 version = "0.2.83"
1376 version = "0.2.83"
1377 source = "registry+https://github.com/rust-lang/crates.io-index"
1377 source = "registry+https://github.com/rust-lang/crates.io-index"
1378 checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
1378 checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
1379
1379
1380 [[package]]
1380 [[package]]
1381 name = "which"
1381 name = "which"
1382 version = "4.3.0"
1382 version = "4.3.0"
1383 source = "registry+https://github.com/rust-lang/crates.io-index"
1383 source = "registry+https://github.com/rust-lang/crates.io-index"
1384 checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
1384 checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
1385 dependencies = [
1385 dependencies = [
1386 "either",
1386 "either",
1387 "libc",
1387 "libc",
1388 "once_cell",
1388 "once_cell",
1389 ]
1389 ]
1390
1390
1391 [[package]]
1391 [[package]]
1392 name = "winapi"
1392 name = "winapi"
1393 version = "0.3.9"
1393 version = "0.3.9"
1394 source = "registry+https://github.com/rust-lang/crates.io-index"
1394 source = "registry+https://github.com/rust-lang/crates.io-index"
1395 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1395 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1396 dependencies = [
1396 dependencies = [
1397 "winapi-i686-pc-windows-gnu",
1397 "winapi-i686-pc-windows-gnu",
1398 "winapi-x86_64-pc-windows-gnu",
1398 "winapi-x86_64-pc-windows-gnu",
1399 ]
1399 ]
1400
1400
1401 [[package]]
1401 [[package]]
1402 name = "winapi-i686-pc-windows-gnu"
1402 name = "winapi-i686-pc-windows-gnu"
1403 version = "0.4.0"
1403 version = "0.4.0"
1404 source = "registry+https://github.com/rust-lang/crates.io-index"
1404 source = "registry+https://github.com/rust-lang/crates.io-index"
1405 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1405 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1406
1406
1407 [[package]]
1407 [[package]]
1408 name = "winapi-util"
1408 name = "winapi-util"
1409 version = "0.1.5"
1409 version = "0.1.5"
1410 source = "registry+https://github.com/rust-lang/crates.io-index"
1410 source = "registry+https://github.com/rust-lang/crates.io-index"
1411 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1411 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1412 dependencies = [
1412 dependencies = [
1413 "winapi",
1413 "winapi",
1414 ]
1414 ]
1415
1415
1416 [[package]]
1416 [[package]]
1417 name = "winapi-x86_64-pc-windows-gnu"
1417 name = "winapi-x86_64-pc-windows-gnu"
1418 version = "0.4.0"
1418 version = "0.4.0"
1419 source = "registry+https://github.com/rust-lang/crates.io-index"
1419 source = "registry+https://github.com/rust-lang/crates.io-index"
1420 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1420 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1421
1421
1422 [[package]]
1422 [[package]]
1423 name = "yansi"
1423 name = "yansi"
1424 version = "0.5.1"
1424 version = "0.5.1"
1425 source = "registry+https://github.com/rust-lang/crates.io-index"
1425 source = "registry+https://github.com/rust-lang/crates.io-index"
1426 checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
1426 checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
1427
1427
1428 [[package]]
1428 [[package]]
1429 name = "zstd"
1429 name = "zstd"
1430 version = "0.11.2+zstd.1.5.2"
1430 version = "0.11.2+zstd.1.5.2"
1431 source = "registry+https://github.com/rust-lang/crates.io-index"
1431 source = "registry+https://github.com/rust-lang/crates.io-index"
1432 checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
1432 checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
1433 dependencies = [
1433 dependencies = [
1434 "zstd-safe",
1434 "zstd-safe",
1435 ]
1435 ]
1436
1436
1437 [[package]]
1437 [[package]]
1438 name = "zstd-safe"
1438 name = "zstd-safe"
1439 version = "5.0.2+zstd.1.5.2"
1439 version = "5.0.2+zstd.1.5.2"
1440 source = "registry+https://github.com/rust-lang/crates.io-index"
1440 source = "registry+https://github.com/rust-lang/crates.io-index"
1441 checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
1441 checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
1442 dependencies = [
1442 dependencies = [
1443 "libc",
1443 "libc",
1444 "zstd-sys",
1444 "zstd-sys",
1445 ]
1445 ]
1446
1446
1447 [[package]]
1447 [[package]]
1448 name = "zstd-sys"
1448 name = "zstd-sys"
1449 version = "2.0.1+zstd.1.5.2"
1449 version = "2.0.1+zstd.1.5.2"
1450 source = "registry+https://github.com/rust-lang/crates.io-index"
1450 source = "registry+https://github.com/rust-lang/crates.io-index"
1451 checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
1451 checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
1452 dependencies = [
1452 dependencies = [
1453 "cc",
1453 "cc",
1454 "libc",
1454 "libc",
1455 ]
1455 ]
@@ -1,52 +1,52 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 = "2021"
6 edition = "2021"
7
7
8 [lib]
8 [lib]
9 name = "hg"
9 name = "hg"
10
10
11 [dependencies]
11 [dependencies]
12 bitflags = "1.3.2"
12 bitflags = "1.3.2"
13 bytes-cast = "0.2.0"
13 bytes-cast = "0.2.0"
14 byteorder = "1.4.3"
14 byteorder = "1.4.3"
15 derive_more = "0.99.17"
15 derive_more = "0.99.17"
16 hashbrown = { version = "0.13.1", features = ["rayon"] }
16 hashbrown = { version = "0.13.1", features = ["rayon"] }
17 home = "0.5.4"
17 home = "0.5.4"
18 im-rc = "15.1.0"
18 im-rc = "15.1.0"
19 itertools = "0.10.5"
19 itertools = "0.10.5"
20 lazy_static = "1.4.0"
20 lazy_static = "1.4.0"
21 libc = "0.2.137"
21 libc = "0.2.137"
22 logging_timer = "1.1.0"
22 ouroboros = "0.15.5"
23 ouroboros = "0.15.5"
23 rand = "0.8.5"
24 rand = "0.8.5"
24 rand_pcg = "0.3.1"
25 rand_pcg = "0.3.1"
25 rand_distr = "0.4.3"
26 rand_distr = "0.4.3"
26 rayon = "1.5.3"
27 rayon = "1.5.3"
27 regex = "1.7.0"
28 regex = "1.7.0"
28 sha-1 = "0.10.0"
29 sha-1 = "0.10.0"
29 twox-hash = "1.6.3"
30 twox-hash = "1.6.3"
30 same-file = "1.0.6"
31 same-file = "1.0.6"
31 tempfile = "3.3.0"
32 tempfile = "3.3.0"
32 thread_local = "1.1.4"
33 thread_local = "1.1.4"
33 crossbeam-channel = "0.5.6"
34 crossbeam-channel = "0.5.6"
34 micro-timer = "0.4.0"
35 log = "0.4.17"
35 log = "0.4.17"
36 memmap2 = { version = "0.5.8", features = ["stable_deref_trait"] }
36 memmap2 = { version = "0.5.8", features = ["stable_deref_trait"] }
37 zstd = "0.11.2"
37 zstd = "0.11.2"
38 format-bytes = "0.3.0"
38 format-bytes = "0.3.0"
39 # once_cell 1.15 uses edition 2021, while the heptapod CI
39 # once_cell 1.15 uses edition 2021, while the heptapod CI
40 # uses an old version of Cargo that doesn't support it.
40 # uses an old version of Cargo that doesn't support it.
41 once_cell = "1.16.0"
41 once_cell = "1.16.0"
42
42
43 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
43 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
44 # we have a clearer view of which backend is the fastest.
44 # we have a clearer view of which backend is the fastest.
45 [dependencies.flate2]
45 [dependencies.flate2]
46 version = "1.0.24"
46 version = "1.0.24"
47 features = ["zlib"]
47 features = ["zlib"]
48 default-features = false
48 default-features = false
49
49
50 [dev-dependencies]
50 [dev-dependencies]
51 clap = { version = "4.0.24", features = ["derive"] }
51 clap = { version = "4.0.24", features = ["derive"] }
52 pretty_assertions = "1.1.0"
52 pretty_assertions = "1.1.0"
@@ -1,136 +1,135 b''
1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
4 // GNU General Public License version 2 or any later version.
5
5
6 use crate::errors::HgError;
6 use crate::errors::HgError;
7 use crate::utils::hg_path::HgPath;
7 use crate::utils::hg_path::HgPath;
8 use crate::{dirstate::EntryState, DirstateEntry, DirstateParents};
8 use crate::{dirstate::EntryState, DirstateEntry, DirstateParents};
9 use byteorder::{BigEndian, WriteBytesExt};
9 use byteorder::{BigEndian, WriteBytesExt};
10 use bytes_cast::{unaligned, BytesCast};
10 use bytes_cast::{unaligned, BytesCast};
11 use micro_timer::timed;
12
11
13 /// Parents are stored in the dirstate as byte hashes.
12 /// Parents are stored in the dirstate as byte hashes.
14 pub const PARENT_SIZE: usize = 20;
13 pub const PARENT_SIZE: usize = 20;
15 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
14 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
16 const MIN_ENTRY_SIZE: usize = 17;
15 const MIN_ENTRY_SIZE: usize = 17;
17
16
18 type ParseResult<'a> = (
17 type ParseResult<'a> = (
19 &'a DirstateParents,
18 &'a DirstateParents,
20 Vec<(&'a HgPath, DirstateEntry)>,
19 Vec<(&'a HgPath, DirstateEntry)>,
21 Vec<(&'a HgPath, &'a HgPath)>,
20 Vec<(&'a HgPath, &'a HgPath)>,
22 );
21 );
23
22
24 pub fn parse_dirstate_parents(
23 pub fn parse_dirstate_parents(
25 contents: &[u8],
24 contents: &[u8],
26 ) -> Result<&DirstateParents, HgError> {
25 ) -> Result<&DirstateParents, HgError> {
27 let (parents, _rest) = DirstateParents::from_bytes(contents)
26 let (parents, _rest) = DirstateParents::from_bytes(contents)
28 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
27 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
29 Ok(parents)
28 Ok(parents)
30 }
29 }
31
30
32 #[timed]
31 #[logging_timer::time("trace")]
33 pub fn parse_dirstate(contents: &[u8]) -> Result<ParseResult, HgError> {
32 pub fn parse_dirstate(contents: &[u8]) -> Result<ParseResult, HgError> {
34 let mut copies = Vec::new();
33 let mut copies = Vec::new();
35 let mut entries = Vec::new();
34 let mut entries = Vec::new();
36 let parents =
35 let parents =
37 parse_dirstate_entries(contents, |path, entry, copy_source| {
36 parse_dirstate_entries(contents, |path, entry, copy_source| {
38 if let Some(source) = copy_source {
37 if let Some(source) = copy_source {
39 copies.push((path, source));
38 copies.push((path, source));
40 }
39 }
41 entries.push((path, *entry));
40 entries.push((path, *entry));
42 Ok(())
41 Ok(())
43 })?;
42 })?;
44 Ok((parents, entries, copies))
43 Ok((parents, entries, copies))
45 }
44 }
46
45
47 #[derive(BytesCast)]
46 #[derive(BytesCast)]
48 #[repr(C)]
47 #[repr(C)]
49 struct RawEntry {
48 struct RawEntry {
50 state: u8,
49 state: u8,
51 mode: unaligned::I32Be,
50 mode: unaligned::I32Be,
52 size: unaligned::I32Be,
51 size: unaligned::I32Be,
53 mtime: unaligned::I32Be,
52 mtime: unaligned::I32Be,
54 length: unaligned::I32Be,
53 length: unaligned::I32Be,
55 }
54 }
56
55
57 pub fn parse_dirstate_entries<'a>(
56 pub fn parse_dirstate_entries<'a>(
58 mut contents: &'a [u8],
57 mut contents: &'a [u8],
59 mut each_entry: impl FnMut(
58 mut each_entry: impl FnMut(
60 &'a HgPath,
59 &'a HgPath,
61 &DirstateEntry,
60 &DirstateEntry,
62 Option<&'a HgPath>,
61 Option<&'a HgPath>,
63 ) -> Result<(), HgError>,
62 ) -> Result<(), HgError>,
64 ) -> Result<&'a DirstateParents, HgError> {
63 ) -> Result<&'a DirstateParents, HgError> {
65 let (parents, rest) = DirstateParents::from_bytes(contents)
64 let (parents, rest) = DirstateParents::from_bytes(contents)
66 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
65 .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
67 contents = rest;
66 contents = rest;
68 while !contents.is_empty() {
67 while !contents.is_empty() {
69 let (raw_entry, rest) = RawEntry::from_bytes(contents)
68 let (raw_entry, rest) = RawEntry::from_bytes(contents)
70 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
69 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
71
70
72 let entry = DirstateEntry::from_v1_data(
71 let entry = DirstateEntry::from_v1_data(
73 EntryState::try_from(raw_entry.state)?,
72 EntryState::try_from(raw_entry.state)?,
74 raw_entry.mode.get(),
73 raw_entry.mode.get(),
75 raw_entry.size.get(),
74 raw_entry.size.get(),
76 raw_entry.mtime.get(),
75 raw_entry.mtime.get(),
77 );
76 );
78 let (paths, rest) =
77 let (paths, rest) =
79 u8::slice_from_bytes(rest, raw_entry.length.get() as usize)
78 u8::slice_from_bytes(rest, raw_entry.length.get() as usize)
80 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
79 .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
81
80
82 // `paths` is either a single path, or two paths separated by a NULL
81 // `paths` is either a single path, or two paths separated by a NULL
83 // byte
82 // byte
84 let mut iter = paths.splitn(2, |&byte| byte == b'\0');
83 let mut iter = paths.splitn(2, |&byte| byte == b'\0');
85 let path = HgPath::new(
84 let path = HgPath::new(
86 iter.next().expect("splitn always yields at least one item"),
85 iter.next().expect("splitn always yields at least one item"),
87 );
86 );
88 let copy_source = iter.next().map(HgPath::new);
87 let copy_source = iter.next().map(HgPath::new);
89 each_entry(path, &entry, copy_source)?;
88 each_entry(path, &entry, copy_source)?;
90
89
91 contents = rest;
90 contents = rest;
92 }
91 }
93 Ok(parents)
92 Ok(parents)
94 }
93 }
95
94
96 fn packed_filename_and_copy_source_size(
95 fn packed_filename_and_copy_source_size(
97 filename: &HgPath,
96 filename: &HgPath,
98 copy_source: Option<&HgPath>,
97 copy_source: Option<&HgPath>,
99 ) -> usize {
98 ) -> usize {
100 filename.len()
99 filename.len()
101 + if let Some(source) = copy_source {
100 + if let Some(source) = copy_source {
102 b"\0".len() + source.len()
101 b"\0".len() + source.len()
103 } else {
102 } else {
104 0
103 0
105 }
104 }
106 }
105 }
107
106
108 pub fn packed_entry_size(
107 pub fn packed_entry_size(
109 filename: &HgPath,
108 filename: &HgPath,
110 copy_source: Option<&HgPath>,
109 copy_source: Option<&HgPath>,
111 ) -> usize {
110 ) -> usize {
112 MIN_ENTRY_SIZE
111 MIN_ENTRY_SIZE
113 + packed_filename_and_copy_source_size(filename, copy_source)
112 + packed_filename_and_copy_source_size(filename, copy_source)
114 }
113 }
115
114
116 pub fn pack_entry(
115 pub fn pack_entry(
117 filename: &HgPath,
116 filename: &HgPath,
118 entry: &DirstateEntry,
117 entry: &DirstateEntry,
119 copy_source: Option<&HgPath>,
118 copy_source: Option<&HgPath>,
120 packed: &mut Vec<u8>,
119 packed: &mut Vec<u8>,
121 ) {
120 ) {
122 let length = packed_filename_and_copy_source_size(filename, copy_source);
121 let length = packed_filename_and_copy_source_size(filename, copy_source);
123 let (state, mode, size, mtime) = entry.v1_data();
122 let (state, mode, size, mtime) = entry.v1_data();
124
123
125 // Unwrapping because `impl std::io::Write for Vec<u8>` never errors
124 // Unwrapping because `impl std::io::Write for Vec<u8>` never errors
126 packed.write_u8(state).unwrap();
125 packed.write_u8(state).unwrap();
127 packed.write_i32::<BigEndian>(mode).unwrap();
126 packed.write_i32::<BigEndian>(mode).unwrap();
128 packed.write_i32::<BigEndian>(size).unwrap();
127 packed.write_i32::<BigEndian>(size).unwrap();
129 packed.write_i32::<BigEndian>(mtime).unwrap();
128 packed.write_i32::<BigEndian>(mtime).unwrap();
130 packed.write_i32::<BigEndian>(length as i32).unwrap();
129 packed.write_i32::<BigEndian>(length as i32).unwrap();
131 packed.extend(filename.as_bytes());
130 packed.extend(filename.as_bytes());
132 if let Some(source) = copy_source {
131 if let Some(source) = copy_source {
133 packed.push(b'\0');
132 packed.push(b'\0');
134 packed.extend(source.as_bytes());
133 packed.extend(source.as_bytes());
135 }
134 }
136 }
135 }
@@ -1,1907 +1,1906 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
3 use std::borrow::Cow;
2 use std::borrow::Cow;
4 use std::path::PathBuf;
3 use std::path::PathBuf;
5
4
6 use super::on_disk;
5 use super::on_disk;
7 use super::on_disk::DirstateV2ParseError;
6 use super::on_disk::DirstateV2ParseError;
8 use super::owning::OwningDirstateMap;
7 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
8 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
9 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
10 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
11 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::CopyMapIter;
12 use crate::dirstate::CopyMapIter;
14 use crate::dirstate::DirstateV2Data;
13 use crate::dirstate::DirstateV2Data;
15 use crate::dirstate::ParentFileData;
14 use crate::dirstate::ParentFileData;
16 use crate::dirstate::StateMapIter;
15 use crate::dirstate::StateMapIter;
17 use crate::dirstate::TruncatedTimestamp;
16 use crate::dirstate::TruncatedTimestamp;
18 use crate::matchers::Matcher;
17 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
18 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
19 use crate::DirstateEntry;
21 use crate::DirstateError;
20 use crate::DirstateError;
22 use crate::DirstateMapError;
21 use crate::DirstateMapError;
23 use crate::DirstateParents;
22 use crate::DirstateParents;
24 use crate::DirstateStatus;
23 use crate::DirstateStatus;
25 use crate::FastHashbrownMap as FastHashMap;
24 use crate::FastHashbrownMap as FastHashMap;
26 use crate::PatternFileWarning;
25 use crate::PatternFileWarning;
27 use crate::StatusError;
26 use crate::StatusError;
28 use crate::StatusOptions;
27 use crate::StatusOptions;
29
28
30 /// Append to an existing data file if the amount of unreachable data (not used
29 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
30 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
31 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
32
34 #[derive(Debug, PartialEq, Eq)]
33 #[derive(Debug, PartialEq, Eq)]
35 /// Version of the on-disk format
34 /// Version of the on-disk format
36 pub enum DirstateVersion {
35 pub enum DirstateVersion {
37 V1,
36 V1,
38 V2,
37 V2,
39 }
38 }
40
39
41 #[derive(Debug)]
40 #[derive(Debug)]
42 pub struct DirstateMap<'on_disk> {
41 pub struct DirstateMap<'on_disk> {
43 /// Contents of the `.hg/dirstate` file
42 /// Contents of the `.hg/dirstate` file
44 pub(super) on_disk: &'on_disk [u8],
43 pub(super) on_disk: &'on_disk [u8],
45
44
46 pub(super) root: ChildNodes<'on_disk>,
45 pub(super) root: ChildNodes<'on_disk>,
47
46
48 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
47 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
49 pub(super) nodes_with_entry_count: u32,
48 pub(super) nodes_with_entry_count: u32,
50
49
51 /// Number of nodes anywhere in the tree that have
50 /// Number of nodes anywhere in the tree that have
52 /// `.copy_source.is_some()`.
51 /// `.copy_source.is_some()`.
53 pub(super) nodes_with_copy_source_count: u32,
52 pub(super) nodes_with_copy_source_count: u32,
54
53
55 /// See on_disk::Header
54 /// See on_disk::Header
56 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
55 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
57
56
58 /// How many bytes of `on_disk` are not used anymore
57 /// How many bytes of `on_disk` are not used anymore
59 pub(super) unreachable_bytes: u32,
58 pub(super) unreachable_bytes: u32,
60
59
61 /// Size of the data used to first load this `DirstateMap`. Used in case
60 /// Size of the data used to first load this `DirstateMap`. Used in case
62 /// we need to write some new metadata, but no new data on disk.
61 /// we need to write some new metadata, but no new data on disk.
63 pub(super) old_data_size: usize,
62 pub(super) old_data_size: usize,
64
63
65 pub(super) dirstate_version: DirstateVersion,
64 pub(super) dirstate_version: DirstateVersion,
66 }
65 }
67
66
68 /// Using a plain `HgPathBuf` of the full path from the repository root as a
67 /// Using a plain `HgPathBuf` of the full path from the repository root as a
69 /// map key would also work: all paths in a given map have the same parent
68 /// map key would also work: all paths in a given map have the same parent
70 /// path, so comparing full paths gives the same result as comparing base
69 /// path, so comparing full paths gives the same result as comparing base
71 /// names. However `HashMap` would waste time always re-hashing the same
70 /// names. However `HashMap` would waste time always re-hashing the same
72 /// string prefix.
71 /// string prefix.
73 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
72 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
74
73
75 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
74 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
76 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
75 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
77 #[derive(Debug)]
76 #[derive(Debug)]
78 pub(super) enum BorrowedPath<'tree, 'on_disk> {
77 pub(super) enum BorrowedPath<'tree, 'on_disk> {
79 InMemory(&'tree HgPathBuf),
78 InMemory(&'tree HgPathBuf),
80 OnDisk(&'on_disk HgPath),
79 OnDisk(&'on_disk HgPath),
81 }
80 }
82
81
83 #[derive(Debug)]
82 #[derive(Debug)]
84 pub(super) enum ChildNodes<'on_disk> {
83 pub(super) enum ChildNodes<'on_disk> {
85 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
84 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
86 OnDisk(&'on_disk [on_disk::Node]),
85 OnDisk(&'on_disk [on_disk::Node]),
87 }
86 }
88
87
89 #[derive(Debug)]
88 #[derive(Debug)]
90 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
89 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
91 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
90 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
92 OnDisk(&'on_disk [on_disk::Node]),
91 OnDisk(&'on_disk [on_disk::Node]),
93 }
92 }
94
93
95 #[derive(Debug)]
94 #[derive(Debug)]
96 pub(super) enum NodeRef<'tree, 'on_disk> {
95 pub(super) enum NodeRef<'tree, 'on_disk> {
97 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
96 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
98 OnDisk(&'on_disk on_disk::Node),
97 OnDisk(&'on_disk on_disk::Node),
99 }
98 }
100
99
101 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
100 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
102 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
101 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
103 match *self {
102 match *self {
104 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
103 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
105 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
104 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
106 }
105 }
107 }
106 }
108 }
107 }
109
108
110 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
109 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
111 type Target = HgPath;
110 type Target = HgPath;
112
111
113 fn deref(&self) -> &HgPath {
112 fn deref(&self) -> &HgPath {
114 match *self {
113 match *self {
115 BorrowedPath::InMemory(in_memory) => in_memory,
114 BorrowedPath::InMemory(in_memory) => in_memory,
116 BorrowedPath::OnDisk(on_disk) => on_disk,
115 BorrowedPath::OnDisk(on_disk) => on_disk,
117 }
116 }
118 }
117 }
119 }
118 }
120
119
121 impl Default for ChildNodes<'_> {
120 impl Default for ChildNodes<'_> {
122 fn default() -> Self {
121 fn default() -> Self {
123 ChildNodes::InMemory(Default::default())
122 ChildNodes::InMemory(Default::default())
124 }
123 }
125 }
124 }
126
125
127 impl<'on_disk> ChildNodes<'on_disk> {
126 impl<'on_disk> ChildNodes<'on_disk> {
128 pub(super) fn as_ref<'tree>(
127 pub(super) fn as_ref<'tree>(
129 &'tree self,
128 &'tree self,
130 ) -> ChildNodesRef<'tree, 'on_disk> {
129 ) -> ChildNodesRef<'tree, 'on_disk> {
131 match self {
130 match self {
132 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
131 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
133 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
132 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
134 }
133 }
135 }
134 }
136
135
137 pub(super) fn is_empty(&self) -> bool {
136 pub(super) fn is_empty(&self) -> bool {
138 match self {
137 match self {
139 ChildNodes::InMemory(nodes) => nodes.is_empty(),
138 ChildNodes::InMemory(nodes) => nodes.is_empty(),
140 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
139 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
141 }
140 }
142 }
141 }
143
142
144 fn make_mut(
143 fn make_mut(
145 &mut self,
144 &mut self,
146 on_disk: &'on_disk [u8],
145 on_disk: &'on_disk [u8],
147 unreachable_bytes: &mut u32,
146 unreachable_bytes: &mut u32,
148 ) -> Result<
147 ) -> Result<
149 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
148 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
150 DirstateV2ParseError,
149 DirstateV2ParseError,
151 > {
150 > {
152 match self {
151 match self {
153 ChildNodes::InMemory(nodes) => Ok(nodes),
152 ChildNodes::InMemory(nodes) => Ok(nodes),
154 ChildNodes::OnDisk(nodes) => {
153 ChildNodes::OnDisk(nodes) => {
155 *unreachable_bytes +=
154 *unreachable_bytes +=
156 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
155 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
157 let nodes = nodes
156 let nodes = nodes
158 .iter()
157 .iter()
159 .map(|node| {
158 .map(|node| {
160 Ok((
159 Ok((
161 node.path(on_disk)?,
160 node.path(on_disk)?,
162 node.to_in_memory_node(on_disk)?,
161 node.to_in_memory_node(on_disk)?,
163 ))
162 ))
164 })
163 })
165 .collect::<Result<_, _>>()?;
164 .collect::<Result<_, _>>()?;
166 *self = ChildNodes::InMemory(nodes);
165 *self = ChildNodes::InMemory(nodes);
167 match self {
166 match self {
168 ChildNodes::InMemory(nodes) => Ok(nodes),
167 ChildNodes::InMemory(nodes) => Ok(nodes),
169 ChildNodes::OnDisk(_) => unreachable!(),
168 ChildNodes::OnDisk(_) => unreachable!(),
170 }
169 }
171 }
170 }
172 }
171 }
173 }
172 }
174 }
173 }
175
174
176 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
175 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
177 pub(super) fn get(
176 pub(super) fn get(
178 &self,
177 &self,
179 base_name: &HgPath,
178 base_name: &HgPath,
180 on_disk: &'on_disk [u8],
179 on_disk: &'on_disk [u8],
181 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
180 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
182 match self {
181 match self {
183 ChildNodesRef::InMemory(nodes) => Ok(nodes
182 ChildNodesRef::InMemory(nodes) => Ok(nodes
184 .get_key_value(base_name)
183 .get_key_value(base_name)
185 .map(|(k, v)| NodeRef::InMemory(k, v))),
184 .map(|(k, v)| NodeRef::InMemory(k, v))),
186 ChildNodesRef::OnDisk(nodes) => {
185 ChildNodesRef::OnDisk(nodes) => {
187 let mut parse_result = Ok(());
186 let mut parse_result = Ok(());
188 let search_result = nodes.binary_search_by(|node| {
187 let search_result = nodes.binary_search_by(|node| {
189 match node.base_name(on_disk) {
188 match node.base_name(on_disk) {
190 Ok(node_base_name) => node_base_name.cmp(base_name),
189 Ok(node_base_name) => node_base_name.cmp(base_name),
191 Err(e) => {
190 Err(e) => {
192 parse_result = Err(e);
191 parse_result = Err(e);
193 // Dummy comparison result, `search_result` won’t
192 // Dummy comparison result, `search_result` won’t
194 // be used since `parse_result` is an error
193 // be used since `parse_result` is an error
195 std::cmp::Ordering::Equal
194 std::cmp::Ordering::Equal
196 }
195 }
197 }
196 }
198 });
197 });
199 parse_result.map(|()| {
198 parse_result.map(|()| {
200 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
199 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
201 })
200 })
202 }
201 }
203 }
202 }
204 }
203 }
205
204
206 /// Iterate in undefined order
205 /// Iterate in undefined order
207 pub(super) fn iter(
206 pub(super) fn iter(
208 &self,
207 &self,
209 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
208 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
210 match self {
209 match self {
211 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
210 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
212 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
213 ),
212 ),
214 ChildNodesRef::OnDisk(nodes) => {
213 ChildNodesRef::OnDisk(nodes) => {
215 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
214 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
216 }
215 }
217 }
216 }
218 }
217 }
219
218
220 /// Iterate in parallel in undefined order
219 /// Iterate in parallel in undefined order
221 pub(super) fn par_iter(
220 pub(super) fn par_iter(
222 &self,
221 &self,
223 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
222 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
224 {
223 {
225 use rayon::prelude::*;
224 use rayon::prelude::*;
226 match self {
225 match self {
227 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
226 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
228 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
227 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
229 ),
228 ),
230 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
229 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
231 nodes.par_iter().map(NodeRef::OnDisk),
230 nodes.par_iter().map(NodeRef::OnDisk),
232 ),
231 ),
233 }
232 }
234 }
233 }
235
234
236 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
235 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
237 match self {
236 match self {
238 ChildNodesRef::InMemory(nodes) => {
237 ChildNodesRef::InMemory(nodes) => {
239 let mut vec: Vec<_> = nodes
238 let mut vec: Vec<_> = nodes
240 .iter()
239 .iter()
241 .map(|(k, v)| NodeRef::InMemory(k, v))
240 .map(|(k, v)| NodeRef::InMemory(k, v))
242 .collect();
241 .collect();
243 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
242 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
244 match node {
243 match node {
245 NodeRef::InMemory(path, _node) => path.base_name(),
244 NodeRef::InMemory(path, _node) => path.base_name(),
246 NodeRef::OnDisk(_) => unreachable!(),
245 NodeRef::OnDisk(_) => unreachable!(),
247 }
246 }
248 }
247 }
249 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
248 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
250 // value: https://github.com/rust-lang/rust/issues/34162
249 // value: https://github.com/rust-lang/rust/issues/34162
251 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
250 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
252 vec
251 vec
253 }
252 }
254 ChildNodesRef::OnDisk(nodes) => {
253 ChildNodesRef::OnDisk(nodes) => {
255 // Nodes on disk are already sorted
254 // Nodes on disk are already sorted
256 nodes.iter().map(NodeRef::OnDisk).collect()
255 nodes.iter().map(NodeRef::OnDisk).collect()
257 }
256 }
258 }
257 }
259 }
258 }
260 }
259 }
261
260
262 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
261 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
263 pub(super) fn full_path(
262 pub(super) fn full_path(
264 &self,
263 &self,
265 on_disk: &'on_disk [u8],
264 on_disk: &'on_disk [u8],
266 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
265 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
267 match self {
266 match self {
268 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
267 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
269 NodeRef::OnDisk(node) => node.full_path(on_disk),
268 NodeRef::OnDisk(node) => node.full_path(on_disk),
270 }
269 }
271 }
270 }
272
271
273 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
272 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
274 /// HgPath>` detached from `'tree`
273 /// HgPath>` detached from `'tree`
275 pub(super) fn full_path_borrowed(
274 pub(super) fn full_path_borrowed(
276 &self,
275 &self,
277 on_disk: &'on_disk [u8],
276 on_disk: &'on_disk [u8],
278 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
277 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
279 match self {
278 match self {
280 NodeRef::InMemory(path, _node) => match path.full_path() {
279 NodeRef::InMemory(path, _node) => match path.full_path() {
281 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
280 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
282 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
281 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
283 },
282 },
284 NodeRef::OnDisk(node) => {
283 NodeRef::OnDisk(node) => {
285 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
284 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
286 }
285 }
287 }
286 }
288 }
287 }
289
288
290 pub(super) fn base_name(
289 pub(super) fn base_name(
291 &self,
290 &self,
292 on_disk: &'on_disk [u8],
291 on_disk: &'on_disk [u8],
293 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
292 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
294 match self {
293 match self {
295 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
294 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
296 NodeRef::OnDisk(node) => node.base_name(on_disk),
295 NodeRef::OnDisk(node) => node.base_name(on_disk),
297 }
296 }
298 }
297 }
299
298
300 pub(super) fn children(
299 pub(super) fn children(
301 &self,
300 &self,
302 on_disk: &'on_disk [u8],
301 on_disk: &'on_disk [u8],
303 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
302 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
304 match self {
303 match self {
305 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
304 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
306 NodeRef::OnDisk(node) => {
305 NodeRef::OnDisk(node) => {
307 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
306 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
308 }
307 }
309 }
308 }
310 }
309 }
311
310
312 pub(super) fn has_copy_source(&self) -> bool {
311 pub(super) fn has_copy_source(&self) -> bool {
313 match self {
312 match self {
314 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
313 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
315 NodeRef::OnDisk(node) => node.has_copy_source(),
314 NodeRef::OnDisk(node) => node.has_copy_source(),
316 }
315 }
317 }
316 }
318
317
319 pub(super) fn copy_source(
318 pub(super) fn copy_source(
320 &self,
319 &self,
321 on_disk: &'on_disk [u8],
320 on_disk: &'on_disk [u8],
322 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
321 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
323 match self {
322 match self {
324 NodeRef::InMemory(_path, node) => {
323 NodeRef::InMemory(_path, node) => {
325 Ok(node.copy_source.as_ref().map(|s| &**s))
324 Ok(node.copy_source.as_ref().map(|s| &**s))
326 }
325 }
327 NodeRef::OnDisk(node) => node.copy_source(on_disk),
326 NodeRef::OnDisk(node) => node.copy_source(on_disk),
328 }
327 }
329 }
328 }
330 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
329 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
331 /// HgPath>` detached from `'tree`
330 /// HgPath>` detached from `'tree`
332 pub(super) fn copy_source_borrowed(
331 pub(super) fn copy_source_borrowed(
333 &self,
332 &self,
334 on_disk: &'on_disk [u8],
333 on_disk: &'on_disk [u8],
335 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
334 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
336 {
335 {
337 Ok(match self {
336 Ok(match self {
338 NodeRef::InMemory(_path, node) => {
337 NodeRef::InMemory(_path, node) => {
339 node.copy_source.as_ref().map(|source| match source {
338 node.copy_source.as_ref().map(|source| match source {
340 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
339 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
341 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
340 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
342 })
341 })
343 }
342 }
344 NodeRef::OnDisk(node) => node
343 NodeRef::OnDisk(node) => node
345 .copy_source(on_disk)?
344 .copy_source(on_disk)?
346 .map(|source| BorrowedPath::OnDisk(source)),
345 .map(|source| BorrowedPath::OnDisk(source)),
347 })
346 })
348 }
347 }
349
348
350 pub(super) fn entry(
349 pub(super) fn entry(
351 &self,
350 &self,
352 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
351 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
353 match self {
352 match self {
354 NodeRef::InMemory(_path, node) => {
353 NodeRef::InMemory(_path, node) => {
355 Ok(node.data.as_entry().copied())
354 Ok(node.data.as_entry().copied())
356 }
355 }
357 NodeRef::OnDisk(node) => node.entry(),
356 NodeRef::OnDisk(node) => node.entry(),
358 }
357 }
359 }
358 }
360
359
361 pub(super) fn cached_directory_mtime(
360 pub(super) fn cached_directory_mtime(
362 &self,
361 &self,
363 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
362 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
364 match self {
363 match self {
365 NodeRef::InMemory(_path, node) => Ok(match node.data {
364 NodeRef::InMemory(_path, node) => Ok(match node.data {
366 NodeData::CachedDirectory { mtime } => Some(mtime),
365 NodeData::CachedDirectory { mtime } => Some(mtime),
367 _ => None,
366 _ => None,
368 }),
367 }),
369 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
368 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
370 }
369 }
371 }
370 }
372
371
373 pub(super) fn descendants_with_entry_count(&self) -> u32 {
372 pub(super) fn descendants_with_entry_count(&self) -> u32 {
374 match self {
373 match self {
375 NodeRef::InMemory(_path, node) => {
374 NodeRef::InMemory(_path, node) => {
376 node.descendants_with_entry_count
375 node.descendants_with_entry_count
377 }
376 }
378 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
377 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
379 }
378 }
380 }
379 }
381
380
382 pub(super) fn tracked_descendants_count(&self) -> u32 {
381 pub(super) fn tracked_descendants_count(&self) -> u32 {
383 match self {
382 match self {
384 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
383 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
385 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
384 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
386 }
385 }
387 }
386 }
388 }
387 }
389
388
390 /// Represents a file or a directory
389 /// Represents a file or a directory
391 #[derive(Default, Debug)]
390 #[derive(Default, Debug)]
392 pub(super) struct Node<'on_disk> {
391 pub(super) struct Node<'on_disk> {
393 pub(super) data: NodeData,
392 pub(super) data: NodeData,
394
393
395 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
394 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
396
395
397 pub(super) children: ChildNodes<'on_disk>,
396 pub(super) children: ChildNodes<'on_disk>,
398
397
399 /// How many (non-inclusive) descendants of this node have an entry.
398 /// How many (non-inclusive) descendants of this node have an entry.
400 pub(super) descendants_with_entry_count: u32,
399 pub(super) descendants_with_entry_count: u32,
401
400
402 /// How many (non-inclusive) descendants of this node have an entry whose
401 /// How many (non-inclusive) descendants of this node have an entry whose
403 /// state is "tracked".
402 /// state is "tracked".
404 pub(super) tracked_descendants_count: u32,
403 pub(super) tracked_descendants_count: u32,
405 }
404 }
406
405
407 #[derive(Debug)]
406 #[derive(Debug)]
408 pub(super) enum NodeData {
407 pub(super) enum NodeData {
409 Entry(DirstateEntry),
408 Entry(DirstateEntry),
410 CachedDirectory { mtime: TruncatedTimestamp },
409 CachedDirectory { mtime: TruncatedTimestamp },
411 None,
410 None,
412 }
411 }
413
412
414 impl Default for NodeData {
413 impl Default for NodeData {
415 fn default() -> Self {
414 fn default() -> Self {
416 NodeData::None
415 NodeData::None
417 }
416 }
418 }
417 }
419
418
420 impl NodeData {
419 impl NodeData {
421 fn has_entry(&self) -> bool {
420 fn has_entry(&self) -> bool {
422 match self {
421 match self {
423 NodeData::Entry(_) => true,
422 NodeData::Entry(_) => true,
424 _ => false,
423 _ => false,
425 }
424 }
426 }
425 }
427
426
428 fn as_entry(&self) -> Option<&DirstateEntry> {
427 fn as_entry(&self) -> Option<&DirstateEntry> {
429 match self {
428 match self {
430 NodeData::Entry(entry) => Some(entry),
429 NodeData::Entry(entry) => Some(entry),
431 _ => None,
430 _ => None,
432 }
431 }
433 }
432 }
434
433
435 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
434 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
436 match self {
435 match self {
437 NodeData::Entry(entry) => Some(entry),
436 NodeData::Entry(entry) => Some(entry),
438 _ => None,
437 _ => None,
439 }
438 }
440 }
439 }
441 }
440 }
442
441
443 impl<'on_disk> DirstateMap<'on_disk> {
442 impl<'on_disk> DirstateMap<'on_disk> {
444 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
443 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
445 Self {
444 Self {
446 on_disk,
445 on_disk,
447 root: ChildNodes::default(),
446 root: ChildNodes::default(),
448 nodes_with_entry_count: 0,
447 nodes_with_entry_count: 0,
449 nodes_with_copy_source_count: 0,
448 nodes_with_copy_source_count: 0,
450 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
449 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
451 unreachable_bytes: 0,
450 unreachable_bytes: 0,
452 old_data_size: 0,
451 old_data_size: 0,
453 dirstate_version: DirstateVersion::V1,
452 dirstate_version: DirstateVersion::V1,
454 }
453 }
455 }
454 }
456
455
457 #[timed]
456 #[logging_timer::time("trace")]
458 pub fn new_v2(
457 pub fn new_v2(
459 on_disk: &'on_disk [u8],
458 on_disk: &'on_disk [u8],
460 data_size: usize,
459 data_size: usize,
461 metadata: &[u8],
460 metadata: &[u8],
462 ) -> Result<Self, DirstateError> {
461 ) -> Result<Self, DirstateError> {
463 if let Some(data) = on_disk.get(..data_size) {
462 if let Some(data) = on_disk.get(..data_size) {
464 Ok(on_disk::read(data, metadata)?)
463 Ok(on_disk::read(data, metadata)?)
465 } else {
464 } else {
466 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
465 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
467 }
466 }
468 }
467 }
469
468
470 #[timed]
469 #[logging_timer::time("trace")]
471 pub fn new_v1(
470 pub fn new_v1(
472 on_disk: &'on_disk [u8],
471 on_disk: &'on_disk [u8],
473 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
472 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
474 let mut map = Self::empty(on_disk);
473 let mut map = Self::empty(on_disk);
475 if map.on_disk.is_empty() {
474 if map.on_disk.is_empty() {
476 return Ok((map, None));
475 return Ok((map, None));
477 }
476 }
478
477
479 let parents = parse_dirstate_entries(
478 let parents = parse_dirstate_entries(
480 map.on_disk,
479 map.on_disk,
481 |path, entry, copy_source| {
480 |path, entry, copy_source| {
482 let tracked = entry.tracked();
481 let tracked = entry.tracked();
483 let node = Self::get_or_insert_node_inner(
482 let node = Self::get_or_insert_node_inner(
484 map.on_disk,
483 map.on_disk,
485 &mut map.unreachable_bytes,
484 &mut map.unreachable_bytes,
486 &mut map.root,
485 &mut map.root,
487 path,
486 path,
488 WithBasename::to_cow_borrowed,
487 WithBasename::to_cow_borrowed,
489 |ancestor| {
488 |ancestor| {
490 if tracked {
489 if tracked {
491 ancestor.tracked_descendants_count += 1
490 ancestor.tracked_descendants_count += 1
492 }
491 }
493 ancestor.descendants_with_entry_count += 1
492 ancestor.descendants_with_entry_count += 1
494 },
493 },
495 )?;
494 )?;
496 assert!(
495 assert!(
497 !node.data.has_entry(),
496 !node.data.has_entry(),
498 "duplicate dirstate entry in read"
497 "duplicate dirstate entry in read"
499 );
498 );
500 assert!(
499 assert!(
501 node.copy_source.is_none(),
500 node.copy_source.is_none(),
502 "duplicate dirstate entry in read"
501 "duplicate dirstate entry in read"
503 );
502 );
504 node.data = NodeData::Entry(*entry);
503 node.data = NodeData::Entry(*entry);
505 node.copy_source = copy_source.map(Cow::Borrowed);
504 node.copy_source = copy_source.map(Cow::Borrowed);
506 map.nodes_with_entry_count += 1;
505 map.nodes_with_entry_count += 1;
507 if copy_source.is_some() {
506 if copy_source.is_some() {
508 map.nodes_with_copy_source_count += 1
507 map.nodes_with_copy_source_count += 1
509 }
508 }
510 Ok(())
509 Ok(())
511 },
510 },
512 )?;
511 )?;
513 let parents = Some(parents.clone());
512 let parents = Some(parents.clone());
514
513
515 Ok((map, parents))
514 Ok((map, parents))
516 }
515 }
517
516
518 /// Assuming dirstate-v2 format, returns whether the next write should
517 /// Assuming dirstate-v2 format, returns whether the next write should
519 /// append to the existing data file that contains `self.on_disk` (true),
518 /// append to the existing data file that contains `self.on_disk` (true),
520 /// or create a new data file from scratch (false).
519 /// or create a new data file from scratch (false).
521 pub(super) fn write_should_append(&self) -> bool {
520 pub(super) fn write_should_append(&self) -> bool {
522 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
521 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
523 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
522 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
524 }
523 }
525
524
526 fn get_node<'tree>(
525 fn get_node<'tree>(
527 &'tree self,
526 &'tree self,
528 path: &HgPath,
527 path: &HgPath,
529 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
528 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
530 let mut children = self.root.as_ref();
529 let mut children = self.root.as_ref();
531 let mut components = path.components();
530 let mut components = path.components();
532 let mut component =
531 let mut component =
533 components.next().expect("expected at least one components");
532 components.next().expect("expected at least one components");
534 loop {
533 loop {
535 if let Some(child) = children.get(component, self.on_disk)? {
534 if let Some(child) = children.get(component, self.on_disk)? {
536 if let Some(next_component) = components.next() {
535 if let Some(next_component) = components.next() {
537 component = next_component;
536 component = next_component;
538 children = child.children(self.on_disk)?;
537 children = child.children(self.on_disk)?;
539 } else {
538 } else {
540 return Ok(Some(child));
539 return Ok(Some(child));
541 }
540 }
542 } else {
541 } else {
543 return Ok(None);
542 return Ok(None);
544 }
543 }
545 }
544 }
546 }
545 }
547
546
548 /// Returns a mutable reference to the node at `path` if it exists
547 /// Returns a mutable reference to the node at `path` if it exists
549 ///
548 ///
550 /// `each_ancestor` is a callback that is called for each ancestor node
549 /// `each_ancestor` is a callback that is called for each ancestor node
551 /// when descending the tree. It is used to keep the different counters
550 /// when descending the tree. It is used to keep the different counters
552 /// of the `DirstateMap` up-to-date.
551 /// of the `DirstateMap` up-to-date.
553 fn get_node_mut<'tree>(
552 fn get_node_mut<'tree>(
554 &'tree mut self,
553 &'tree mut self,
555 path: &HgPath,
554 path: &HgPath,
556 each_ancestor: impl FnMut(&mut Node),
555 each_ancestor: impl FnMut(&mut Node),
557 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
556 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
558 Self::get_node_mut_inner(
557 Self::get_node_mut_inner(
559 self.on_disk,
558 self.on_disk,
560 &mut self.unreachable_bytes,
559 &mut self.unreachable_bytes,
561 &mut self.root,
560 &mut self.root,
562 path,
561 path,
563 each_ancestor,
562 each_ancestor,
564 )
563 )
565 }
564 }
566
565
567 /// Lower-level version of `get_node_mut`.
566 /// Lower-level version of `get_node_mut`.
568 ///
567 ///
569 /// This takes `root` instead of `&mut self` so that callers can mutate
568 /// This takes `root` instead of `&mut self` so that callers can mutate
570 /// other fields while the returned borrow is still valid.
569 /// other fields while the returned borrow is still valid.
571 ///
570 ///
572 /// `each_ancestor` is a callback that is called for each ancestor node
571 /// `each_ancestor` is a callback that is called for each ancestor node
573 /// when descending the tree. It is used to keep the different counters
572 /// when descending the tree. It is used to keep the different counters
574 /// of the `DirstateMap` up-to-date.
573 /// of the `DirstateMap` up-to-date.
575 fn get_node_mut_inner<'tree>(
574 fn get_node_mut_inner<'tree>(
576 on_disk: &'on_disk [u8],
575 on_disk: &'on_disk [u8],
577 unreachable_bytes: &mut u32,
576 unreachable_bytes: &mut u32,
578 root: &'tree mut ChildNodes<'on_disk>,
577 root: &'tree mut ChildNodes<'on_disk>,
579 path: &HgPath,
578 path: &HgPath,
580 mut each_ancestor: impl FnMut(&mut Node),
579 mut each_ancestor: impl FnMut(&mut Node),
581 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
580 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
582 let mut children = root;
581 let mut children = root;
583 let mut components = path.components();
582 let mut components = path.components();
584 let mut component =
583 let mut component =
585 components.next().expect("expected at least one components");
584 components.next().expect("expected at least one components");
586 loop {
585 loop {
587 if let Some(child) = children
586 if let Some(child) = children
588 .make_mut(on_disk, unreachable_bytes)?
587 .make_mut(on_disk, unreachable_bytes)?
589 .get_mut(component)
588 .get_mut(component)
590 {
589 {
591 if let Some(next_component) = components.next() {
590 if let Some(next_component) = components.next() {
592 each_ancestor(child);
591 each_ancestor(child);
593 component = next_component;
592 component = next_component;
594 children = &mut child.children;
593 children = &mut child.children;
595 } else {
594 } else {
596 return Ok(Some(child));
595 return Ok(Some(child));
597 }
596 }
598 } else {
597 } else {
599 return Ok(None);
598 return Ok(None);
600 }
599 }
601 }
600 }
602 }
601 }
603
602
604 /// Get a mutable reference to the node at `path`, creating it if it does
603 /// Get a mutable reference to the node at `path`, creating it if it does
605 /// not exist.
604 /// not exist.
606 ///
605 ///
607 /// `each_ancestor` is a callback that is called for each ancestor node
606 /// `each_ancestor` is a callback that is called for each ancestor node
608 /// when descending the tree. It is used to keep the different counters
607 /// when descending the tree. It is used to keep the different counters
609 /// of the `DirstateMap` up-to-date.
608 /// of the `DirstateMap` up-to-date.
610 fn get_or_insert_node<'tree, 'path>(
609 fn get_or_insert_node<'tree, 'path>(
611 &'tree mut self,
610 &'tree mut self,
612 path: &'path HgPath,
611 path: &'path HgPath,
613 each_ancestor: impl FnMut(&mut Node),
612 each_ancestor: impl FnMut(&mut Node),
614 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
613 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
615 Self::get_or_insert_node_inner(
614 Self::get_or_insert_node_inner(
616 self.on_disk,
615 self.on_disk,
617 &mut self.unreachable_bytes,
616 &mut self.unreachable_bytes,
618 &mut self.root,
617 &mut self.root,
619 path,
618 path,
620 WithBasename::to_cow_owned,
619 WithBasename::to_cow_owned,
621 each_ancestor,
620 each_ancestor,
622 )
621 )
623 }
622 }
624
623
625 /// Lower-level version of `get_or_insert_node_inner`, which is used when
624 /// Lower-level version of `get_or_insert_node_inner`, which is used when
626 /// parsing disk data to remove allocations for new nodes.
625 /// parsing disk data to remove allocations for new nodes.
627 fn get_or_insert_node_inner<'tree, 'path>(
626 fn get_or_insert_node_inner<'tree, 'path>(
628 on_disk: &'on_disk [u8],
627 on_disk: &'on_disk [u8],
629 unreachable_bytes: &mut u32,
628 unreachable_bytes: &mut u32,
630 root: &'tree mut ChildNodes<'on_disk>,
629 root: &'tree mut ChildNodes<'on_disk>,
631 path: &'path HgPath,
630 path: &'path HgPath,
632 to_cow: impl Fn(
631 to_cow: impl Fn(
633 WithBasename<&'path HgPath>,
632 WithBasename<&'path HgPath>,
634 ) -> WithBasename<Cow<'on_disk, HgPath>>,
633 ) -> WithBasename<Cow<'on_disk, HgPath>>,
635 mut each_ancestor: impl FnMut(&mut Node),
634 mut each_ancestor: impl FnMut(&mut Node),
636 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
635 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
637 let mut child_nodes = root;
636 let mut child_nodes = root;
638 let mut inclusive_ancestor_paths =
637 let mut inclusive_ancestor_paths =
639 WithBasename::inclusive_ancestors_of(path);
638 WithBasename::inclusive_ancestors_of(path);
640 let mut ancestor_path = inclusive_ancestor_paths
639 let mut ancestor_path = inclusive_ancestor_paths
641 .next()
640 .next()
642 .expect("expected at least one inclusive ancestor");
641 .expect("expected at least one inclusive ancestor");
643 loop {
642 loop {
644 let (_, child_node) = child_nodes
643 let (_, child_node) = child_nodes
645 .make_mut(on_disk, unreachable_bytes)?
644 .make_mut(on_disk, unreachable_bytes)?
646 .raw_entry_mut()
645 .raw_entry_mut()
647 .from_key(ancestor_path.base_name())
646 .from_key(ancestor_path.base_name())
648 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
647 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
649 if let Some(next) = inclusive_ancestor_paths.next() {
648 if let Some(next) = inclusive_ancestor_paths.next() {
650 each_ancestor(child_node);
649 each_ancestor(child_node);
651 ancestor_path = next;
650 ancestor_path = next;
652 child_nodes = &mut child_node.children;
651 child_nodes = &mut child_node.children;
653 } else {
652 } else {
654 return Ok(child_node);
653 return Ok(child_node);
655 }
654 }
656 }
655 }
657 }
656 }
658
657
659 fn reset_state(
658 fn reset_state(
660 &mut self,
659 &mut self,
661 filename: &HgPath,
660 filename: &HgPath,
662 old_entry_opt: Option<DirstateEntry>,
661 old_entry_opt: Option<DirstateEntry>,
663 wc_tracked: bool,
662 wc_tracked: bool,
664 p1_tracked: bool,
663 p1_tracked: bool,
665 p2_info: bool,
664 p2_info: bool,
666 has_meaningful_mtime: bool,
665 has_meaningful_mtime: bool,
667 parent_file_data_opt: Option<ParentFileData>,
666 parent_file_data_opt: Option<ParentFileData>,
668 ) -> Result<(), DirstateError> {
667 ) -> Result<(), DirstateError> {
669 let (had_entry, was_tracked) = match old_entry_opt {
668 let (had_entry, was_tracked) = match old_entry_opt {
670 Some(old_entry) => (true, old_entry.tracked()),
669 Some(old_entry) => (true, old_entry.tracked()),
671 None => (false, false),
670 None => (false, false),
672 };
671 };
673 let node = self.get_or_insert_node(filename, |ancestor| {
672 let node = self.get_or_insert_node(filename, |ancestor| {
674 if !had_entry {
673 if !had_entry {
675 ancestor.descendants_with_entry_count += 1;
674 ancestor.descendants_with_entry_count += 1;
676 }
675 }
677 if was_tracked {
676 if was_tracked {
678 if !wc_tracked {
677 if !wc_tracked {
679 ancestor.tracked_descendants_count = ancestor
678 ancestor.tracked_descendants_count = ancestor
680 .tracked_descendants_count
679 .tracked_descendants_count
681 .checked_sub(1)
680 .checked_sub(1)
682 .expect("tracked count to be >= 0");
681 .expect("tracked count to be >= 0");
683 }
682 }
684 } else {
683 } else {
685 if wc_tracked {
684 if wc_tracked {
686 ancestor.tracked_descendants_count += 1;
685 ancestor.tracked_descendants_count += 1;
687 }
686 }
688 }
687 }
689 })?;
688 })?;
690
689
691 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
690 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
692 DirstateV2Data {
691 DirstateV2Data {
693 wc_tracked,
692 wc_tracked,
694 p1_tracked,
693 p1_tracked,
695 p2_info,
694 p2_info,
696 mode_size: parent_file_data.mode_size,
695 mode_size: parent_file_data.mode_size,
697 mtime: if has_meaningful_mtime {
696 mtime: if has_meaningful_mtime {
698 parent_file_data.mtime
697 parent_file_data.mtime
699 } else {
698 } else {
700 None
699 None
701 },
700 },
702 ..Default::default()
701 ..Default::default()
703 }
702 }
704 } else {
703 } else {
705 DirstateV2Data {
704 DirstateV2Data {
706 wc_tracked,
705 wc_tracked,
707 p1_tracked,
706 p1_tracked,
708 p2_info,
707 p2_info,
709 ..Default::default()
708 ..Default::default()
710 }
709 }
711 };
710 };
712 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
711 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
713 if !had_entry {
712 if !had_entry {
714 self.nodes_with_entry_count += 1;
713 self.nodes_with_entry_count += 1;
715 }
714 }
716 Ok(())
715 Ok(())
717 }
716 }
718
717
719 fn set_tracked(
718 fn set_tracked(
720 &mut self,
719 &mut self,
721 filename: &HgPath,
720 filename: &HgPath,
722 old_entry_opt: Option<DirstateEntry>,
721 old_entry_opt: Option<DirstateEntry>,
723 ) -> Result<bool, DirstateV2ParseError> {
722 ) -> Result<bool, DirstateV2ParseError> {
724 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
723 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
725 let had_entry = old_entry_opt.is_some();
724 let had_entry = old_entry_opt.is_some();
726 let tracked_count_increment = if was_tracked { 0 } else { 1 };
725 let tracked_count_increment = if was_tracked { 0 } else { 1 };
727 let mut new = false;
726 let mut new = false;
728
727
729 let node = self.get_or_insert_node(filename, |ancestor| {
728 let node = self.get_or_insert_node(filename, |ancestor| {
730 if !had_entry {
729 if !had_entry {
731 ancestor.descendants_with_entry_count += 1;
730 ancestor.descendants_with_entry_count += 1;
732 }
731 }
733
732
734 ancestor.tracked_descendants_count += tracked_count_increment;
733 ancestor.tracked_descendants_count += tracked_count_increment;
735 })?;
734 })?;
736 if let Some(old_entry) = old_entry_opt {
735 if let Some(old_entry) = old_entry_opt {
737 let mut e = old_entry.clone();
736 let mut e = old_entry.clone();
738 if e.tracked() {
737 if e.tracked() {
739 // XXX
738 // XXX
740 // This is probably overkill for more case, but we need this to
739 // This is probably overkill for more case, but we need this to
741 // fully replace the `normallookup` call with `set_tracked`
740 // fully replace the `normallookup` call with `set_tracked`
742 // one. Consider smoothing this in the future.
741 // one. Consider smoothing this in the future.
743 e.set_possibly_dirty();
742 e.set_possibly_dirty();
744 } else {
743 } else {
745 new = true;
744 new = true;
746 e.set_tracked();
745 e.set_tracked();
747 }
746 }
748 node.data = NodeData::Entry(e)
747 node.data = NodeData::Entry(e)
749 } else {
748 } else {
750 node.data = NodeData::Entry(DirstateEntry::new_tracked());
749 node.data = NodeData::Entry(DirstateEntry::new_tracked());
751 self.nodes_with_entry_count += 1;
750 self.nodes_with_entry_count += 1;
752 new = true;
751 new = true;
753 };
752 };
754 Ok(new)
753 Ok(new)
755 }
754 }
756
755
757 /// Set a node as untracked in the dirstate.
756 /// Set a node as untracked in the dirstate.
758 ///
757 ///
759 /// It is the responsibility of the caller to remove the copy source and/or
758 /// It is the responsibility of the caller to remove the copy source and/or
760 /// the entry itself if appropriate.
759 /// the entry itself if appropriate.
761 ///
760 ///
762 /// # Panics
761 /// # Panics
763 ///
762 ///
764 /// Panics if the node does not exist.
763 /// Panics if the node does not exist.
765 fn set_untracked(
764 fn set_untracked(
766 &mut self,
765 &mut self,
767 filename: &HgPath,
766 filename: &HgPath,
768 old_entry: DirstateEntry,
767 old_entry: DirstateEntry,
769 ) -> Result<(), DirstateV2ParseError> {
768 ) -> Result<(), DirstateV2ParseError> {
770 let node = self
769 let node = self
771 .get_node_mut(filename, |ancestor| {
770 .get_node_mut(filename, |ancestor| {
772 ancestor.tracked_descendants_count = ancestor
771 ancestor.tracked_descendants_count = ancestor
773 .tracked_descendants_count
772 .tracked_descendants_count
774 .checked_sub(1)
773 .checked_sub(1)
775 .expect("tracked_descendants_count should be >= 0");
774 .expect("tracked_descendants_count should be >= 0");
776 })?
775 })?
777 .expect("node should exist");
776 .expect("node should exist");
778 let mut new_entry = old_entry.clone();
777 let mut new_entry = old_entry.clone();
779 new_entry.set_untracked();
778 new_entry.set_untracked();
780 node.data = NodeData::Entry(new_entry);
779 node.data = NodeData::Entry(new_entry);
781 Ok(())
780 Ok(())
782 }
781 }
783
782
784 /// Set a node as clean in the dirstate.
783 /// Set a node as clean in the dirstate.
785 ///
784 ///
786 /// It is the responsibility of the caller to remove the copy source.
785 /// It is the responsibility of the caller to remove the copy source.
787 ///
786 ///
788 /// # Panics
787 /// # Panics
789 ///
788 ///
790 /// Panics if the node does not exist.
789 /// Panics if the node does not exist.
791 fn set_clean(
790 fn set_clean(
792 &mut self,
791 &mut self,
793 filename: &HgPath,
792 filename: &HgPath,
794 old_entry: DirstateEntry,
793 old_entry: DirstateEntry,
795 mode: u32,
794 mode: u32,
796 size: u32,
795 size: u32,
797 mtime: TruncatedTimestamp,
796 mtime: TruncatedTimestamp,
798 ) -> Result<(), DirstateError> {
797 ) -> Result<(), DirstateError> {
799 let node = self
798 let node = self
800 .get_node_mut(filename, |ancestor| {
799 .get_node_mut(filename, |ancestor| {
801 if !old_entry.tracked() {
800 if !old_entry.tracked() {
802 ancestor.tracked_descendants_count += 1;
801 ancestor.tracked_descendants_count += 1;
803 }
802 }
804 })?
803 })?
805 .expect("node should exist");
804 .expect("node should exist");
806 let mut new_entry = old_entry.clone();
805 let mut new_entry = old_entry.clone();
807 new_entry.set_clean(mode, size, mtime);
806 new_entry.set_clean(mode, size, mtime);
808 node.data = NodeData::Entry(new_entry);
807 node.data = NodeData::Entry(new_entry);
809 Ok(())
808 Ok(())
810 }
809 }
811
810
812 /// Set a node as possibly dirty in the dirstate.
811 /// Set a node as possibly dirty in the dirstate.
813 ///
812 ///
814 /// # Panics
813 /// # Panics
815 ///
814 ///
816 /// Panics if the node does not exist.
815 /// Panics if the node does not exist.
817 fn set_possibly_dirty(
816 fn set_possibly_dirty(
818 &mut self,
817 &mut self,
819 filename: &HgPath,
818 filename: &HgPath,
820 ) -> Result<(), DirstateError> {
819 ) -> Result<(), DirstateError> {
821 let node = self
820 let node = self
822 .get_node_mut(filename, |_ancestor| {})?
821 .get_node_mut(filename, |_ancestor| {})?
823 .expect("node should exist");
822 .expect("node should exist");
824 let entry = node.data.as_entry_mut().expect("entry should exist");
823 let entry = node.data.as_entry_mut().expect("entry should exist");
825 entry.set_possibly_dirty();
824 entry.set_possibly_dirty();
826 node.data = NodeData::Entry(*entry);
825 node.data = NodeData::Entry(*entry);
827 Ok(())
826 Ok(())
828 }
827 }
829
828
830 /// Clears the cached mtime for the (potential) folder at `path`.
829 /// Clears the cached mtime for the (potential) folder at `path`.
831 pub(super) fn clear_cached_mtime(
830 pub(super) fn clear_cached_mtime(
832 &mut self,
831 &mut self,
833 path: &HgPath,
832 path: &HgPath,
834 ) -> Result<(), DirstateV2ParseError> {
833 ) -> Result<(), DirstateV2ParseError> {
835 let node = match self.get_node_mut(path, |_ancestor| {})? {
834 let node = match self.get_node_mut(path, |_ancestor| {})? {
836 Some(node) => node,
835 Some(node) => node,
837 None => return Ok(()),
836 None => return Ok(()),
838 };
837 };
839 if let NodeData::CachedDirectory { .. } = &node.data {
838 if let NodeData::CachedDirectory { .. } = &node.data {
840 node.data = NodeData::None
839 node.data = NodeData::None
841 }
840 }
842 Ok(())
841 Ok(())
843 }
842 }
844
843
845 /// Sets the cached mtime for the (potential) folder at `path`.
844 /// Sets the cached mtime for the (potential) folder at `path`.
846 pub(super) fn set_cached_mtime(
845 pub(super) fn set_cached_mtime(
847 &mut self,
846 &mut self,
848 path: &HgPath,
847 path: &HgPath,
849 mtime: TruncatedTimestamp,
848 mtime: TruncatedTimestamp,
850 ) -> Result<(), DirstateV2ParseError> {
849 ) -> Result<(), DirstateV2ParseError> {
851 let node = match self.get_node_mut(path, |_ancestor| {})? {
850 let node = match self.get_node_mut(path, |_ancestor| {})? {
852 Some(node) => node,
851 Some(node) => node,
853 None => return Ok(()),
852 None => return Ok(()),
854 };
853 };
855 match &node.data {
854 match &node.data {
856 NodeData::Entry(_) => {} // Don’t overwrite an entry
855 NodeData::Entry(_) => {} // Don’t overwrite an entry
857 NodeData::CachedDirectory { .. } | NodeData::None => {
856 NodeData::CachedDirectory { .. } | NodeData::None => {
858 node.data = NodeData::CachedDirectory { mtime }
857 node.data = NodeData::CachedDirectory { mtime }
859 }
858 }
860 }
859 }
861 Ok(())
860 Ok(())
862 }
861 }
863
862
864 fn iter_nodes<'tree>(
863 fn iter_nodes<'tree>(
865 &'tree self,
864 &'tree self,
866 ) -> impl Iterator<
865 ) -> impl Iterator<
867 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
866 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
868 > + 'tree {
867 > + 'tree {
869 // Depth first tree traversal.
868 // Depth first tree traversal.
870 //
869 //
871 // If we could afford internal iteration and recursion,
870 // If we could afford internal iteration and recursion,
872 // this would look like:
871 // this would look like:
873 //
872 //
874 // ```
873 // ```
875 // fn traverse_children(
874 // fn traverse_children(
876 // children: &ChildNodes,
875 // children: &ChildNodes,
877 // each: &mut impl FnMut(&Node),
876 // each: &mut impl FnMut(&Node),
878 // ) {
877 // ) {
879 // for child in children.values() {
878 // for child in children.values() {
880 // traverse_children(&child.children, each);
879 // traverse_children(&child.children, each);
881 // each(child);
880 // each(child);
882 // }
881 // }
883 // }
882 // }
884 // ```
883 // ```
885 //
884 //
886 // However we want an external iterator and therefore can’t use the
885 // However we want an external iterator and therefore can’t use the
887 // call stack. Use an explicit stack instead:
886 // call stack. Use an explicit stack instead:
888 let mut stack = Vec::new();
887 let mut stack = Vec::new();
889 let mut iter = self.root.as_ref().iter();
888 let mut iter = self.root.as_ref().iter();
890 std::iter::from_fn(move || {
889 std::iter::from_fn(move || {
891 while let Some(child_node) = iter.next() {
890 while let Some(child_node) = iter.next() {
892 let children = match child_node.children(self.on_disk) {
891 let children = match child_node.children(self.on_disk) {
893 Ok(children) => children,
892 Ok(children) => children,
894 Err(error) => return Some(Err(error)),
893 Err(error) => return Some(Err(error)),
895 };
894 };
896 // Pseudo-recursion
895 // Pseudo-recursion
897 let new_iter = children.iter();
896 let new_iter = children.iter();
898 let old_iter = std::mem::replace(&mut iter, new_iter);
897 let old_iter = std::mem::replace(&mut iter, new_iter);
899 stack.push((child_node, old_iter));
898 stack.push((child_node, old_iter));
900 }
899 }
901 // Found the end of a `children.iter()` iterator.
900 // Found the end of a `children.iter()` iterator.
902 if let Some((child_node, next_iter)) = stack.pop() {
901 if let Some((child_node, next_iter)) = stack.pop() {
903 // "Return" from pseudo-recursion by restoring state from the
902 // "Return" from pseudo-recursion by restoring state from the
904 // explicit stack
903 // explicit stack
905 iter = next_iter;
904 iter = next_iter;
906
905
907 Some(Ok(child_node))
906 Some(Ok(child_node))
908 } else {
907 } else {
909 // Reached the bottom of the stack, we’re done
908 // Reached the bottom of the stack, we’re done
910 None
909 None
911 }
910 }
912 })
911 })
913 }
912 }
914
913
915 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
914 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
916 if let Cow::Borrowed(path) = path {
915 if let Cow::Borrowed(path) = path {
917 *unreachable_bytes += path.len() as u32
916 *unreachable_bytes += path.len() as u32
918 }
917 }
919 }
918 }
920 }
919 }
921
920
922 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
921 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
923 ///
922 ///
924 /// The callback is only called for incoming `Ok` values. Errors are passed
923 /// The callback is only called for incoming `Ok` values. Errors are passed
925 /// through as-is. In order to let it use the `?` operator the callback is
924 /// through as-is. In order to let it use the `?` operator the callback is
926 /// expected to return a `Result` of `Option`, instead of an `Option` of
925 /// expected to return a `Result` of `Option`, instead of an `Option` of
927 /// `Result`.
926 /// `Result`.
928 fn filter_map_results<'a, I, F, A, B, E>(
927 fn filter_map_results<'a, I, F, A, B, E>(
929 iter: I,
928 iter: I,
930 f: F,
929 f: F,
931 ) -> impl Iterator<Item = Result<B, E>> + 'a
930 ) -> impl Iterator<Item = Result<B, E>> + 'a
932 where
931 where
933 I: Iterator<Item = Result<A, E>> + 'a,
932 I: Iterator<Item = Result<A, E>> + 'a,
934 F: Fn(A) -> Result<Option<B>, E> + 'a,
933 F: Fn(A) -> Result<Option<B>, E> + 'a,
935 {
934 {
936 iter.filter_map(move |result| match result {
935 iter.filter_map(move |result| match result {
937 Ok(node) => f(node).transpose(),
936 Ok(node) => f(node).transpose(),
938 Err(e) => Some(Err(e)),
937 Err(e) => Some(Err(e)),
939 })
938 })
940 }
939 }
941
940
942 impl OwningDirstateMap {
941 impl OwningDirstateMap {
943 pub fn clear(&mut self) {
942 pub fn clear(&mut self) {
944 self.with_dmap_mut(|map| {
943 self.with_dmap_mut(|map| {
945 map.root = Default::default();
944 map.root = Default::default();
946 map.nodes_with_entry_count = 0;
945 map.nodes_with_entry_count = 0;
947 map.nodes_with_copy_source_count = 0;
946 map.nodes_with_copy_source_count = 0;
948 });
947 });
949 }
948 }
950
949
951 pub fn set_tracked(
950 pub fn set_tracked(
952 &mut self,
951 &mut self,
953 filename: &HgPath,
952 filename: &HgPath,
954 ) -> Result<bool, DirstateV2ParseError> {
953 ) -> Result<bool, DirstateV2ParseError> {
955 let old_entry_opt = self.get(filename)?;
954 let old_entry_opt = self.get(filename)?;
956 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
955 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
957 }
956 }
958
957
959 pub fn set_untracked(
958 pub fn set_untracked(
960 &mut self,
959 &mut self,
961 filename: &HgPath,
960 filename: &HgPath,
962 ) -> Result<bool, DirstateError> {
961 ) -> Result<bool, DirstateError> {
963 let old_entry_opt = self.get(filename)?;
962 let old_entry_opt = self.get(filename)?;
964 match old_entry_opt {
963 match old_entry_opt {
965 None => Ok(false),
964 None => Ok(false),
966 Some(old_entry) => {
965 Some(old_entry) => {
967 if !old_entry.tracked() {
966 if !old_entry.tracked() {
968 // `DirstateMap::set_untracked` is not a noop if
967 // `DirstateMap::set_untracked` is not a noop if
969 // already not tracked as it will decrement the
968 // already not tracked as it will decrement the
970 // tracked counters while going down.
969 // tracked counters while going down.
971 return Ok(true);
970 return Ok(true);
972 }
971 }
973 if old_entry.added() {
972 if old_entry.added() {
974 // Untracking an "added" entry will just result in a
973 // Untracking an "added" entry will just result in a
975 // worthless entry (and other parts of the code will
974 // worthless entry (and other parts of the code will
976 // complain about it), just drop it entirely.
975 // complain about it), just drop it entirely.
977 self.drop_entry_and_copy_source(filename)?;
976 self.drop_entry_and_copy_source(filename)?;
978 return Ok(true);
977 return Ok(true);
979 }
978 }
980 if !old_entry.p2_info() {
979 if !old_entry.p2_info() {
981 self.copy_map_remove(filename)?;
980 self.copy_map_remove(filename)?;
982 }
981 }
983
982
984 self.with_dmap_mut(|map| {
983 self.with_dmap_mut(|map| {
985 map.set_untracked(filename, old_entry)?;
984 map.set_untracked(filename, old_entry)?;
986 Ok(true)
985 Ok(true)
987 })
986 })
988 }
987 }
989 }
988 }
990 }
989 }
991
990
992 pub fn set_clean(
991 pub fn set_clean(
993 &mut self,
992 &mut self,
994 filename: &HgPath,
993 filename: &HgPath,
995 mode: u32,
994 mode: u32,
996 size: u32,
995 size: u32,
997 mtime: TruncatedTimestamp,
996 mtime: TruncatedTimestamp,
998 ) -> Result<(), DirstateError> {
997 ) -> Result<(), DirstateError> {
999 let old_entry = match self.get(filename)? {
998 let old_entry = match self.get(filename)? {
1000 None => {
999 None => {
1001 return Err(
1000 return Err(
1002 DirstateMapError::PathNotFound(filename.into()).into()
1001 DirstateMapError::PathNotFound(filename.into()).into()
1003 )
1002 )
1004 }
1003 }
1005 Some(e) => e,
1004 Some(e) => e,
1006 };
1005 };
1007 self.copy_map_remove(filename)?;
1006 self.copy_map_remove(filename)?;
1008 self.with_dmap_mut(|map| {
1007 self.with_dmap_mut(|map| {
1009 map.set_clean(filename, old_entry, mode, size, mtime)
1008 map.set_clean(filename, old_entry, mode, size, mtime)
1010 })
1009 })
1011 }
1010 }
1012
1011
1013 pub fn set_possibly_dirty(
1012 pub fn set_possibly_dirty(
1014 &mut self,
1013 &mut self,
1015 filename: &HgPath,
1014 filename: &HgPath,
1016 ) -> Result<(), DirstateError> {
1015 ) -> Result<(), DirstateError> {
1017 if self.get(filename)?.is_none() {
1016 if self.get(filename)?.is_none() {
1018 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1017 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1019 }
1018 }
1020 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1019 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1021 }
1020 }
1022
1021
1023 pub fn reset_state(
1022 pub fn reset_state(
1024 &mut self,
1023 &mut self,
1025 filename: &HgPath,
1024 filename: &HgPath,
1026 wc_tracked: bool,
1025 wc_tracked: bool,
1027 p1_tracked: bool,
1026 p1_tracked: bool,
1028 p2_info: bool,
1027 p2_info: bool,
1029 has_meaningful_mtime: bool,
1028 has_meaningful_mtime: bool,
1030 parent_file_data_opt: Option<ParentFileData>,
1029 parent_file_data_opt: Option<ParentFileData>,
1031 ) -> Result<(), DirstateError> {
1030 ) -> Result<(), DirstateError> {
1032 if !(p1_tracked || p2_info || wc_tracked) {
1031 if !(p1_tracked || p2_info || wc_tracked) {
1033 self.drop_entry_and_copy_source(filename)?;
1032 self.drop_entry_and_copy_source(filename)?;
1034 return Ok(());
1033 return Ok(());
1035 }
1034 }
1036 self.copy_map_remove(filename)?;
1035 self.copy_map_remove(filename)?;
1037 let old_entry_opt = self.get(filename)?;
1036 let old_entry_opt = self.get(filename)?;
1038 self.with_dmap_mut(|map| {
1037 self.with_dmap_mut(|map| {
1039 map.reset_state(
1038 map.reset_state(
1040 filename,
1039 filename,
1041 old_entry_opt,
1040 old_entry_opt,
1042 wc_tracked,
1041 wc_tracked,
1043 p1_tracked,
1042 p1_tracked,
1044 p2_info,
1043 p2_info,
1045 has_meaningful_mtime,
1044 has_meaningful_mtime,
1046 parent_file_data_opt,
1045 parent_file_data_opt,
1047 )
1046 )
1048 })
1047 })
1049 }
1048 }
1050
1049
1051 pub fn drop_entry_and_copy_source(
1050 pub fn drop_entry_and_copy_source(
1052 &mut self,
1051 &mut self,
1053 filename: &HgPath,
1052 filename: &HgPath,
1054 ) -> Result<(), DirstateError> {
1053 ) -> Result<(), DirstateError> {
1055 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1054 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1056 struct Dropped {
1055 struct Dropped {
1057 was_tracked: bool,
1056 was_tracked: bool,
1058 had_entry: bool,
1057 had_entry: bool,
1059 had_copy_source: bool,
1058 had_copy_source: bool,
1060 }
1059 }
1061
1060
1062 /// If this returns `Ok(Some((dropped, removed)))`, then
1061 /// If this returns `Ok(Some((dropped, removed)))`, then
1063 ///
1062 ///
1064 /// * `dropped` is about the leaf node that was at `filename`
1063 /// * `dropped` is about the leaf node that was at `filename`
1065 /// * `removed` is whether this particular level of recursion just
1064 /// * `removed` is whether this particular level of recursion just
1066 /// removed a node in `nodes`.
1065 /// removed a node in `nodes`.
1067 fn recur<'on_disk>(
1066 fn recur<'on_disk>(
1068 on_disk: &'on_disk [u8],
1067 on_disk: &'on_disk [u8],
1069 unreachable_bytes: &mut u32,
1068 unreachable_bytes: &mut u32,
1070 nodes: &mut ChildNodes<'on_disk>,
1069 nodes: &mut ChildNodes<'on_disk>,
1071 path: &HgPath,
1070 path: &HgPath,
1072 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1071 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1073 let (first_path_component, rest_of_path) =
1072 let (first_path_component, rest_of_path) =
1074 path.split_first_component();
1073 path.split_first_component();
1075 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1074 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1076 let node = if let Some(node) = nodes.get_mut(first_path_component)
1075 let node = if let Some(node) = nodes.get_mut(first_path_component)
1077 {
1076 {
1078 node
1077 node
1079 } else {
1078 } else {
1080 return Ok(None);
1079 return Ok(None);
1081 };
1080 };
1082 let dropped;
1081 let dropped;
1083 if let Some(rest) = rest_of_path {
1082 if let Some(rest) = rest_of_path {
1084 if let Some((d, removed)) = recur(
1083 if let Some((d, removed)) = recur(
1085 on_disk,
1084 on_disk,
1086 unreachable_bytes,
1085 unreachable_bytes,
1087 &mut node.children,
1086 &mut node.children,
1088 rest,
1087 rest,
1089 )? {
1088 )? {
1090 dropped = d;
1089 dropped = d;
1091 if dropped.had_entry {
1090 if dropped.had_entry {
1092 node.descendants_with_entry_count = node
1091 node.descendants_with_entry_count = node
1093 .descendants_with_entry_count
1092 .descendants_with_entry_count
1094 .checked_sub(1)
1093 .checked_sub(1)
1095 .expect(
1094 .expect(
1096 "descendants_with_entry_count should be >= 0",
1095 "descendants_with_entry_count should be >= 0",
1097 );
1096 );
1098 }
1097 }
1099 if dropped.was_tracked {
1098 if dropped.was_tracked {
1100 node.tracked_descendants_count = node
1099 node.tracked_descendants_count = node
1101 .tracked_descendants_count
1100 .tracked_descendants_count
1102 .checked_sub(1)
1101 .checked_sub(1)
1103 .expect(
1102 .expect(
1104 "tracked_descendants_count should be >= 0",
1103 "tracked_descendants_count should be >= 0",
1105 );
1104 );
1106 }
1105 }
1107
1106
1108 // Directory caches must be invalidated when removing a
1107 // Directory caches must be invalidated when removing a
1109 // child node
1108 // child node
1110 if removed {
1109 if removed {
1111 if let NodeData::CachedDirectory { .. } = &node.data {
1110 if let NodeData::CachedDirectory { .. } = &node.data {
1112 node.data = NodeData::None
1111 node.data = NodeData::None
1113 }
1112 }
1114 }
1113 }
1115 } else {
1114 } else {
1116 return Ok(None);
1115 return Ok(None);
1117 }
1116 }
1118 } else {
1117 } else {
1119 let entry = node.data.as_entry();
1118 let entry = node.data.as_entry();
1120 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1119 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1121 let had_entry = entry.is_some();
1120 let had_entry = entry.is_some();
1122 if had_entry {
1121 if had_entry {
1123 node.data = NodeData::None
1122 node.data = NodeData::None
1124 }
1123 }
1125 let mut had_copy_source = false;
1124 let mut had_copy_source = false;
1126 if let Some(source) = &node.copy_source {
1125 if let Some(source) = &node.copy_source {
1127 DirstateMap::count_dropped_path(unreachable_bytes, source);
1126 DirstateMap::count_dropped_path(unreachable_bytes, source);
1128 had_copy_source = true;
1127 had_copy_source = true;
1129 node.copy_source = None
1128 node.copy_source = None
1130 }
1129 }
1131 dropped = Dropped {
1130 dropped = Dropped {
1132 was_tracked,
1131 was_tracked,
1133 had_entry,
1132 had_entry,
1134 had_copy_source,
1133 had_copy_source,
1135 };
1134 };
1136 }
1135 }
1137 // After recursion, for both leaf (rest_of_path is None) nodes and
1136 // After recursion, for both leaf (rest_of_path is None) nodes and
1138 // parent nodes, remove a node if it just became empty.
1137 // parent nodes, remove a node if it just became empty.
1139 let remove = !node.data.has_entry()
1138 let remove = !node.data.has_entry()
1140 && node.copy_source.is_none()
1139 && node.copy_source.is_none()
1141 && node.children.is_empty();
1140 && node.children.is_empty();
1142 if remove {
1141 if remove {
1143 let (key, _) =
1142 let (key, _) =
1144 nodes.remove_entry(first_path_component).unwrap();
1143 nodes.remove_entry(first_path_component).unwrap();
1145 DirstateMap::count_dropped_path(
1144 DirstateMap::count_dropped_path(
1146 unreachable_bytes,
1145 unreachable_bytes,
1147 key.full_path(),
1146 key.full_path(),
1148 )
1147 )
1149 }
1148 }
1150 Ok(Some((dropped, remove)))
1149 Ok(Some((dropped, remove)))
1151 }
1150 }
1152
1151
1153 self.with_dmap_mut(|map| {
1152 self.with_dmap_mut(|map| {
1154 if let Some((dropped, _removed)) = recur(
1153 if let Some((dropped, _removed)) = recur(
1155 map.on_disk,
1154 map.on_disk,
1156 &mut map.unreachable_bytes,
1155 &mut map.unreachable_bytes,
1157 &mut map.root,
1156 &mut map.root,
1158 filename,
1157 filename,
1159 )? {
1158 )? {
1160 if dropped.had_entry {
1159 if dropped.had_entry {
1161 map.nodes_with_entry_count = map
1160 map.nodes_with_entry_count = map
1162 .nodes_with_entry_count
1161 .nodes_with_entry_count
1163 .checked_sub(1)
1162 .checked_sub(1)
1164 .expect("nodes_with_entry_count should be >= 0");
1163 .expect("nodes_with_entry_count should be >= 0");
1165 }
1164 }
1166 if dropped.had_copy_source {
1165 if dropped.had_copy_source {
1167 map.nodes_with_copy_source_count = map
1166 map.nodes_with_copy_source_count = map
1168 .nodes_with_copy_source_count
1167 .nodes_with_copy_source_count
1169 .checked_sub(1)
1168 .checked_sub(1)
1170 .expect("nodes_with_copy_source_count should be >= 0");
1169 .expect("nodes_with_copy_source_count should be >= 0");
1171 }
1170 }
1172 } else {
1171 } else {
1173 debug_assert!(!was_tracked);
1172 debug_assert!(!was_tracked);
1174 }
1173 }
1175 Ok(())
1174 Ok(())
1176 })
1175 })
1177 }
1176 }
1178
1177
1179 pub fn has_tracked_dir(
1178 pub fn has_tracked_dir(
1180 &mut self,
1179 &mut self,
1181 directory: &HgPath,
1180 directory: &HgPath,
1182 ) -> Result<bool, DirstateError> {
1181 ) -> Result<bool, DirstateError> {
1183 self.with_dmap_mut(|map| {
1182 self.with_dmap_mut(|map| {
1184 if let Some(node) = map.get_node(directory)? {
1183 if let Some(node) = map.get_node(directory)? {
1185 // A node without a `DirstateEntry` was created to hold child
1184 // A node without a `DirstateEntry` was created to hold child
1186 // nodes, and is therefore a directory.
1185 // nodes, and is therefore a directory.
1187 let is_dir = node.entry()?.is_none();
1186 let is_dir = node.entry()?.is_none();
1188 Ok(is_dir && node.tracked_descendants_count() > 0)
1187 Ok(is_dir && node.tracked_descendants_count() > 0)
1189 } else {
1188 } else {
1190 Ok(false)
1189 Ok(false)
1191 }
1190 }
1192 })
1191 })
1193 }
1192 }
1194
1193
1195 pub fn has_dir(
1194 pub fn has_dir(
1196 &mut self,
1195 &mut self,
1197 directory: &HgPath,
1196 directory: &HgPath,
1198 ) -> Result<bool, DirstateError> {
1197 ) -> Result<bool, DirstateError> {
1199 self.with_dmap_mut(|map| {
1198 self.with_dmap_mut(|map| {
1200 if let Some(node) = map.get_node(directory)? {
1199 if let Some(node) = map.get_node(directory)? {
1201 // A node without a `DirstateEntry` was created to hold child
1200 // A node without a `DirstateEntry` was created to hold child
1202 // nodes, and is therefore a directory.
1201 // nodes, and is therefore a directory.
1203 let is_dir = node.entry()?.is_none();
1202 let is_dir = node.entry()?.is_none();
1204 Ok(is_dir && node.descendants_with_entry_count() > 0)
1203 Ok(is_dir && node.descendants_with_entry_count() > 0)
1205 } else {
1204 } else {
1206 Ok(false)
1205 Ok(false)
1207 }
1206 }
1208 })
1207 })
1209 }
1208 }
1210
1209
1211 #[timed]
1210 #[logging_timer::time("trace")]
1212 pub fn pack_v1(
1211 pub fn pack_v1(
1213 &self,
1212 &self,
1214 parents: DirstateParents,
1213 parents: DirstateParents,
1215 ) -> Result<Vec<u8>, DirstateError> {
1214 ) -> Result<Vec<u8>, DirstateError> {
1216 let map = self.get_map();
1215 let map = self.get_map();
1217 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1216 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1218 // reallocations
1217 // reallocations
1219 let mut size = parents.as_bytes().len();
1218 let mut size = parents.as_bytes().len();
1220 for node in map.iter_nodes() {
1219 for node in map.iter_nodes() {
1221 let node = node?;
1220 let node = node?;
1222 if node.entry()?.is_some() {
1221 if node.entry()?.is_some() {
1223 size += packed_entry_size(
1222 size += packed_entry_size(
1224 node.full_path(map.on_disk)?,
1223 node.full_path(map.on_disk)?,
1225 node.copy_source(map.on_disk)?,
1224 node.copy_source(map.on_disk)?,
1226 );
1225 );
1227 }
1226 }
1228 }
1227 }
1229
1228
1230 let mut packed = Vec::with_capacity(size);
1229 let mut packed = Vec::with_capacity(size);
1231 packed.extend(parents.as_bytes());
1230 packed.extend(parents.as_bytes());
1232
1231
1233 for node in map.iter_nodes() {
1232 for node in map.iter_nodes() {
1234 let node = node?;
1233 let node = node?;
1235 if let Some(entry) = node.entry()? {
1234 if let Some(entry) = node.entry()? {
1236 pack_entry(
1235 pack_entry(
1237 node.full_path(map.on_disk)?,
1236 node.full_path(map.on_disk)?,
1238 &entry,
1237 &entry,
1239 node.copy_source(map.on_disk)?,
1238 node.copy_source(map.on_disk)?,
1240 &mut packed,
1239 &mut packed,
1241 );
1240 );
1242 }
1241 }
1243 }
1242 }
1244 Ok(packed)
1243 Ok(packed)
1245 }
1244 }
1246
1245
1247 /// Returns new data and metadata together with whether that data should be
1246 /// Returns new data and metadata together with whether that data should be
1248 /// appended to the existing data file whose content is at
1247 /// appended to the existing data file whose content is at
1249 /// `map.on_disk` (true), instead of written to a new data file
1248 /// `map.on_disk` (true), instead of written to a new data file
1250 /// (false), and the previous size of data on disk.
1249 /// (false), and the previous size of data on disk.
1251 #[timed]
1250 #[logging_timer::time("trace")]
1252 pub fn pack_v2(
1251 pub fn pack_v2(
1253 &self,
1252 &self,
1254 can_append: bool,
1253 can_append: bool,
1255 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1254 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1256 {
1255 {
1257 let map = self.get_map();
1256 let map = self.get_map();
1258 on_disk::write(map, can_append)
1257 on_disk::write(map, can_append)
1259 }
1258 }
1260
1259
1261 /// `callback` allows the caller to process and do something with the
1260 /// `callback` allows the caller to process and do something with the
1262 /// results of the status. This is needed to do so efficiently (i.e.
1261 /// results of the status. This is needed to do so efficiently (i.e.
1263 /// without cloning the `DirstateStatus` object with its paths) because
1262 /// without cloning the `DirstateStatus` object with its paths) because
1264 /// we need to borrow from `Self`.
1263 /// we need to borrow from `Self`.
1265 pub fn with_status<R>(
1264 pub fn with_status<R>(
1266 &mut self,
1265 &mut self,
1267 matcher: &(dyn Matcher + Sync),
1266 matcher: &(dyn Matcher + Sync),
1268 root_dir: PathBuf,
1267 root_dir: PathBuf,
1269 ignore_files: Vec<PathBuf>,
1268 ignore_files: Vec<PathBuf>,
1270 options: StatusOptions,
1269 options: StatusOptions,
1271 callback: impl for<'r> FnOnce(
1270 callback: impl for<'r> FnOnce(
1272 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1271 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1273 ) -> R,
1272 ) -> R,
1274 ) -> R {
1273 ) -> R {
1275 self.with_dmap_mut(|map| {
1274 self.with_dmap_mut(|map| {
1276 callback(super::status::status(
1275 callback(super::status::status(
1277 map,
1276 map,
1278 matcher,
1277 matcher,
1279 root_dir,
1278 root_dir,
1280 ignore_files,
1279 ignore_files,
1281 options,
1280 options,
1282 ))
1281 ))
1283 })
1282 })
1284 }
1283 }
1285
1284
1286 pub fn copy_map_len(&self) -> usize {
1285 pub fn copy_map_len(&self) -> usize {
1287 let map = self.get_map();
1286 let map = self.get_map();
1288 map.nodes_with_copy_source_count as usize
1287 map.nodes_with_copy_source_count as usize
1289 }
1288 }
1290
1289
1291 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1290 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1292 let map = self.get_map();
1291 let map = self.get_map();
1293 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1292 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1294 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1293 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1295 Some((node.full_path(map.on_disk)?, source))
1294 Some((node.full_path(map.on_disk)?, source))
1296 } else {
1295 } else {
1297 None
1296 None
1298 })
1297 })
1299 }))
1298 }))
1300 }
1299 }
1301
1300
1302 pub fn copy_map_contains_key(
1301 pub fn copy_map_contains_key(
1303 &self,
1302 &self,
1304 key: &HgPath,
1303 key: &HgPath,
1305 ) -> Result<bool, DirstateV2ParseError> {
1304 ) -> Result<bool, DirstateV2ParseError> {
1306 let map = self.get_map();
1305 let map = self.get_map();
1307 Ok(if let Some(node) = map.get_node(key)? {
1306 Ok(if let Some(node) = map.get_node(key)? {
1308 node.has_copy_source()
1307 node.has_copy_source()
1309 } else {
1308 } else {
1310 false
1309 false
1311 })
1310 })
1312 }
1311 }
1313
1312
1314 pub fn copy_map_get(
1313 pub fn copy_map_get(
1315 &self,
1314 &self,
1316 key: &HgPath,
1315 key: &HgPath,
1317 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1316 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1318 let map = self.get_map();
1317 let map = self.get_map();
1319 if let Some(node) = map.get_node(key)? {
1318 if let Some(node) = map.get_node(key)? {
1320 if let Some(source) = node.copy_source(map.on_disk)? {
1319 if let Some(source) = node.copy_source(map.on_disk)? {
1321 return Ok(Some(source));
1320 return Ok(Some(source));
1322 }
1321 }
1323 }
1322 }
1324 Ok(None)
1323 Ok(None)
1325 }
1324 }
1326
1325
1327 pub fn copy_map_remove(
1326 pub fn copy_map_remove(
1328 &mut self,
1327 &mut self,
1329 key: &HgPath,
1328 key: &HgPath,
1330 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1329 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1331 self.with_dmap_mut(|map| {
1330 self.with_dmap_mut(|map| {
1332 let count = &mut map.nodes_with_copy_source_count;
1331 let count = &mut map.nodes_with_copy_source_count;
1333 let unreachable_bytes = &mut map.unreachable_bytes;
1332 let unreachable_bytes = &mut map.unreachable_bytes;
1334 Ok(DirstateMap::get_node_mut_inner(
1333 Ok(DirstateMap::get_node_mut_inner(
1335 map.on_disk,
1334 map.on_disk,
1336 unreachable_bytes,
1335 unreachable_bytes,
1337 &mut map.root,
1336 &mut map.root,
1338 key,
1337 key,
1339 |_ancestor| {},
1338 |_ancestor| {},
1340 )?
1339 )?
1341 .and_then(|node| {
1340 .and_then(|node| {
1342 if let Some(source) = &node.copy_source {
1341 if let Some(source) = &node.copy_source {
1343 *count = count
1342 *count = count
1344 .checked_sub(1)
1343 .checked_sub(1)
1345 .expect("nodes_with_copy_source_count should be >= 0");
1344 .expect("nodes_with_copy_source_count should be >= 0");
1346 DirstateMap::count_dropped_path(unreachable_bytes, source);
1345 DirstateMap::count_dropped_path(unreachable_bytes, source);
1347 }
1346 }
1348 node.copy_source.take().map(Cow::into_owned)
1347 node.copy_source.take().map(Cow::into_owned)
1349 }))
1348 }))
1350 })
1349 })
1351 }
1350 }
1352
1351
1353 pub fn copy_map_insert(
1352 pub fn copy_map_insert(
1354 &mut self,
1353 &mut self,
1355 key: &HgPath,
1354 key: &HgPath,
1356 value: &HgPath,
1355 value: &HgPath,
1357 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1356 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1358 self.with_dmap_mut(|map| {
1357 self.with_dmap_mut(|map| {
1359 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1358 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1360 let had_copy_source = node.copy_source.is_none();
1359 let had_copy_source = node.copy_source.is_none();
1361 let old = node
1360 let old = node
1362 .copy_source
1361 .copy_source
1363 .replace(value.to_owned().into())
1362 .replace(value.to_owned().into())
1364 .map(Cow::into_owned);
1363 .map(Cow::into_owned);
1365 if had_copy_source {
1364 if had_copy_source {
1366 map.nodes_with_copy_source_count += 1
1365 map.nodes_with_copy_source_count += 1
1367 }
1366 }
1368 Ok(old)
1367 Ok(old)
1369 })
1368 })
1370 }
1369 }
1371
1370
1372 pub fn len(&self) -> usize {
1371 pub fn len(&self) -> usize {
1373 let map = self.get_map();
1372 let map = self.get_map();
1374 map.nodes_with_entry_count as usize
1373 map.nodes_with_entry_count as usize
1375 }
1374 }
1376
1375
1377 pub fn contains_key(
1376 pub fn contains_key(
1378 &self,
1377 &self,
1379 key: &HgPath,
1378 key: &HgPath,
1380 ) -> Result<bool, DirstateV2ParseError> {
1379 ) -> Result<bool, DirstateV2ParseError> {
1381 Ok(self.get(key)?.is_some())
1380 Ok(self.get(key)?.is_some())
1382 }
1381 }
1383
1382
1384 pub fn get(
1383 pub fn get(
1385 &self,
1384 &self,
1386 key: &HgPath,
1385 key: &HgPath,
1387 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1386 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1388 let map = self.get_map();
1387 let map = self.get_map();
1389 Ok(if let Some(node) = map.get_node(key)? {
1388 Ok(if let Some(node) = map.get_node(key)? {
1390 node.entry()?
1389 node.entry()?
1391 } else {
1390 } else {
1392 None
1391 None
1393 })
1392 })
1394 }
1393 }
1395
1394
1396 pub fn iter(&self) -> StateMapIter<'_> {
1395 pub fn iter(&self) -> StateMapIter<'_> {
1397 let map = self.get_map();
1396 let map = self.get_map();
1398 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1397 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1399 Ok(if let Some(entry) = node.entry()? {
1398 Ok(if let Some(entry) = node.entry()? {
1400 Some((node.full_path(map.on_disk)?, entry))
1399 Some((node.full_path(map.on_disk)?, entry))
1401 } else {
1400 } else {
1402 None
1401 None
1403 })
1402 })
1404 }))
1403 }))
1405 }
1404 }
1406
1405
1407 pub fn iter_tracked_dirs(
1406 pub fn iter_tracked_dirs(
1408 &mut self,
1407 &mut self,
1409 ) -> Result<
1408 ) -> Result<
1410 Box<
1409 Box<
1411 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1410 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1412 + Send
1411 + Send
1413 + '_,
1412 + '_,
1414 >,
1413 >,
1415 DirstateError,
1414 DirstateError,
1416 > {
1415 > {
1417 let map = self.get_map();
1416 let map = self.get_map();
1418 let on_disk = map.on_disk;
1417 let on_disk = map.on_disk;
1419 Ok(Box::new(filter_map_results(
1418 Ok(Box::new(filter_map_results(
1420 map.iter_nodes(),
1419 map.iter_nodes(),
1421 move |node| {
1420 move |node| {
1422 Ok(if node.tracked_descendants_count() > 0 {
1421 Ok(if node.tracked_descendants_count() > 0 {
1423 Some(node.full_path(on_disk)?)
1422 Some(node.full_path(on_disk)?)
1424 } else {
1423 } else {
1425 None
1424 None
1426 })
1425 })
1427 },
1426 },
1428 )))
1427 )))
1429 }
1428 }
1430
1429
1431 /// Only public because it needs to be exposed to the Python layer.
1430 /// Only public because it needs to be exposed to the Python layer.
1432 /// It is not the full `setparents` logic, only the parts that mutate the
1431 /// It is not the full `setparents` logic, only the parts that mutate the
1433 /// entries.
1432 /// entries.
1434 pub fn setparents_fixup(
1433 pub fn setparents_fixup(
1435 &mut self,
1434 &mut self,
1436 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1435 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1437 // XXX
1436 // XXX
1438 // All the copying and re-querying is quite inefficient, but this is
1437 // All the copying and re-querying is quite inefficient, but this is
1439 // still a lot better than doing it from Python.
1438 // still a lot better than doing it from Python.
1440 //
1439 //
1441 // The better solution is to develop a mechanism for `iter_mut`,
1440 // The better solution is to develop a mechanism for `iter_mut`,
1442 // which will be a lot more involved: we're dealing with a lazy,
1441 // which will be a lot more involved: we're dealing with a lazy,
1443 // append-mostly, tree-like data structure. This will do for now.
1442 // append-mostly, tree-like data structure. This will do for now.
1444 let mut copies = vec![];
1443 let mut copies = vec![];
1445 let mut files_with_p2_info = vec![];
1444 let mut files_with_p2_info = vec![];
1446 for res in self.iter() {
1445 for res in self.iter() {
1447 let (path, entry) = res?;
1446 let (path, entry) = res?;
1448 if entry.p2_info() {
1447 if entry.p2_info() {
1449 files_with_p2_info.push(path.to_owned())
1448 files_with_p2_info.push(path.to_owned())
1450 }
1449 }
1451 }
1450 }
1452 self.with_dmap_mut(|map| {
1451 self.with_dmap_mut(|map| {
1453 for path in files_with_p2_info.iter() {
1452 for path in files_with_p2_info.iter() {
1454 let node = map.get_or_insert_node(path, |_| {})?;
1453 let node = map.get_or_insert_node(path, |_| {})?;
1455 let entry =
1454 let entry =
1456 node.data.as_entry_mut().expect("entry should exist");
1455 node.data.as_entry_mut().expect("entry should exist");
1457 entry.drop_merge_data();
1456 entry.drop_merge_data();
1458 if let Some(source) = node.copy_source.take().as_deref() {
1457 if let Some(source) = node.copy_source.take().as_deref() {
1459 copies.push((path.to_owned(), source.to_owned()));
1458 copies.push((path.to_owned(), source.to_owned()));
1460 }
1459 }
1461 }
1460 }
1462 Ok(copies)
1461 Ok(copies)
1463 })
1462 })
1464 }
1463 }
1465
1464
1466 pub fn debug_iter(
1465 pub fn debug_iter(
1467 &self,
1466 &self,
1468 all: bool,
1467 all: bool,
1469 ) -> Box<
1468 ) -> Box<
1470 dyn Iterator<
1469 dyn Iterator<
1471 Item = Result<
1470 Item = Result<
1472 (&HgPath, (u8, i32, i32, i32)),
1471 (&HgPath, (u8, i32, i32, i32)),
1473 DirstateV2ParseError,
1472 DirstateV2ParseError,
1474 >,
1473 >,
1475 > + Send
1474 > + Send
1476 + '_,
1475 + '_,
1477 > {
1476 > {
1478 let map = self.get_map();
1477 let map = self.get_map();
1479 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1478 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1480 let debug_tuple = if let Some(entry) = node.entry()? {
1479 let debug_tuple = if let Some(entry) = node.entry()? {
1481 entry.debug_tuple()
1480 entry.debug_tuple()
1482 } else if !all {
1481 } else if !all {
1483 return Ok(None);
1482 return Ok(None);
1484 } else if let Some(mtime) = node.cached_directory_mtime()? {
1483 } else if let Some(mtime) = node.cached_directory_mtime()? {
1485 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1484 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1486 } else {
1485 } else {
1487 (b' ', 0, -1, -1)
1486 (b' ', 0, -1, -1)
1488 };
1487 };
1489 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1488 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1490 }))
1489 }))
1491 }
1490 }
1492 }
1491 }
1493 #[cfg(test)]
1492 #[cfg(test)]
1494 mod tests {
1493 mod tests {
1495 use super::*;
1494 use super::*;
1496
1495
1497 /// Shortcut to return tracked descendants of a path.
1496 /// Shortcut to return tracked descendants of a path.
1498 /// Panics if the path does not exist.
1497 /// Panics if the path does not exist.
1499 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1498 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1500 let path = dbg!(HgPath::new(path));
1499 let path = dbg!(HgPath::new(path));
1501 let node = map.get_map().get_node(path);
1500 let node = map.get_map().get_node(path);
1502 node.unwrap().unwrap().tracked_descendants_count()
1501 node.unwrap().unwrap().tracked_descendants_count()
1503 }
1502 }
1504
1503
1505 /// Shortcut to return descendants with an entry.
1504 /// Shortcut to return descendants with an entry.
1506 /// Panics if the path does not exist.
1505 /// Panics if the path does not exist.
1507 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1506 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1508 let path = dbg!(HgPath::new(path));
1507 let path = dbg!(HgPath::new(path));
1509 let node = map.get_map().get_node(path);
1508 let node = map.get_map().get_node(path);
1510 node.unwrap().unwrap().descendants_with_entry_count()
1509 node.unwrap().unwrap().descendants_with_entry_count()
1511 }
1510 }
1512
1511
1513 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1512 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1514 let path = dbg!(HgPath::new(path));
1513 let path = dbg!(HgPath::new(path));
1515 let node = map.get_map().get_node(path);
1514 let node = map.get_map().get_node(path);
1516 assert!(node.unwrap().is_none());
1515 assert!(node.unwrap().is_none());
1517 }
1516 }
1518
1517
1519 /// Shortcut for path creation in tests
1518 /// Shortcut for path creation in tests
1520 fn p(b: &[u8]) -> &HgPath {
1519 fn p(b: &[u8]) -> &HgPath {
1521 HgPath::new(b)
1520 HgPath::new(b)
1522 }
1521 }
1523
1522
1524 /// Test the very simple case a single tracked file
1523 /// Test the very simple case a single tracked file
1525 #[test]
1524 #[test]
1526 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1525 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1527 let mut map = OwningDirstateMap::new_empty(vec![]);
1526 let mut map = OwningDirstateMap::new_empty(vec![]);
1528 assert_eq!(map.len(), 0);
1527 assert_eq!(map.len(), 0);
1529
1528
1530 map.set_tracked(p(b"some/nested/path"))?;
1529 map.set_tracked(p(b"some/nested/path"))?;
1531
1530
1532 assert_eq!(map.len(), 1);
1531 assert_eq!(map.len(), 1);
1533 assert_eq!(tracked_descendants(&map, b"some"), 1);
1532 assert_eq!(tracked_descendants(&map, b"some"), 1);
1534 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1533 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1535 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1534 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1536
1535
1537 map.set_untracked(p(b"some/nested/path"))?;
1536 map.set_untracked(p(b"some/nested/path"))?;
1538 assert_eq!(map.len(), 0);
1537 assert_eq!(map.len(), 0);
1539 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1538 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1540
1539
1541 Ok(())
1540 Ok(())
1542 }
1541 }
1543
1542
1544 /// Test the simple case of all tracked, but multiple files
1543 /// Test the simple case of all tracked, but multiple files
1545 #[test]
1544 #[test]
1546 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1545 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1547 let mut map = OwningDirstateMap::new_empty(vec![]);
1546 let mut map = OwningDirstateMap::new_empty(vec![]);
1548
1547
1549 map.set_tracked(p(b"some/nested/path"))?;
1548 map.set_tracked(p(b"some/nested/path"))?;
1550 map.set_tracked(p(b"some/nested/file"))?;
1549 map.set_tracked(p(b"some/nested/file"))?;
1551 // one layer without any files to test deletion cascade
1550 // one layer without any files to test deletion cascade
1552 map.set_tracked(p(b"some/other/nested/path"))?;
1551 map.set_tracked(p(b"some/other/nested/path"))?;
1553 map.set_tracked(p(b"root_file"))?;
1552 map.set_tracked(p(b"root_file"))?;
1554 map.set_tracked(p(b"some/file"))?;
1553 map.set_tracked(p(b"some/file"))?;
1555 map.set_tracked(p(b"some/file2"))?;
1554 map.set_tracked(p(b"some/file2"))?;
1556 map.set_tracked(p(b"some/file3"))?;
1555 map.set_tracked(p(b"some/file3"))?;
1557
1556
1558 assert_eq!(map.len(), 7);
1557 assert_eq!(map.len(), 7);
1559 assert_eq!(tracked_descendants(&map, b"some"), 6);
1558 assert_eq!(tracked_descendants(&map, b"some"), 6);
1560 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1559 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1561 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1560 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1562 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1561 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1563 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1562 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1564
1563
1565 map.set_untracked(p(b"some/nested/path"))?;
1564 map.set_untracked(p(b"some/nested/path"))?;
1566 assert_eq!(map.len(), 6);
1565 assert_eq!(map.len(), 6);
1567 assert_eq!(tracked_descendants(&map, b"some"), 5);
1566 assert_eq!(tracked_descendants(&map, b"some"), 5);
1568 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1567 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1569 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1568 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1570 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1569 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1571
1570
1572 map.set_untracked(p(b"some/nested/file"))?;
1571 map.set_untracked(p(b"some/nested/file"))?;
1573 assert_eq!(map.len(), 5);
1572 assert_eq!(map.len(), 5);
1574 assert_eq!(tracked_descendants(&map, b"some"), 4);
1573 assert_eq!(tracked_descendants(&map, b"some"), 4);
1575 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1574 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1576 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1575 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1577 assert_does_not_exist(&map, b"some_nested");
1576 assert_does_not_exist(&map, b"some_nested");
1578
1577
1579 map.set_untracked(p(b"some/other/nested/path"))?;
1578 map.set_untracked(p(b"some/other/nested/path"))?;
1580 assert_eq!(map.len(), 4);
1579 assert_eq!(map.len(), 4);
1581 assert_eq!(tracked_descendants(&map, b"some"), 3);
1580 assert_eq!(tracked_descendants(&map, b"some"), 3);
1582 assert_does_not_exist(&map, b"some/other");
1581 assert_does_not_exist(&map, b"some/other");
1583
1582
1584 map.set_untracked(p(b"root_file"))?;
1583 map.set_untracked(p(b"root_file"))?;
1585 assert_eq!(map.len(), 3);
1584 assert_eq!(map.len(), 3);
1586 assert_eq!(tracked_descendants(&map, b"some"), 3);
1585 assert_eq!(tracked_descendants(&map, b"some"), 3);
1587 assert_does_not_exist(&map, b"root_file");
1586 assert_does_not_exist(&map, b"root_file");
1588
1587
1589 map.set_untracked(p(b"some/file"))?;
1588 map.set_untracked(p(b"some/file"))?;
1590 assert_eq!(map.len(), 2);
1589 assert_eq!(map.len(), 2);
1591 assert_eq!(tracked_descendants(&map, b"some"), 2);
1590 assert_eq!(tracked_descendants(&map, b"some"), 2);
1592 assert_does_not_exist(&map, b"some/file");
1591 assert_does_not_exist(&map, b"some/file");
1593
1592
1594 map.set_untracked(p(b"some/file2"))?;
1593 map.set_untracked(p(b"some/file2"))?;
1595 assert_eq!(map.len(), 1);
1594 assert_eq!(map.len(), 1);
1596 assert_eq!(tracked_descendants(&map, b"some"), 1);
1595 assert_eq!(tracked_descendants(&map, b"some"), 1);
1597 assert_does_not_exist(&map, b"some/file2");
1596 assert_does_not_exist(&map, b"some/file2");
1598
1597
1599 map.set_untracked(p(b"some/file3"))?;
1598 map.set_untracked(p(b"some/file3"))?;
1600 assert_eq!(map.len(), 0);
1599 assert_eq!(map.len(), 0);
1601 assert_does_not_exist(&map, b"some/file3");
1600 assert_does_not_exist(&map, b"some/file3");
1602
1601
1603 Ok(())
1602 Ok(())
1604 }
1603 }
1605
1604
1606 /// Check with a mix of tracked and non-tracked items
1605 /// Check with a mix of tracked and non-tracked items
1607 #[test]
1606 #[test]
1608 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1607 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1609 let mut map = OwningDirstateMap::new_empty(vec![]);
1608 let mut map = OwningDirstateMap::new_empty(vec![]);
1610
1609
1611 // A file that was just added
1610 // A file that was just added
1612 map.set_tracked(p(b"some/nested/path"))?;
1611 map.set_tracked(p(b"some/nested/path"))?;
1613 // This has no information, the dirstate should ignore it
1612 // This has no information, the dirstate should ignore it
1614 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1613 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1615 assert_does_not_exist(&map, b"some/file");
1614 assert_does_not_exist(&map, b"some/file");
1616
1615
1617 // A file that was removed
1616 // A file that was removed
1618 map.reset_state(
1617 map.reset_state(
1619 p(b"some/nested/file"),
1618 p(b"some/nested/file"),
1620 false,
1619 false,
1621 true,
1620 true,
1622 false,
1621 false,
1623 false,
1622 false,
1624 None,
1623 None,
1625 )?;
1624 )?;
1626 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1625 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1627 // Only present in p2
1626 // Only present in p2
1628 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1627 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1629 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1628 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1630 // A file that was merged
1629 // A file that was merged
1631 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1630 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1632 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1631 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1633 // A file that is added, with info from p2
1632 // A file that is added, with info from p2
1634 // XXX is that actually possible?
1633 // XXX is that actually possible?
1635 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1634 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1636 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1635 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1637 // A clean file
1636 // A clean file
1638 // One layer without any files to test deletion cascade
1637 // One layer without any files to test deletion cascade
1639 map.reset_state(
1638 map.reset_state(
1640 p(b"some/other/nested/path"),
1639 p(b"some/other/nested/path"),
1641 true,
1640 true,
1642 true,
1641 true,
1643 false,
1642 false,
1644 false,
1643 false,
1645 None,
1644 None,
1646 )?;
1645 )?;
1647 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1646 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1648
1647
1649 assert_eq!(map.len(), 6);
1648 assert_eq!(map.len(), 6);
1650 assert_eq!(tracked_descendants(&map, b"some"), 3);
1649 assert_eq!(tracked_descendants(&map, b"some"), 3);
1651 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1650 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1652 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1651 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1653 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1652 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1654 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1653 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1655 assert_eq!(
1654 assert_eq!(
1656 descendants_with_an_entry(&map, b"some/other/nested/path"),
1655 descendants_with_an_entry(&map, b"some/other/nested/path"),
1657 0
1656 0
1658 );
1657 );
1659 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1658 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1660 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1659 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1661
1660
1662 // might as well check this
1661 // might as well check this
1663 map.set_untracked(p(b"path/does/not/exist"))?;
1662 map.set_untracked(p(b"path/does/not/exist"))?;
1664 assert_eq!(map.len(), 6);
1663 assert_eq!(map.len(), 6);
1665
1664
1666 map.set_untracked(p(b"some/other/nested/path"))?;
1665 map.set_untracked(p(b"some/other/nested/path"))?;
1667 // It is set untracked but not deleted since it held other information
1666 // It is set untracked but not deleted since it held other information
1668 assert_eq!(map.len(), 6);
1667 assert_eq!(map.len(), 6);
1669 assert_eq!(tracked_descendants(&map, b"some"), 2);
1668 assert_eq!(tracked_descendants(&map, b"some"), 2);
1670 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1669 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1671 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1670 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1672 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1671 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1673 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1672 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1674 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1673 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1675
1674
1676 map.set_untracked(p(b"some/nested/path"))?;
1675 map.set_untracked(p(b"some/nested/path"))?;
1677 // It is set untracked *and* deleted since it was only added
1676 // It is set untracked *and* deleted since it was only added
1678 assert_eq!(map.len(), 5);
1677 assert_eq!(map.len(), 5);
1679 assert_eq!(tracked_descendants(&map, b"some"), 1);
1678 assert_eq!(tracked_descendants(&map, b"some"), 1);
1680 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1679 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1681 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1680 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1682 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1681 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1683 assert_does_not_exist(&map, b"some/nested/path");
1682 assert_does_not_exist(&map, b"some/nested/path");
1684
1683
1685 map.set_untracked(p(b"root_file"))?;
1684 map.set_untracked(p(b"root_file"))?;
1686 // Untracked but not deleted
1685 // Untracked but not deleted
1687 assert_eq!(map.len(), 5);
1686 assert_eq!(map.len(), 5);
1688 assert!(map.get(p(b"root_file"))?.is_some());
1687 assert!(map.get(p(b"root_file"))?.is_some());
1689
1688
1690 map.set_untracked(p(b"some/file2"))?;
1689 map.set_untracked(p(b"some/file2"))?;
1691 assert_eq!(map.len(), 5);
1690 assert_eq!(map.len(), 5);
1692 assert_eq!(tracked_descendants(&map, b"some"), 0);
1691 assert_eq!(tracked_descendants(&map, b"some"), 0);
1693 assert!(map.get(p(b"some/file2"))?.is_some());
1692 assert!(map.get(p(b"some/file2"))?.is_some());
1694
1693
1695 map.set_untracked(p(b"some/file3"))?;
1694 map.set_untracked(p(b"some/file3"))?;
1696 assert_eq!(map.len(), 5);
1695 assert_eq!(map.len(), 5);
1697 assert_eq!(tracked_descendants(&map, b"some"), 0);
1696 assert_eq!(tracked_descendants(&map, b"some"), 0);
1698 assert!(map.get(p(b"some/file3"))?.is_some());
1697 assert!(map.get(p(b"some/file3"))?.is_some());
1699
1698
1700 Ok(())
1699 Ok(())
1701 }
1700 }
1702
1701
1703 /// Check that copies counter is correctly updated
1702 /// Check that copies counter is correctly updated
1704 #[test]
1703 #[test]
1705 fn test_copy_source() -> Result<(), DirstateError> {
1704 fn test_copy_source() -> Result<(), DirstateError> {
1706 let mut map = OwningDirstateMap::new_empty(vec![]);
1705 let mut map = OwningDirstateMap::new_empty(vec![]);
1707
1706
1708 // Clean file
1707 // Clean file
1709 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1708 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1710 // Merged file
1709 // Merged file
1711 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1710 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1712 // Removed file
1711 // Removed file
1713 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1712 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1714 // Added file
1713 // Added file
1715 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1714 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1716 // Add copy
1715 // Add copy
1717 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1716 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1718 assert_eq!(map.copy_map_len(), 1);
1717 assert_eq!(map.copy_map_len(), 1);
1719
1718
1720 // Copy override
1719 // Copy override
1721 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1720 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1722 assert_eq!(map.copy_map_len(), 1);
1721 assert_eq!(map.copy_map_len(), 1);
1723
1722
1724 // Multiple copies
1723 // Multiple copies
1725 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1724 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1726 assert_eq!(map.copy_map_len(), 2);
1725 assert_eq!(map.copy_map_len(), 2);
1727
1726
1728 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1727 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1729 assert_eq!(map.copy_map_len(), 3);
1728 assert_eq!(map.copy_map_len(), 3);
1730
1729
1731 // Added, so the entry is completely removed
1730 // Added, so the entry is completely removed
1732 map.set_untracked(p(b"files/added"))?;
1731 map.set_untracked(p(b"files/added"))?;
1733 assert_does_not_exist(&map, b"files/added");
1732 assert_does_not_exist(&map, b"files/added");
1734 assert_eq!(map.copy_map_len(), 2);
1733 assert_eq!(map.copy_map_len(), 2);
1735
1734
1736 // Removed, so the entry is kept around, so is its copy
1735 // Removed, so the entry is kept around, so is its copy
1737 map.set_untracked(p(b"removed"))?;
1736 map.set_untracked(p(b"removed"))?;
1738 assert!(map.get(p(b"removed"))?.is_some());
1737 assert!(map.get(p(b"removed"))?.is_some());
1739 assert_eq!(map.copy_map_len(), 2);
1738 assert_eq!(map.copy_map_len(), 2);
1740
1739
1741 // Clean, so the entry is kept around, but not its copy
1740 // Clean, so the entry is kept around, but not its copy
1742 map.set_untracked(p(b"files/clean"))?;
1741 map.set_untracked(p(b"files/clean"))?;
1743 assert!(map.get(p(b"files/clean"))?.is_some());
1742 assert!(map.get(p(b"files/clean"))?.is_some());
1744 assert_eq!(map.copy_map_len(), 1);
1743 assert_eq!(map.copy_map_len(), 1);
1745
1744
1746 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1745 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1747 assert_eq!(map.copy_map_len(), 2);
1746 assert_eq!(map.copy_map_len(), 2);
1748
1747
1749 // Info from p2, so its copy source info is kept around
1748 // Info from p2, so its copy source info is kept around
1750 map.set_untracked(p(b"files/from_p2"))?;
1749 map.set_untracked(p(b"files/from_p2"))?;
1751 assert!(map.get(p(b"files/from_p2"))?.is_some());
1750 assert!(map.get(p(b"files/from_p2"))?.is_some());
1752 assert_eq!(map.copy_map_len(), 2);
1751 assert_eq!(map.copy_map_len(), 2);
1753
1752
1754 Ok(())
1753 Ok(())
1755 }
1754 }
1756
1755
1757 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1756 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1758 /// does not actually come from the disk, but it's opaque to the code being
1757 /// does not actually come from the disk, but it's opaque to the code being
1759 /// tested.
1758 /// tested.
1760 #[test]
1759 #[test]
1761 fn test_on_disk() -> Result<(), DirstateError> {
1760 fn test_on_disk() -> Result<(), DirstateError> {
1762 // First let's create some data to put "on disk"
1761 // First let's create some data to put "on disk"
1763 let mut map = OwningDirstateMap::new_empty(vec![]);
1762 let mut map = OwningDirstateMap::new_empty(vec![]);
1764
1763
1765 // A file that was just added
1764 // A file that was just added
1766 map.set_tracked(p(b"some/nested/added"))?;
1765 map.set_tracked(p(b"some/nested/added"))?;
1767 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1766 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1768
1767
1769 // A file that was removed
1768 // A file that was removed
1770 map.reset_state(
1769 map.reset_state(
1771 p(b"some/nested/removed"),
1770 p(b"some/nested/removed"),
1772 false,
1771 false,
1773 true,
1772 true,
1774 false,
1773 false,
1775 false,
1774 false,
1776 None,
1775 None,
1777 )?;
1776 )?;
1778 // Only present in p2
1777 // Only present in p2
1779 map.reset_state(
1778 map.reset_state(
1780 p(b"other/p2_info_only"),
1779 p(b"other/p2_info_only"),
1781 false,
1780 false,
1782 false,
1781 false,
1783 true,
1782 true,
1784 false,
1783 false,
1785 None,
1784 None,
1786 )?;
1785 )?;
1787 map.copy_map_insert(
1786 map.copy_map_insert(
1788 p(b"other/p2_info_only"),
1787 p(b"other/p2_info_only"),
1789 p(b"other/p2_info_copy_source"),
1788 p(b"other/p2_info_copy_source"),
1790 )?;
1789 )?;
1791 // A file that was merged
1790 // A file that was merged
1792 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1791 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1793 // A file that is added, with info from p2
1792 // A file that is added, with info from p2
1794 // XXX is that actually possible?
1793 // XXX is that actually possible?
1795 map.reset_state(
1794 map.reset_state(
1796 p(b"other/added_with_p2"),
1795 p(b"other/added_with_p2"),
1797 true,
1796 true,
1798 false,
1797 false,
1799 true,
1798 true,
1800 false,
1799 false,
1801 None,
1800 None,
1802 )?;
1801 )?;
1803 // One layer without any files to test deletion cascade
1802 // One layer without any files to test deletion cascade
1804 // A clean file
1803 // A clean file
1805 map.reset_state(
1804 map.reset_state(
1806 p(b"some/other/nested/clean"),
1805 p(b"some/other/nested/clean"),
1807 true,
1806 true,
1808 true,
1807 true,
1809 false,
1808 false,
1810 false,
1809 false,
1811 None,
1810 None,
1812 )?;
1811 )?;
1813
1812
1814 let (packed, metadata, _should_append, _old_data_size) =
1813 let (packed, metadata, _should_append, _old_data_size) =
1815 map.pack_v2(false)?;
1814 map.pack_v2(false)?;
1816 let packed_len = packed.len();
1815 let packed_len = packed.len();
1817 assert!(packed_len > 0);
1816 assert!(packed_len > 0);
1818
1817
1819 // Recreate "from disk"
1818 // Recreate "from disk"
1820 let mut map = OwningDirstateMap::new_v2(
1819 let mut map = OwningDirstateMap::new_v2(
1821 packed,
1820 packed,
1822 packed_len,
1821 packed_len,
1823 metadata.as_bytes(),
1822 metadata.as_bytes(),
1824 )?;
1823 )?;
1825
1824
1826 // Check that everything is accounted for
1825 // Check that everything is accounted for
1827 assert!(map.contains_key(p(b"some/nested/added"))?);
1826 assert!(map.contains_key(p(b"some/nested/added"))?);
1828 assert!(map.contains_key(p(b"some/nested/removed"))?);
1827 assert!(map.contains_key(p(b"some/nested/removed"))?);
1829 assert!(map.contains_key(p(b"merged"))?);
1828 assert!(map.contains_key(p(b"merged"))?);
1830 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1829 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1831 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1830 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1832 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1831 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1833 assert_eq!(
1832 assert_eq!(
1834 map.copy_map_get(p(b"some/nested/added"))?,
1833 map.copy_map_get(p(b"some/nested/added"))?,
1835 Some(p(b"added_copy_source"))
1834 Some(p(b"added_copy_source"))
1836 );
1835 );
1837 assert_eq!(
1836 assert_eq!(
1838 map.copy_map_get(p(b"other/p2_info_only"))?,
1837 map.copy_map_get(p(b"other/p2_info_only"))?,
1839 Some(p(b"other/p2_info_copy_source"))
1838 Some(p(b"other/p2_info_copy_source"))
1840 );
1839 );
1841 assert_eq!(tracked_descendants(&map, b"some"), 2);
1840 assert_eq!(tracked_descendants(&map, b"some"), 2);
1842 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1841 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1843 assert_eq!(tracked_descendants(&map, b"other"), 1);
1842 assert_eq!(tracked_descendants(&map, b"other"), 1);
1844 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1843 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1845 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1844 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1846 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1845 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1847 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1846 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1848 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1847 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1849 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1848 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1850 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1849 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1851 assert_eq!(map.len(), 6);
1850 assert_eq!(map.len(), 6);
1852 assert_eq!(map.get_map().unreachable_bytes, 0);
1851 assert_eq!(map.get_map().unreachable_bytes, 0);
1853 assert_eq!(map.copy_map_len(), 2);
1852 assert_eq!(map.copy_map_len(), 2);
1854
1853
1855 // Shouldn't change anything since it's already not tracked
1854 // Shouldn't change anything since it's already not tracked
1856 map.set_untracked(p(b"some/nested/removed"))?;
1855 map.set_untracked(p(b"some/nested/removed"))?;
1857 assert_eq!(map.get_map().unreachable_bytes, 0);
1856 assert_eq!(map.get_map().unreachable_bytes, 0);
1858
1857
1859 match map.get_map().root {
1858 match map.get_map().root {
1860 ChildNodes::InMemory(_) => {
1859 ChildNodes::InMemory(_) => {
1861 panic!("root should not have been mutated")
1860 panic!("root should not have been mutated")
1862 }
1861 }
1863 _ => (),
1862 _ => (),
1864 }
1863 }
1865 // We haven't mutated enough (nothing, actually), we should still be in
1864 // We haven't mutated enough (nothing, actually), we should still be in
1866 // the append strategy
1865 // the append strategy
1867 assert!(map.get_map().write_should_append());
1866 assert!(map.get_map().write_should_append());
1868
1867
1869 // But this mutates the structure, so there should be unreachable_bytes
1868 // But this mutates the structure, so there should be unreachable_bytes
1870 assert!(map.set_untracked(p(b"some/nested/added"))?);
1869 assert!(map.set_untracked(p(b"some/nested/added"))?);
1871 let unreachable_bytes = map.get_map().unreachable_bytes;
1870 let unreachable_bytes = map.get_map().unreachable_bytes;
1872 assert!(unreachable_bytes > 0);
1871 assert!(unreachable_bytes > 0);
1873
1872
1874 match map.get_map().root {
1873 match map.get_map().root {
1875 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1874 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1876 _ => (),
1875 _ => (),
1877 }
1876 }
1878
1877
1879 // This should not mutate the structure either, since `root` has
1878 // This should not mutate the structure either, since `root` has
1880 // already been mutated along with its direct children.
1879 // already been mutated along with its direct children.
1881 map.set_untracked(p(b"merged"))?;
1880 map.set_untracked(p(b"merged"))?;
1882 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1881 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1883
1882
1884 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1883 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1885 NodeRef::InMemory(_, _) => {
1884 NodeRef::InMemory(_, _) => {
1886 panic!("'other/added_with_p2' should not have been mutated")
1885 panic!("'other/added_with_p2' should not have been mutated")
1887 }
1886 }
1888 _ => (),
1887 _ => (),
1889 }
1888 }
1890 // But this should, since it's in a different path
1889 // But this should, since it's in a different path
1891 // than `<root>some/nested/add`
1890 // than `<root>some/nested/add`
1892 map.set_untracked(p(b"other/added_with_p2"))?;
1891 map.set_untracked(p(b"other/added_with_p2"))?;
1893 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1892 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1894
1893
1895 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1894 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1896 NodeRef::OnDisk(_) => {
1895 NodeRef::OnDisk(_) => {
1897 panic!("'other/added_with_p2' should have been mutated")
1896 panic!("'other/added_with_p2' should have been mutated")
1898 }
1897 }
1899 _ => (),
1898 _ => (),
1900 }
1899 }
1901
1900
1902 // We have rewritten most of the tree, we should create a new file
1901 // We have rewritten most of the tree, we should create a new file
1903 assert!(!map.get_map().write_should_append());
1902 assert!(!map.get_map().write_should_append());
1904
1903
1905 Ok(())
1904 Ok(())
1906 }
1905 }
1907 }
1906 }
@@ -1,1003 +1,1002 b''
1 use crate::dirstate::entry::TruncatedTimestamp;
1 use crate::dirstate::entry::TruncatedTimestamp;
2 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate::status::IgnoreFnType;
3 use crate::dirstate::status::StatusPath;
3 use crate::dirstate::status::StatusPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
10 use crate::matchers::get_ignore_function;
10 use crate::matchers::get_ignore_function;
11 use crate::matchers::Matcher;
11 use crate::matchers::Matcher;
12 use crate::utils::files::get_bytes_from_os_string;
12 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_bytes_from_path;
13 use crate::utils::files::get_bytes_from_path;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
16 use crate::BadMatch;
16 use crate::BadMatch;
17 use crate::DirstateStatus;
17 use crate::DirstateStatus;
18 use crate::HgPathCow;
18 use crate::HgPathCow;
19 use crate::PatternFileWarning;
19 use crate::PatternFileWarning;
20 use crate::StatusError;
20 use crate::StatusError;
21 use crate::StatusOptions;
21 use crate::StatusOptions;
22 use micro_timer::timed;
23 use once_cell::sync::OnceCell;
22 use once_cell::sync::OnceCell;
24 use rayon::prelude::*;
23 use rayon::prelude::*;
25 use sha1::{Digest, Sha1};
24 use sha1::{Digest, Sha1};
26 use std::borrow::Cow;
25 use std::borrow::Cow;
27 use std::io;
26 use std::io;
28 use std::path::Path;
27 use std::path::Path;
29 use std::path::PathBuf;
28 use std::path::PathBuf;
30 use std::sync::Mutex;
29 use std::sync::Mutex;
31 use std::time::SystemTime;
30 use std::time::SystemTime;
32
31
33 /// Returns the status of the working directory compared to its parent
32 /// Returns the status of the working directory compared to its parent
34 /// changeset.
33 /// changeset.
35 ///
34 ///
36 /// This algorithm is based on traversing the filesystem tree (`fs` in function
35 /// This algorithm is based on traversing the filesystem tree (`fs` in function
37 /// and variable names) and dirstate tree at the same time. The core of this
36 /// and variable names) and dirstate tree at the same time. The core of this
38 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
37 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
39 /// and its use of `itertools::merge_join_by`. When reaching a path that only
38 /// and its use of `itertools::merge_join_by`. When reaching a path that only
40 /// exists in one of the two trees, depending on information requested by
39 /// exists in one of the two trees, depending on information requested by
41 /// `options` we may need to traverse the remaining subtree.
40 /// `options` we may need to traverse the remaining subtree.
42 #[timed]
41 #[logging_timer::time("trace")]
43 pub fn status<'dirstate>(
42 pub fn status<'dirstate>(
44 dmap: &'dirstate mut DirstateMap,
43 dmap: &'dirstate mut DirstateMap,
45 matcher: &(dyn Matcher + Sync),
44 matcher: &(dyn Matcher + Sync),
46 root_dir: PathBuf,
45 root_dir: PathBuf,
47 ignore_files: Vec<PathBuf>,
46 ignore_files: Vec<PathBuf>,
48 options: StatusOptions,
47 options: StatusOptions,
49 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
48 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
50 {
49 {
51 // Force the global rayon threadpool to not exceed 16 concurrent threads.
50 // Force the global rayon threadpool to not exceed 16 concurrent threads.
52 // This is a stop-gap measure until we figure out why using more than 16
51 // This is a stop-gap measure until we figure out why using more than 16
53 // threads makes `status` slower for each additional thread.
52 // threads makes `status` slower for each additional thread.
54 // We use `ok()` in case the global threadpool has already been
53 // We use `ok()` in case the global threadpool has already been
55 // instantiated in `rhg` or some other caller.
54 // instantiated in `rhg` or some other caller.
56 // TODO find the underlying cause and fix it, then remove this.
55 // TODO find the underlying cause and fix it, then remove this.
57 rayon::ThreadPoolBuilder::new()
56 rayon::ThreadPoolBuilder::new()
58 .num_threads(16.min(rayon::current_num_threads()))
57 .num_threads(16.min(rayon::current_num_threads()))
59 .build_global()
58 .build_global()
60 .ok();
59 .ok();
61
60
62 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
61 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
63 if options.list_ignored || options.list_unknown {
62 if options.list_ignored || options.list_unknown {
64 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
63 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
65 DirstateVersion::V1 => {
64 DirstateVersion::V1 => {
66 let (ignore_fn, warnings) = get_ignore_function(
65 let (ignore_fn, warnings) = get_ignore_function(
67 ignore_files,
66 ignore_files,
68 &root_dir,
67 &root_dir,
69 &mut |_source, _pattern_bytes| {},
68 &mut |_source, _pattern_bytes| {},
70 )?;
69 )?;
71 (ignore_fn, warnings, None)
70 (ignore_fn, warnings, None)
72 }
71 }
73 DirstateVersion::V2 => {
72 DirstateVersion::V2 => {
74 let mut hasher = Sha1::new();
73 let mut hasher = Sha1::new();
75 let (ignore_fn, warnings) = get_ignore_function(
74 let (ignore_fn, warnings) = get_ignore_function(
76 ignore_files,
75 ignore_files,
77 &root_dir,
76 &root_dir,
78 &mut |source, pattern_bytes| {
77 &mut |source, pattern_bytes| {
79 // If inside the repo, use the relative version to
78 // If inside the repo, use the relative version to
80 // make it deterministic inside tests.
79 // make it deterministic inside tests.
81 // The performance hit should be negligible.
80 // The performance hit should be negligible.
82 let source = source
81 let source = source
83 .strip_prefix(&root_dir)
82 .strip_prefix(&root_dir)
84 .unwrap_or(source);
83 .unwrap_or(source);
85 let source = get_bytes_from_path(source);
84 let source = get_bytes_from_path(source);
86
85
87 let mut subhasher = Sha1::new();
86 let mut subhasher = Sha1::new();
88 subhasher.update(pattern_bytes);
87 subhasher.update(pattern_bytes);
89 let patterns_hash = subhasher.finalize();
88 let patterns_hash = subhasher.finalize();
90
89
91 hasher.update(source);
90 hasher.update(source);
92 hasher.update(b" ");
91 hasher.update(b" ");
93 hasher.update(patterns_hash);
92 hasher.update(patterns_hash);
94 hasher.update(b"\n");
93 hasher.update(b"\n");
95 },
94 },
96 )?;
95 )?;
97 let new_hash = *hasher.finalize().as_ref();
96 let new_hash = *hasher.finalize().as_ref();
98 let changed = new_hash != dmap.ignore_patterns_hash;
97 let changed = new_hash != dmap.ignore_patterns_hash;
99 dmap.ignore_patterns_hash = new_hash;
98 dmap.ignore_patterns_hash = new_hash;
100 (ignore_fn, warnings, Some(changed))
99 (ignore_fn, warnings, Some(changed))
101 }
100 }
102 };
101 };
103 (ignore_fn, warnings, changed)
102 (ignore_fn, warnings, changed)
104 } else {
103 } else {
105 (Box::new(|&_| true), vec![], None)
104 (Box::new(|&_| true), vec![], None)
106 };
105 };
107
106
108 let filesystem_time_at_status_start =
107 let filesystem_time_at_status_start =
109 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
108 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
110
109
111 // If the repository is under the current directory, prefer using a
110 // If the repository is under the current directory, prefer using a
112 // relative path, so the kernel needs to traverse fewer directory in every
111 // relative path, so the kernel needs to traverse fewer directory in every
113 // call to `read_dir` or `symlink_metadata`.
112 // call to `read_dir` or `symlink_metadata`.
114 // This is effective in the common case where the current directory is the
113 // This is effective in the common case where the current directory is the
115 // repository root.
114 // repository root.
116
115
117 // TODO: Better yet would be to use libc functions like `openat` and
116 // TODO: Better yet would be to use libc functions like `openat` and
118 // `fstatat` to remove such repeated traversals entirely, but the standard
117 // `fstatat` to remove such repeated traversals entirely, but the standard
119 // library does not provide APIs based on those.
118 // library does not provide APIs based on those.
120 // Maybe with a crate like https://crates.io/crates/openat instead?
119 // Maybe with a crate like https://crates.io/crates/openat instead?
121 let root_dir = if let Some(relative) = std::env::current_dir()
120 let root_dir = if let Some(relative) = std::env::current_dir()
122 .ok()
121 .ok()
123 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
122 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
124 {
123 {
125 relative
124 relative
126 } else {
125 } else {
127 &root_dir
126 &root_dir
128 };
127 };
129
128
130 let outcome = DirstateStatus {
129 let outcome = DirstateStatus {
131 filesystem_time_at_status_start,
130 filesystem_time_at_status_start,
132 ..Default::default()
131 ..Default::default()
133 };
132 };
134 let common = StatusCommon {
133 let common = StatusCommon {
135 dmap,
134 dmap,
136 options,
135 options,
137 matcher,
136 matcher,
138 ignore_fn,
137 ignore_fn,
139 outcome: Mutex::new(outcome),
138 outcome: Mutex::new(outcome),
140 ignore_patterns_have_changed: patterns_changed,
139 ignore_patterns_have_changed: patterns_changed,
141 new_cacheable_directories: Default::default(),
140 new_cacheable_directories: Default::default(),
142 outdated_cached_directories: Default::default(),
141 outdated_cached_directories: Default::default(),
143 filesystem_time_at_status_start,
142 filesystem_time_at_status_start,
144 };
143 };
145 let is_at_repo_root = true;
144 let is_at_repo_root = true;
146 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
145 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
147 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
146 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
148 let root_cached_mtime = None;
147 let root_cached_mtime = None;
149 // If the path we have for the repository root is a symlink, do follow it.
148 // If the path we have for the repository root is a symlink, do follow it.
150 // (As opposed to symlinks within the working directory which are not
149 // (As opposed to symlinks within the working directory which are not
151 // followed, using `std::fs::symlink_metadata`.)
150 // followed, using `std::fs::symlink_metadata`.)
152 common.traverse_fs_directory_and_dirstate(
151 common.traverse_fs_directory_and_dirstate(
153 &has_ignored_ancestor,
152 &has_ignored_ancestor,
154 dmap.root.as_ref(),
153 dmap.root.as_ref(),
155 hg_path,
154 hg_path,
156 &DirEntry {
155 &DirEntry {
157 hg_path: Cow::Borrowed(HgPath::new(b"")),
156 hg_path: Cow::Borrowed(HgPath::new(b"")),
158 fs_path: Cow::Borrowed(&root_dir),
157 fs_path: Cow::Borrowed(&root_dir),
159 symlink_metadata: None,
158 symlink_metadata: None,
160 file_type: FakeFileType::Directory,
159 file_type: FakeFileType::Directory,
161 },
160 },
162 root_cached_mtime,
161 root_cached_mtime,
163 is_at_repo_root,
162 is_at_repo_root,
164 )?;
163 )?;
165 let mut outcome = common.outcome.into_inner().unwrap();
164 let mut outcome = common.outcome.into_inner().unwrap();
166 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
165 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
167 let outdated = common.outdated_cached_directories.into_inner().unwrap();
166 let outdated = common.outdated_cached_directories.into_inner().unwrap();
168
167
169 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
168 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
170 || !outdated.is_empty()
169 || !outdated.is_empty()
171 || (!new_cacheable.is_empty()
170 || (!new_cacheable.is_empty()
172 && dmap.dirstate_version == DirstateVersion::V2);
171 && dmap.dirstate_version == DirstateVersion::V2);
173
172
174 // Remove outdated mtimes before adding new mtimes, in case a given
173 // Remove outdated mtimes before adding new mtimes, in case a given
175 // directory is both
174 // directory is both
176 for path in &outdated {
175 for path in &outdated {
177 dmap.clear_cached_mtime(path)?;
176 dmap.clear_cached_mtime(path)?;
178 }
177 }
179 for (path, mtime) in &new_cacheable {
178 for (path, mtime) in &new_cacheable {
180 dmap.set_cached_mtime(path, *mtime)?;
179 dmap.set_cached_mtime(path, *mtime)?;
181 }
180 }
182
181
183 Ok((outcome, warnings))
182 Ok((outcome, warnings))
184 }
183 }
185
184
186 /// Bag of random things needed by various parts of the algorithm. Reduces the
185 /// Bag of random things needed by various parts of the algorithm. Reduces the
187 /// number of parameters passed to functions.
186 /// number of parameters passed to functions.
188 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
187 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
189 dmap: &'tree DirstateMap<'on_disk>,
188 dmap: &'tree DirstateMap<'on_disk>,
190 options: StatusOptions,
189 options: StatusOptions,
191 matcher: &'a (dyn Matcher + Sync),
190 matcher: &'a (dyn Matcher + Sync),
192 ignore_fn: IgnoreFnType<'a>,
191 ignore_fn: IgnoreFnType<'a>,
193 outcome: Mutex<DirstateStatus<'on_disk>>,
192 outcome: Mutex<DirstateStatus<'on_disk>>,
194 /// New timestamps of directories to be used for caching their readdirs
193 /// New timestamps of directories to be used for caching their readdirs
195 new_cacheable_directories:
194 new_cacheable_directories:
196 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
195 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
197 /// Used to invalidate the readdir cache of directories
196 /// Used to invalidate the readdir cache of directories
198 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
197 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
199
198
200 /// Whether ignore files like `.hgignore` have changed since the previous
199 /// Whether ignore files like `.hgignore` have changed since the previous
201 /// time a `status()` call wrote their hash to the dirstate. `None` means
200 /// time a `status()` call wrote their hash to the dirstate. `None` means
202 /// we don’t know as this run doesn’t list either ignored or uknown files
201 /// we don’t know as this run doesn’t list either ignored or uknown files
203 /// and therefore isn’t reading `.hgignore`.
202 /// and therefore isn’t reading `.hgignore`.
204 ignore_patterns_have_changed: Option<bool>,
203 ignore_patterns_have_changed: Option<bool>,
205
204
206 /// The current time at the start of the `status()` algorithm, as measured
205 /// The current time at the start of the `status()` algorithm, as measured
207 /// and possibly truncated by the filesystem.
206 /// and possibly truncated by the filesystem.
208 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
207 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
209 }
208 }
210
209
211 enum Outcome {
210 enum Outcome {
212 Modified,
211 Modified,
213 Added,
212 Added,
214 Removed,
213 Removed,
215 Deleted,
214 Deleted,
216 Clean,
215 Clean,
217 Ignored,
216 Ignored,
218 Unknown,
217 Unknown,
219 Unsure,
218 Unsure,
220 }
219 }
221
220
222 /// Lazy computation of whether a given path has a hgignored
221 /// Lazy computation of whether a given path has a hgignored
223 /// ancestor.
222 /// ancestor.
224 struct HasIgnoredAncestor<'a> {
223 struct HasIgnoredAncestor<'a> {
225 /// `path` and `parent` constitute the inputs to the computation,
224 /// `path` and `parent` constitute the inputs to the computation,
226 /// `cache` stores the outcome.
225 /// `cache` stores the outcome.
227 path: &'a HgPath,
226 path: &'a HgPath,
228 parent: Option<&'a HasIgnoredAncestor<'a>>,
227 parent: Option<&'a HasIgnoredAncestor<'a>>,
229 cache: OnceCell<bool>,
228 cache: OnceCell<bool>,
230 }
229 }
231
230
232 impl<'a> HasIgnoredAncestor<'a> {
231 impl<'a> HasIgnoredAncestor<'a> {
233 fn create(
232 fn create(
234 parent: Option<&'a HasIgnoredAncestor<'a>>,
233 parent: Option<&'a HasIgnoredAncestor<'a>>,
235 path: &'a HgPath,
234 path: &'a HgPath,
236 ) -> HasIgnoredAncestor<'a> {
235 ) -> HasIgnoredAncestor<'a> {
237 Self {
236 Self {
238 path,
237 path,
239 parent,
238 parent,
240 cache: OnceCell::new(),
239 cache: OnceCell::new(),
241 }
240 }
242 }
241 }
243
242
244 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
243 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
245 match self.parent {
244 match self.parent {
246 None => false,
245 None => false,
247 Some(parent) => {
246 Some(parent) => {
248 *(parent.cache.get_or_init(|| {
247 *(parent.cache.get_or_init(|| {
249 parent.force(ignore_fn) || ignore_fn(&self.path)
248 parent.force(ignore_fn) || ignore_fn(&self.path)
250 }))
249 }))
251 }
250 }
252 }
251 }
253 }
252 }
254 }
253 }
255
254
256 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
255 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
257 fn push_outcome(
256 fn push_outcome(
258 &self,
257 &self,
259 which: Outcome,
258 which: Outcome,
260 dirstate_node: &NodeRef<'tree, 'on_disk>,
259 dirstate_node: &NodeRef<'tree, 'on_disk>,
261 ) -> Result<(), DirstateV2ParseError> {
260 ) -> Result<(), DirstateV2ParseError> {
262 let path = dirstate_node
261 let path = dirstate_node
263 .full_path_borrowed(self.dmap.on_disk)?
262 .full_path_borrowed(self.dmap.on_disk)?
264 .detach_from_tree();
263 .detach_from_tree();
265 let copy_source = if self.options.list_copies {
264 let copy_source = if self.options.list_copies {
266 dirstate_node
265 dirstate_node
267 .copy_source_borrowed(self.dmap.on_disk)?
266 .copy_source_borrowed(self.dmap.on_disk)?
268 .map(|source| source.detach_from_tree())
267 .map(|source| source.detach_from_tree())
269 } else {
268 } else {
270 None
269 None
271 };
270 };
272 self.push_outcome_common(which, path, copy_source);
271 self.push_outcome_common(which, path, copy_source);
273 Ok(())
272 Ok(())
274 }
273 }
275
274
276 fn push_outcome_without_copy_source(
275 fn push_outcome_without_copy_source(
277 &self,
276 &self,
278 which: Outcome,
277 which: Outcome,
279 path: &BorrowedPath<'_, 'on_disk>,
278 path: &BorrowedPath<'_, 'on_disk>,
280 ) {
279 ) {
281 self.push_outcome_common(which, path.detach_from_tree(), None)
280 self.push_outcome_common(which, path.detach_from_tree(), None)
282 }
281 }
283
282
284 fn push_outcome_common(
283 fn push_outcome_common(
285 &self,
284 &self,
286 which: Outcome,
285 which: Outcome,
287 path: HgPathCow<'on_disk>,
286 path: HgPathCow<'on_disk>,
288 copy_source: Option<HgPathCow<'on_disk>>,
287 copy_source: Option<HgPathCow<'on_disk>>,
289 ) {
288 ) {
290 let mut outcome = self.outcome.lock().unwrap();
289 let mut outcome = self.outcome.lock().unwrap();
291 let vec = match which {
290 let vec = match which {
292 Outcome::Modified => &mut outcome.modified,
291 Outcome::Modified => &mut outcome.modified,
293 Outcome::Added => &mut outcome.added,
292 Outcome::Added => &mut outcome.added,
294 Outcome::Removed => &mut outcome.removed,
293 Outcome::Removed => &mut outcome.removed,
295 Outcome::Deleted => &mut outcome.deleted,
294 Outcome::Deleted => &mut outcome.deleted,
296 Outcome::Clean => &mut outcome.clean,
295 Outcome::Clean => &mut outcome.clean,
297 Outcome::Ignored => &mut outcome.ignored,
296 Outcome::Ignored => &mut outcome.ignored,
298 Outcome::Unknown => &mut outcome.unknown,
297 Outcome::Unknown => &mut outcome.unknown,
299 Outcome::Unsure => &mut outcome.unsure,
298 Outcome::Unsure => &mut outcome.unsure,
300 };
299 };
301 vec.push(StatusPath { path, copy_source });
300 vec.push(StatusPath { path, copy_source });
302 }
301 }
303
302
304 fn read_dir(
303 fn read_dir(
305 &self,
304 &self,
306 hg_path: &HgPath,
305 hg_path: &HgPath,
307 fs_path: &Path,
306 fs_path: &Path,
308 is_at_repo_root: bool,
307 is_at_repo_root: bool,
309 ) -> Result<Vec<DirEntry>, ()> {
308 ) -> Result<Vec<DirEntry>, ()> {
310 DirEntry::read_dir(fs_path, is_at_repo_root)
309 DirEntry::read_dir(fs_path, is_at_repo_root)
311 .map_err(|error| self.io_error(error, hg_path))
310 .map_err(|error| self.io_error(error, hg_path))
312 }
311 }
313
312
314 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
313 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
315 let errno = error.raw_os_error().expect("expected real OS error");
314 let errno = error.raw_os_error().expect("expected real OS error");
316 self.outcome
315 self.outcome
317 .lock()
316 .lock()
318 .unwrap()
317 .unwrap()
319 .bad
318 .bad
320 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
319 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
321 }
320 }
322
321
323 fn check_for_outdated_directory_cache(
322 fn check_for_outdated_directory_cache(
324 &self,
323 &self,
325 dirstate_node: &NodeRef<'tree, 'on_disk>,
324 dirstate_node: &NodeRef<'tree, 'on_disk>,
326 ) -> Result<bool, DirstateV2ParseError> {
325 ) -> Result<bool, DirstateV2ParseError> {
327 if self.ignore_patterns_have_changed == Some(true)
326 if self.ignore_patterns_have_changed == Some(true)
328 && dirstate_node.cached_directory_mtime()?.is_some()
327 && dirstate_node.cached_directory_mtime()?.is_some()
329 {
328 {
330 self.outdated_cached_directories.lock().unwrap().push(
329 self.outdated_cached_directories.lock().unwrap().push(
331 dirstate_node
330 dirstate_node
332 .full_path_borrowed(self.dmap.on_disk)?
331 .full_path_borrowed(self.dmap.on_disk)?
333 .detach_from_tree(),
332 .detach_from_tree(),
334 );
333 );
335 return Ok(true);
334 return Ok(true);
336 }
335 }
337 Ok(false)
336 Ok(false)
338 }
337 }
339
338
340 /// If this returns true, we can get accurate results by only using
339 /// If this returns true, we can get accurate results by only using
341 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
340 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
342 /// need to call `read_dir`.
341 /// need to call `read_dir`.
343 fn can_skip_fs_readdir(
342 fn can_skip_fs_readdir(
344 &self,
343 &self,
345 directory_entry: &DirEntry,
344 directory_entry: &DirEntry,
346 cached_directory_mtime: Option<TruncatedTimestamp>,
345 cached_directory_mtime: Option<TruncatedTimestamp>,
347 ) -> bool {
346 ) -> bool {
348 if !self.options.list_unknown && !self.options.list_ignored {
347 if !self.options.list_unknown && !self.options.list_ignored {
349 // All states that we care about listing have corresponding
348 // All states that we care about listing have corresponding
350 // dirstate entries.
349 // dirstate entries.
351 // This happens for example with `hg status -mard`.
350 // This happens for example with `hg status -mard`.
352 return true;
351 return true;
353 }
352 }
354 if !self.options.list_ignored
353 if !self.options.list_ignored
355 && self.ignore_patterns_have_changed == Some(false)
354 && self.ignore_patterns_have_changed == Some(false)
356 {
355 {
357 if let Some(cached_mtime) = cached_directory_mtime {
356 if let Some(cached_mtime) = cached_directory_mtime {
358 // The dirstate contains a cached mtime for this directory, set
357 // The dirstate contains a cached mtime for this directory, set
359 // by a previous run of the `status` algorithm which found this
358 // by a previous run of the `status` algorithm which found this
360 // directory eligible for `read_dir` caching.
359 // directory eligible for `read_dir` caching.
361 if let Ok(meta) = directory_entry.symlink_metadata() {
360 if let Ok(meta) = directory_entry.symlink_metadata() {
362 if cached_mtime
361 if cached_mtime
363 .likely_equal_to_mtime_of(&meta)
362 .likely_equal_to_mtime_of(&meta)
364 .unwrap_or(false)
363 .unwrap_or(false)
365 {
364 {
366 // The mtime of that directory has not changed
365 // The mtime of that directory has not changed
367 // since then, which means that the results of
366 // since then, which means that the results of
368 // `read_dir` should also be unchanged.
367 // `read_dir` should also be unchanged.
369 return true;
368 return true;
370 }
369 }
371 }
370 }
372 }
371 }
373 }
372 }
374 false
373 false
375 }
374 }
376
375
377 /// Returns whether all child entries of the filesystem directory have a
376 /// Returns whether all child entries of the filesystem directory have a
378 /// corresponding dirstate node or are ignored.
377 /// corresponding dirstate node or are ignored.
379 fn traverse_fs_directory_and_dirstate<'ancestor>(
378 fn traverse_fs_directory_and_dirstate<'ancestor>(
380 &self,
379 &self,
381 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
380 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
382 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
381 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
383 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
382 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
384 directory_entry: &DirEntry,
383 directory_entry: &DirEntry,
385 cached_directory_mtime: Option<TruncatedTimestamp>,
384 cached_directory_mtime: Option<TruncatedTimestamp>,
386 is_at_repo_root: bool,
385 is_at_repo_root: bool,
387 ) -> Result<bool, DirstateV2ParseError> {
386 ) -> Result<bool, DirstateV2ParseError> {
388 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
387 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
389 dirstate_nodes
388 dirstate_nodes
390 .par_iter()
389 .par_iter()
391 .map(|dirstate_node| {
390 .map(|dirstate_node| {
392 let fs_path = &directory_entry.fs_path;
391 let fs_path = &directory_entry.fs_path;
393 let fs_path = fs_path.join(get_path_from_bytes(
392 let fs_path = fs_path.join(get_path_from_bytes(
394 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
393 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
395 ));
394 ));
396 match std::fs::symlink_metadata(&fs_path) {
395 match std::fs::symlink_metadata(&fs_path) {
397 Ok(fs_metadata) => {
396 Ok(fs_metadata) => {
398 let file_type =
397 let file_type =
399 match fs_metadata.file_type().try_into() {
398 match fs_metadata.file_type().try_into() {
400 Ok(file_type) => file_type,
399 Ok(file_type) => file_type,
401 Err(_) => return Ok(()),
400 Err(_) => return Ok(()),
402 };
401 };
403 let entry = DirEntry {
402 let entry = DirEntry {
404 hg_path: Cow::Borrowed(
403 hg_path: Cow::Borrowed(
405 dirstate_node
404 dirstate_node
406 .full_path(&self.dmap.on_disk)?,
405 .full_path(&self.dmap.on_disk)?,
407 ),
406 ),
408 fs_path: Cow::Borrowed(&fs_path),
407 fs_path: Cow::Borrowed(&fs_path),
409 symlink_metadata: Some(fs_metadata),
408 symlink_metadata: Some(fs_metadata),
410 file_type,
409 file_type,
411 };
410 };
412 self.traverse_fs_and_dirstate(
411 self.traverse_fs_and_dirstate(
413 &entry,
412 &entry,
414 dirstate_node,
413 dirstate_node,
415 has_ignored_ancestor,
414 has_ignored_ancestor,
416 )
415 )
417 }
416 }
418 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
417 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
419 self.traverse_dirstate_only(dirstate_node)
418 self.traverse_dirstate_only(dirstate_node)
420 }
419 }
421 Err(error) => {
420 Err(error) => {
422 let hg_path =
421 let hg_path =
423 dirstate_node.full_path(self.dmap.on_disk)?;
422 dirstate_node.full_path(self.dmap.on_disk)?;
424 Ok(self.io_error(error, hg_path))
423 Ok(self.io_error(error, hg_path))
425 }
424 }
426 }
425 }
427 })
426 })
428 .collect::<Result<_, _>>()?;
427 .collect::<Result<_, _>>()?;
429
428
430 // We don’t know, so conservatively say this isn’t the case
429 // We don’t know, so conservatively say this isn’t the case
431 let children_all_have_dirstate_node_or_are_ignored = false;
430 let children_all_have_dirstate_node_or_are_ignored = false;
432
431
433 return Ok(children_all_have_dirstate_node_or_are_ignored);
432 return Ok(children_all_have_dirstate_node_or_are_ignored);
434 }
433 }
435
434
436 let mut fs_entries = if let Ok(entries) = self.read_dir(
435 let mut fs_entries = if let Ok(entries) = self.read_dir(
437 directory_hg_path,
436 directory_hg_path,
438 &directory_entry.fs_path,
437 &directory_entry.fs_path,
439 is_at_repo_root,
438 is_at_repo_root,
440 ) {
439 ) {
441 entries
440 entries
442 } else {
441 } else {
443 // Treat an unreadable directory (typically because of insufficient
442 // Treat an unreadable directory (typically because of insufficient
444 // permissions) like an empty directory. `self.read_dir` has
443 // permissions) like an empty directory. `self.read_dir` has
445 // already called `self.io_error` so a warning will be emitted.
444 // already called `self.io_error` so a warning will be emitted.
446 Vec::new()
445 Vec::new()
447 };
446 };
448
447
449 // `merge_join_by` requires both its input iterators to be sorted:
448 // `merge_join_by` requires both its input iterators to be sorted:
450
449
451 let dirstate_nodes = dirstate_nodes.sorted();
450 let dirstate_nodes = dirstate_nodes.sorted();
452 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
451 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
453 // https://github.com/rust-lang/rust/issues/34162
452 // https://github.com/rust-lang/rust/issues/34162
454 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
453 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
455
454
456 // Propagate here any error that would happen inside the comparison
455 // Propagate here any error that would happen inside the comparison
457 // callback below
456 // callback below
458 for dirstate_node in &dirstate_nodes {
457 for dirstate_node in &dirstate_nodes {
459 dirstate_node.base_name(self.dmap.on_disk)?;
458 dirstate_node.base_name(self.dmap.on_disk)?;
460 }
459 }
461 itertools::merge_join_by(
460 itertools::merge_join_by(
462 dirstate_nodes,
461 dirstate_nodes,
463 &fs_entries,
462 &fs_entries,
464 |dirstate_node, fs_entry| {
463 |dirstate_node, fs_entry| {
465 // This `unwrap` never panics because we already propagated
464 // This `unwrap` never panics because we already propagated
466 // those errors above
465 // those errors above
467 dirstate_node
466 dirstate_node
468 .base_name(self.dmap.on_disk)
467 .base_name(self.dmap.on_disk)
469 .unwrap()
468 .unwrap()
470 .cmp(&fs_entry.hg_path)
469 .cmp(&fs_entry.hg_path)
471 },
470 },
472 )
471 )
473 .par_bridge()
472 .par_bridge()
474 .map(|pair| {
473 .map(|pair| {
475 use itertools::EitherOrBoth::*;
474 use itertools::EitherOrBoth::*;
476 let has_dirstate_node_or_is_ignored;
475 let has_dirstate_node_or_is_ignored;
477 match pair {
476 match pair {
478 Both(dirstate_node, fs_entry) => {
477 Both(dirstate_node, fs_entry) => {
479 self.traverse_fs_and_dirstate(
478 self.traverse_fs_and_dirstate(
480 &fs_entry,
479 &fs_entry,
481 dirstate_node,
480 dirstate_node,
482 has_ignored_ancestor,
481 has_ignored_ancestor,
483 )?;
482 )?;
484 has_dirstate_node_or_is_ignored = true
483 has_dirstate_node_or_is_ignored = true
485 }
484 }
486 Left(dirstate_node) => {
485 Left(dirstate_node) => {
487 self.traverse_dirstate_only(dirstate_node)?;
486 self.traverse_dirstate_only(dirstate_node)?;
488 has_dirstate_node_or_is_ignored = true;
487 has_dirstate_node_or_is_ignored = true;
489 }
488 }
490 Right(fs_entry) => {
489 Right(fs_entry) => {
491 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
490 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
492 has_ignored_ancestor.force(&self.ignore_fn),
491 has_ignored_ancestor.force(&self.ignore_fn),
493 directory_hg_path,
492 directory_hg_path,
494 fs_entry,
493 fs_entry,
495 )
494 )
496 }
495 }
497 }
496 }
498 Ok(has_dirstate_node_or_is_ignored)
497 Ok(has_dirstate_node_or_is_ignored)
499 })
498 })
500 .try_reduce(|| true, |a, b| Ok(a && b))
499 .try_reduce(|| true, |a, b| Ok(a && b))
501 }
500 }
502
501
503 fn traverse_fs_and_dirstate<'ancestor>(
502 fn traverse_fs_and_dirstate<'ancestor>(
504 &self,
503 &self,
505 fs_entry: &DirEntry,
504 fs_entry: &DirEntry,
506 dirstate_node: NodeRef<'tree, 'on_disk>,
505 dirstate_node: NodeRef<'tree, 'on_disk>,
507 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
506 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
508 ) -> Result<(), DirstateV2ParseError> {
507 ) -> Result<(), DirstateV2ParseError> {
509 let outdated_dircache =
508 let outdated_dircache =
510 self.check_for_outdated_directory_cache(&dirstate_node)?;
509 self.check_for_outdated_directory_cache(&dirstate_node)?;
511 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
510 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
512 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
511 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
513 if !file_or_symlink {
512 if !file_or_symlink {
514 // If we previously had a file here, it was removed (with
513 // If we previously had a file here, it was removed (with
515 // `hg rm` or similar) or deleted before it could be
514 // `hg rm` or similar) or deleted before it could be
516 // replaced by a directory or something else.
515 // replaced by a directory or something else.
517 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
516 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
518 }
517 }
519 if fs_entry.is_dir() {
518 if fs_entry.is_dir() {
520 if self.options.collect_traversed_dirs {
519 if self.options.collect_traversed_dirs {
521 self.outcome
520 self.outcome
522 .lock()
521 .lock()
523 .unwrap()
522 .unwrap()
524 .traversed
523 .traversed
525 .push(hg_path.detach_from_tree())
524 .push(hg_path.detach_from_tree())
526 }
525 }
527 let is_ignored = HasIgnoredAncestor::create(
526 let is_ignored = HasIgnoredAncestor::create(
528 Some(&has_ignored_ancestor),
527 Some(&has_ignored_ancestor),
529 hg_path,
528 hg_path,
530 );
529 );
531 let is_at_repo_root = false;
530 let is_at_repo_root = false;
532 let children_all_have_dirstate_node_or_are_ignored = self
531 let children_all_have_dirstate_node_or_are_ignored = self
533 .traverse_fs_directory_and_dirstate(
532 .traverse_fs_directory_and_dirstate(
534 &is_ignored,
533 &is_ignored,
535 dirstate_node.children(self.dmap.on_disk)?,
534 dirstate_node.children(self.dmap.on_disk)?,
536 hg_path,
535 hg_path,
537 fs_entry,
536 fs_entry,
538 dirstate_node.cached_directory_mtime()?,
537 dirstate_node.cached_directory_mtime()?,
539 is_at_repo_root,
538 is_at_repo_root,
540 )?;
539 )?;
541 self.maybe_save_directory_mtime(
540 self.maybe_save_directory_mtime(
542 children_all_have_dirstate_node_or_are_ignored,
541 children_all_have_dirstate_node_or_are_ignored,
543 fs_entry,
542 fs_entry,
544 dirstate_node,
543 dirstate_node,
545 outdated_dircache,
544 outdated_dircache,
546 )?
545 )?
547 } else {
546 } else {
548 if file_or_symlink && self.matcher.matches(&hg_path) {
547 if file_or_symlink && self.matcher.matches(&hg_path) {
549 if let Some(entry) = dirstate_node.entry()? {
548 if let Some(entry) = dirstate_node.entry()? {
550 if !entry.any_tracked() {
549 if !entry.any_tracked() {
551 // Forward-compat if we start tracking unknown/ignored
550 // Forward-compat if we start tracking unknown/ignored
552 // files for caching reasons
551 // files for caching reasons
553 self.mark_unknown_or_ignored(
552 self.mark_unknown_or_ignored(
554 has_ignored_ancestor.force(&self.ignore_fn),
553 has_ignored_ancestor.force(&self.ignore_fn),
555 &hg_path,
554 &hg_path,
556 );
555 );
557 }
556 }
558 if entry.added() {
557 if entry.added() {
559 self.push_outcome(Outcome::Added, &dirstate_node)?;
558 self.push_outcome(Outcome::Added, &dirstate_node)?;
560 } else if entry.removed() {
559 } else if entry.removed() {
561 self.push_outcome(Outcome::Removed, &dirstate_node)?;
560 self.push_outcome(Outcome::Removed, &dirstate_node)?;
562 } else if entry.modified() {
561 } else if entry.modified() {
563 self.push_outcome(Outcome::Modified, &dirstate_node)?;
562 self.push_outcome(Outcome::Modified, &dirstate_node)?;
564 } else {
563 } else {
565 self.handle_normal_file(&dirstate_node, fs_entry)?;
564 self.handle_normal_file(&dirstate_node, fs_entry)?;
566 }
565 }
567 } else {
566 } else {
568 // `node.entry.is_none()` indicates a "directory"
567 // `node.entry.is_none()` indicates a "directory"
569 // node, but the filesystem has a file
568 // node, but the filesystem has a file
570 self.mark_unknown_or_ignored(
569 self.mark_unknown_or_ignored(
571 has_ignored_ancestor.force(&self.ignore_fn),
570 has_ignored_ancestor.force(&self.ignore_fn),
572 hg_path,
571 hg_path,
573 );
572 );
574 }
573 }
575 }
574 }
576
575
577 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
576 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
578 {
577 {
579 self.traverse_dirstate_only(child_node)?
578 self.traverse_dirstate_only(child_node)?
580 }
579 }
581 }
580 }
582 Ok(())
581 Ok(())
583 }
582 }
584
583
585 /// Save directory mtime if applicable.
584 /// Save directory mtime if applicable.
586 ///
585 ///
587 /// `outdated_directory_cache` is `true` if we've just invalidated the
586 /// `outdated_directory_cache` is `true` if we've just invalidated the
588 /// cache for this directory in `check_for_outdated_directory_cache`,
587 /// cache for this directory in `check_for_outdated_directory_cache`,
589 /// which forces the update.
588 /// which forces the update.
590 fn maybe_save_directory_mtime(
589 fn maybe_save_directory_mtime(
591 &self,
590 &self,
592 children_all_have_dirstate_node_or_are_ignored: bool,
591 children_all_have_dirstate_node_or_are_ignored: bool,
593 directory_entry: &DirEntry,
592 directory_entry: &DirEntry,
594 dirstate_node: NodeRef<'tree, 'on_disk>,
593 dirstate_node: NodeRef<'tree, 'on_disk>,
595 outdated_directory_cache: bool,
594 outdated_directory_cache: bool,
596 ) -> Result<(), DirstateV2ParseError> {
595 ) -> Result<(), DirstateV2ParseError> {
597 if !children_all_have_dirstate_node_or_are_ignored {
596 if !children_all_have_dirstate_node_or_are_ignored {
598 return Ok(());
597 return Ok(());
599 }
598 }
600 // All filesystem directory entries from `read_dir` have a
599 // All filesystem directory entries from `read_dir` have a
601 // corresponding node in the dirstate, so we can reconstitute the
600 // corresponding node in the dirstate, so we can reconstitute the
602 // names of those entries without calling `read_dir` again.
601 // names of those entries without calling `read_dir` again.
603
602
604 // TODO: use let-else here and below when available:
603 // TODO: use let-else here and below when available:
605 // https://github.com/rust-lang/rust/issues/87335
604 // https://github.com/rust-lang/rust/issues/87335
606 let status_start = if let Some(status_start) =
605 let status_start = if let Some(status_start) =
607 &self.filesystem_time_at_status_start
606 &self.filesystem_time_at_status_start
608 {
607 {
609 status_start
608 status_start
610 } else {
609 } else {
611 return Ok(());
610 return Ok(());
612 };
611 };
613
612
614 // Although the Rust standard library’s `SystemTime` type
613 // Although the Rust standard library’s `SystemTime` type
615 // has nanosecond precision, the times reported for a
614 // has nanosecond precision, the times reported for a
616 // directory’s (or file’s) modified time may have lower
615 // directory’s (or file’s) modified time may have lower
617 // resolution based on the filesystem (for example ext3
616 // resolution based on the filesystem (for example ext3
618 // only stores integer seconds), kernel (see
617 // only stores integer seconds), kernel (see
619 // https://stackoverflow.com/a/14393315/1162888), etc.
618 // https://stackoverflow.com/a/14393315/1162888), etc.
620 let metadata = match directory_entry.symlink_metadata() {
619 let metadata = match directory_entry.symlink_metadata() {
621 Ok(meta) => meta,
620 Ok(meta) => meta,
622 Err(_) => return Ok(()),
621 Err(_) => return Ok(()),
623 };
622 };
624 let directory_mtime = if let Ok(option) =
623 let directory_mtime = if let Ok(option) =
625 TruncatedTimestamp::for_reliable_mtime_of(&metadata, status_start)
624 TruncatedTimestamp::for_reliable_mtime_of(&metadata, status_start)
626 {
625 {
627 if let Some(directory_mtime) = option {
626 if let Some(directory_mtime) = option {
628 directory_mtime
627 directory_mtime
629 } else {
628 } else {
630 // The directory was modified too recently,
629 // The directory was modified too recently,
631 // don’t cache its `read_dir` results.
630 // don’t cache its `read_dir` results.
632 //
631 //
633 // 1. A change to this directory (direct child was
632 // 1. A change to this directory (direct child was
634 // added or removed) cause its mtime to be set
633 // added or removed) cause its mtime to be set
635 // (possibly truncated) to `directory_mtime`
634 // (possibly truncated) to `directory_mtime`
636 // 2. This `status` algorithm calls `read_dir`
635 // 2. This `status` algorithm calls `read_dir`
637 // 3. An other change is made to the same directory is
636 // 3. An other change is made to the same directory is
638 // made so that calling `read_dir` agin would give
637 // made so that calling `read_dir` agin would give
639 // different results, but soon enough after 1. that
638 // different results, but soon enough after 1. that
640 // the mtime stays the same
639 // the mtime stays the same
641 //
640 //
642 // On a system where the time resolution poor, this
641 // On a system where the time resolution poor, this
643 // scenario is not unlikely if all three steps are caused
642 // scenario is not unlikely if all three steps are caused
644 // by the same script.
643 // by the same script.
645 return Ok(());
644 return Ok(());
646 }
645 }
647 } else {
646 } else {
648 // OS/libc does not support mtime?
647 // OS/libc does not support mtime?
649 return Ok(());
648 return Ok(());
650 };
649 };
651 // We’ve observed (through `status_start`) that time has
650 // We’ve observed (through `status_start`) that time has
652 // “progressed” since `directory_mtime`, so any further
651 // “progressed” since `directory_mtime`, so any further
653 // change to this directory is extremely likely to cause a
652 // change to this directory is extremely likely to cause a
654 // different mtime.
653 // different mtime.
655 //
654 //
656 // Having the same mtime again is not entirely impossible
655 // Having the same mtime again is not entirely impossible
657 // since the system clock is not monotonous. It could jump
656 // since the system clock is not monotonous. It could jump
658 // backward to some point before `directory_mtime`, then a
657 // backward to some point before `directory_mtime`, then a
659 // directory change could potentially happen during exactly
658 // directory change could potentially happen during exactly
660 // the wrong tick.
659 // the wrong tick.
661 //
660 //
662 // We deem this scenario (unlike the previous one) to be
661 // We deem this scenario (unlike the previous one) to be
663 // unlikely enough in practice.
662 // unlikely enough in practice.
664
663
665 let is_up_to_date = if let Some(cached) =
664 let is_up_to_date = if let Some(cached) =
666 dirstate_node.cached_directory_mtime()?
665 dirstate_node.cached_directory_mtime()?
667 {
666 {
668 !outdated_directory_cache && cached.likely_equal(directory_mtime)
667 !outdated_directory_cache && cached.likely_equal(directory_mtime)
669 } else {
668 } else {
670 false
669 false
671 };
670 };
672 if !is_up_to_date {
671 if !is_up_to_date {
673 let hg_path = dirstate_node
672 let hg_path = dirstate_node
674 .full_path_borrowed(self.dmap.on_disk)?
673 .full_path_borrowed(self.dmap.on_disk)?
675 .detach_from_tree();
674 .detach_from_tree();
676 self.new_cacheable_directories
675 self.new_cacheable_directories
677 .lock()
676 .lock()
678 .unwrap()
677 .unwrap()
679 .push((hg_path, directory_mtime))
678 .push((hg_path, directory_mtime))
680 }
679 }
681 Ok(())
680 Ok(())
682 }
681 }
683
682
684 /// A file that is clean in the dirstate was found in the filesystem
683 /// A file that is clean in the dirstate was found in the filesystem
685 fn handle_normal_file(
684 fn handle_normal_file(
686 &self,
685 &self,
687 dirstate_node: &NodeRef<'tree, 'on_disk>,
686 dirstate_node: &NodeRef<'tree, 'on_disk>,
688 fs_entry: &DirEntry,
687 fs_entry: &DirEntry,
689 ) -> Result<(), DirstateV2ParseError> {
688 ) -> Result<(), DirstateV2ParseError> {
690 // Keep the low 31 bits
689 // Keep the low 31 bits
691 fn truncate_u64(value: u64) -> i32 {
690 fn truncate_u64(value: u64) -> i32 {
692 (value & 0x7FFF_FFFF) as i32
691 (value & 0x7FFF_FFFF) as i32
693 }
692 }
694
693
695 let fs_metadata = match fs_entry.symlink_metadata() {
694 let fs_metadata = match fs_entry.symlink_metadata() {
696 Ok(meta) => meta,
695 Ok(meta) => meta,
697 Err(_) => return Ok(()),
696 Err(_) => return Ok(()),
698 };
697 };
699
698
700 let entry = dirstate_node
699 let entry = dirstate_node
701 .entry()?
700 .entry()?
702 .expect("handle_normal_file called with entry-less node");
701 .expect("handle_normal_file called with entry-less node");
703 let mode_changed =
702 let mode_changed =
704 || self.options.check_exec && entry.mode_changed(&fs_metadata);
703 || self.options.check_exec && entry.mode_changed(&fs_metadata);
705 let size = entry.size();
704 let size = entry.size();
706 let size_changed = size != truncate_u64(fs_metadata.len());
705 let size_changed = size != truncate_u64(fs_metadata.len());
707 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
706 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
708 // issue6456: Size returned may be longer due to encryption
707 // issue6456: Size returned may be longer due to encryption
709 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
708 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
710 self.push_outcome(Outcome::Unsure, dirstate_node)?
709 self.push_outcome(Outcome::Unsure, dirstate_node)?
711 } else if dirstate_node.has_copy_source()
710 } else if dirstate_node.has_copy_source()
712 || entry.is_from_other_parent()
711 || entry.is_from_other_parent()
713 || (size >= 0 && (size_changed || mode_changed()))
712 || (size >= 0 && (size_changed || mode_changed()))
714 {
713 {
715 self.push_outcome(Outcome::Modified, dirstate_node)?
714 self.push_outcome(Outcome::Modified, dirstate_node)?
716 } else {
715 } else {
717 let mtime_looks_clean;
716 let mtime_looks_clean;
718 if let Some(dirstate_mtime) = entry.truncated_mtime() {
717 if let Some(dirstate_mtime) = entry.truncated_mtime() {
719 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
718 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
720 .expect("OS/libc does not support mtime?");
719 .expect("OS/libc does not support mtime?");
721 // There might be a change in the future if for example the
720 // There might be a change in the future if for example the
722 // internal clock become off while process run, but this is a
721 // internal clock become off while process run, but this is a
723 // case where the issues the user would face
722 // case where the issues the user would face
724 // would be a lot worse and there is nothing we
723 // would be a lot worse and there is nothing we
725 // can really do.
724 // can really do.
726 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
725 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
727 } else {
726 } else {
728 // No mtime in the dirstate entry
727 // No mtime in the dirstate entry
729 mtime_looks_clean = false
728 mtime_looks_clean = false
730 };
729 };
731 if !mtime_looks_clean {
730 if !mtime_looks_clean {
732 self.push_outcome(Outcome::Unsure, dirstate_node)?
731 self.push_outcome(Outcome::Unsure, dirstate_node)?
733 } else if self.options.list_clean {
732 } else if self.options.list_clean {
734 self.push_outcome(Outcome::Clean, dirstate_node)?
733 self.push_outcome(Outcome::Clean, dirstate_node)?
735 }
734 }
736 }
735 }
737 Ok(())
736 Ok(())
738 }
737 }
739
738
740 /// A node in the dirstate tree has no corresponding filesystem entry
739 /// A node in the dirstate tree has no corresponding filesystem entry
741 fn traverse_dirstate_only(
740 fn traverse_dirstate_only(
742 &self,
741 &self,
743 dirstate_node: NodeRef<'tree, 'on_disk>,
742 dirstate_node: NodeRef<'tree, 'on_disk>,
744 ) -> Result<(), DirstateV2ParseError> {
743 ) -> Result<(), DirstateV2ParseError> {
745 self.check_for_outdated_directory_cache(&dirstate_node)?;
744 self.check_for_outdated_directory_cache(&dirstate_node)?;
746 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
745 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
747 dirstate_node
746 dirstate_node
748 .children(self.dmap.on_disk)?
747 .children(self.dmap.on_disk)?
749 .par_iter()
748 .par_iter()
750 .map(|child_node| self.traverse_dirstate_only(child_node))
749 .map(|child_node| self.traverse_dirstate_only(child_node))
751 .collect()
750 .collect()
752 }
751 }
753
752
754 /// A node in the dirstate tree has no corresponding *file* on the
753 /// A node in the dirstate tree has no corresponding *file* on the
755 /// filesystem
754 /// filesystem
756 ///
755 ///
757 /// Does nothing on a "directory" node
756 /// Does nothing on a "directory" node
758 fn mark_removed_or_deleted_if_file(
757 fn mark_removed_or_deleted_if_file(
759 &self,
758 &self,
760 dirstate_node: &NodeRef<'tree, 'on_disk>,
759 dirstate_node: &NodeRef<'tree, 'on_disk>,
761 ) -> Result<(), DirstateV2ParseError> {
760 ) -> Result<(), DirstateV2ParseError> {
762 if let Some(entry) = dirstate_node.entry()? {
761 if let Some(entry) = dirstate_node.entry()? {
763 if !entry.any_tracked() {
762 if !entry.any_tracked() {
764 // Future-compat for when we start storing ignored and unknown
763 // Future-compat for when we start storing ignored and unknown
765 // files for caching reasons
764 // files for caching reasons
766 return Ok(());
765 return Ok(());
767 }
766 }
768 let path = dirstate_node.full_path(self.dmap.on_disk)?;
767 let path = dirstate_node.full_path(self.dmap.on_disk)?;
769 if self.matcher.matches(path) {
768 if self.matcher.matches(path) {
770 if entry.removed() {
769 if entry.removed() {
771 self.push_outcome(Outcome::Removed, dirstate_node)?
770 self.push_outcome(Outcome::Removed, dirstate_node)?
772 } else {
771 } else {
773 self.push_outcome(Outcome::Deleted, &dirstate_node)?
772 self.push_outcome(Outcome::Deleted, &dirstate_node)?
774 }
773 }
775 }
774 }
776 }
775 }
777 Ok(())
776 Ok(())
778 }
777 }
779
778
780 /// Something in the filesystem has no corresponding dirstate node
779 /// Something in the filesystem has no corresponding dirstate node
781 ///
780 ///
782 /// Returns whether that path is ignored
781 /// Returns whether that path is ignored
783 fn traverse_fs_only(
782 fn traverse_fs_only(
784 &self,
783 &self,
785 has_ignored_ancestor: bool,
784 has_ignored_ancestor: bool,
786 directory_hg_path: &HgPath,
785 directory_hg_path: &HgPath,
787 fs_entry: &DirEntry,
786 fs_entry: &DirEntry,
788 ) -> bool {
787 ) -> bool {
789 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
788 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
790 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
789 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
791 if fs_entry.is_dir() {
790 if fs_entry.is_dir() {
792 let is_ignored =
791 let is_ignored =
793 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
792 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
794 let traverse_children = if is_ignored {
793 let traverse_children = if is_ignored {
795 // Descendants of an ignored directory are all ignored
794 // Descendants of an ignored directory are all ignored
796 self.options.list_ignored
795 self.options.list_ignored
797 } else {
796 } else {
798 // Descendants of an unknown directory may be either unknown or
797 // Descendants of an unknown directory may be either unknown or
799 // ignored
798 // ignored
800 self.options.list_unknown || self.options.list_ignored
799 self.options.list_unknown || self.options.list_ignored
801 };
800 };
802 if traverse_children {
801 if traverse_children {
803 let is_at_repo_root = false;
802 let is_at_repo_root = false;
804 if let Ok(children_fs_entries) =
803 if let Ok(children_fs_entries) =
805 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
804 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
806 {
805 {
807 children_fs_entries.par_iter().for_each(|child_fs_entry| {
806 children_fs_entries.par_iter().for_each(|child_fs_entry| {
808 self.traverse_fs_only(
807 self.traverse_fs_only(
809 is_ignored,
808 is_ignored,
810 &hg_path,
809 &hg_path,
811 child_fs_entry,
810 child_fs_entry,
812 );
811 );
813 })
812 })
814 }
813 }
815 if self.options.collect_traversed_dirs {
814 if self.options.collect_traversed_dirs {
816 self.outcome.lock().unwrap().traversed.push(hg_path.into())
815 self.outcome.lock().unwrap().traversed.push(hg_path.into())
817 }
816 }
818 }
817 }
819 is_ignored
818 is_ignored
820 } else {
819 } else {
821 if file_or_symlink {
820 if file_or_symlink {
822 if self.matcher.matches(&hg_path) {
821 if self.matcher.matches(&hg_path) {
823 self.mark_unknown_or_ignored(
822 self.mark_unknown_or_ignored(
824 has_ignored_ancestor,
823 has_ignored_ancestor,
825 &BorrowedPath::InMemory(&hg_path),
824 &BorrowedPath::InMemory(&hg_path),
826 )
825 )
827 } else {
826 } else {
828 // We haven’t computed whether this path is ignored. It
827 // We haven’t computed whether this path is ignored. It
829 // might not be, and a future run of status might have a
828 // might not be, and a future run of status might have a
830 // different matcher that matches it. So treat it as not
829 // different matcher that matches it. So treat it as not
831 // ignored. That is, inhibit readdir caching of the parent
830 // ignored. That is, inhibit readdir caching of the parent
832 // directory.
831 // directory.
833 false
832 false
834 }
833 }
835 } else {
834 } else {
836 // This is neither a directory, a plain file, or a symlink.
835 // This is neither a directory, a plain file, or a symlink.
837 // Treat it like an ignored file.
836 // Treat it like an ignored file.
838 true
837 true
839 }
838 }
840 }
839 }
841 }
840 }
842
841
843 /// Returns whether that path is ignored
842 /// Returns whether that path is ignored
844 fn mark_unknown_or_ignored(
843 fn mark_unknown_or_ignored(
845 &self,
844 &self,
846 has_ignored_ancestor: bool,
845 has_ignored_ancestor: bool,
847 hg_path: &BorrowedPath<'_, 'on_disk>,
846 hg_path: &BorrowedPath<'_, 'on_disk>,
848 ) -> bool {
847 ) -> bool {
849 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
848 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
850 if is_ignored {
849 if is_ignored {
851 if self.options.list_ignored {
850 if self.options.list_ignored {
852 self.push_outcome_without_copy_source(
851 self.push_outcome_without_copy_source(
853 Outcome::Ignored,
852 Outcome::Ignored,
854 hg_path,
853 hg_path,
855 )
854 )
856 }
855 }
857 } else {
856 } else {
858 if self.options.list_unknown {
857 if self.options.list_unknown {
859 self.push_outcome_without_copy_source(
858 self.push_outcome_without_copy_source(
860 Outcome::Unknown,
859 Outcome::Unknown,
861 hg_path,
860 hg_path,
862 )
861 )
863 }
862 }
864 }
863 }
865 is_ignored
864 is_ignored
866 }
865 }
867 }
866 }
868
867
869 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
868 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
870 /// care about.
869 /// care about.
871 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
870 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
872 enum FakeFileType {
871 enum FakeFileType {
873 File,
872 File,
874 Directory,
873 Directory,
875 Symlink,
874 Symlink,
876 }
875 }
877
876
878 impl TryFrom<std::fs::FileType> for FakeFileType {
877 impl TryFrom<std::fs::FileType> for FakeFileType {
879 type Error = ();
878 type Error = ();
880
879
881 fn try_from(f: std::fs::FileType) -> Result<Self, Self::Error> {
880 fn try_from(f: std::fs::FileType) -> Result<Self, Self::Error> {
882 if f.is_dir() {
881 if f.is_dir() {
883 Ok(Self::Directory)
882 Ok(Self::Directory)
884 } else if f.is_file() {
883 } else if f.is_file() {
885 Ok(Self::File)
884 Ok(Self::File)
886 } else if f.is_symlink() {
885 } else if f.is_symlink() {
887 Ok(Self::Symlink)
886 Ok(Self::Symlink)
888 } else {
887 } else {
889 // Things like FIFO etc.
888 // Things like FIFO etc.
890 Err(())
889 Err(())
891 }
890 }
892 }
891 }
893 }
892 }
894
893
895 struct DirEntry<'a> {
894 struct DirEntry<'a> {
896 /// Path as stored in the dirstate, or just the filename for optimization.
895 /// Path as stored in the dirstate, or just the filename for optimization.
897 hg_path: HgPathCow<'a>,
896 hg_path: HgPathCow<'a>,
898 /// Filesystem path
897 /// Filesystem path
899 fs_path: Cow<'a, Path>,
898 fs_path: Cow<'a, Path>,
900 /// Lazily computed
899 /// Lazily computed
901 symlink_metadata: Option<std::fs::Metadata>,
900 symlink_metadata: Option<std::fs::Metadata>,
902 /// Already computed for ergonomics.
901 /// Already computed for ergonomics.
903 file_type: FakeFileType,
902 file_type: FakeFileType,
904 }
903 }
905
904
906 impl<'a> DirEntry<'a> {
905 impl<'a> DirEntry<'a> {
907 /// Returns **unsorted** entries in the given directory, with name,
906 /// Returns **unsorted** entries in the given directory, with name,
908 /// metadata and file type.
907 /// metadata and file type.
909 ///
908 ///
910 /// If a `.hg` sub-directory is encountered:
909 /// If a `.hg` sub-directory is encountered:
911 ///
910 ///
912 /// * At the repository root, ignore that sub-directory
911 /// * At the repository root, ignore that sub-directory
913 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
912 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
914 /// list instead.
913 /// list instead.
915 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
914 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
916 // `read_dir` returns a "not found" error for the empty path
915 // `read_dir` returns a "not found" error for the empty path
917 let at_cwd = path == Path::new("");
916 let at_cwd = path == Path::new("");
918 let read_dir_path = if at_cwd { Path::new(".") } else { path };
917 let read_dir_path = if at_cwd { Path::new(".") } else { path };
919 let mut results = Vec::new();
918 let mut results = Vec::new();
920 for entry in read_dir_path.read_dir()? {
919 for entry in read_dir_path.read_dir()? {
921 let entry = entry?;
920 let entry = entry?;
922 let file_type = match entry.file_type() {
921 let file_type = match entry.file_type() {
923 Ok(v) => v,
922 Ok(v) => v,
924 Err(e) => {
923 Err(e) => {
925 // race with file deletion?
924 // race with file deletion?
926 if e.kind() == std::io::ErrorKind::NotFound {
925 if e.kind() == std::io::ErrorKind::NotFound {
927 continue;
926 continue;
928 } else {
927 } else {
929 return Err(e);
928 return Err(e);
930 }
929 }
931 }
930 }
932 };
931 };
933 let file_name = entry.file_name();
932 let file_name = entry.file_name();
934 // FIXME don't do this when cached
933 // FIXME don't do this when cached
935 if file_name == ".hg" {
934 if file_name == ".hg" {
936 if is_at_repo_root {
935 if is_at_repo_root {
937 // Skip the repo’s own .hg (might be a symlink)
936 // Skip the repo’s own .hg (might be a symlink)
938 continue;
937 continue;
939 } else if file_type.is_dir() {
938 } else if file_type.is_dir() {
940 // A .hg sub-directory at another location means a subrepo,
939 // A .hg sub-directory at another location means a subrepo,
941 // skip it entirely.
940 // skip it entirely.
942 return Ok(Vec::new());
941 return Ok(Vec::new());
943 }
942 }
944 }
943 }
945 let full_path = if at_cwd {
944 let full_path = if at_cwd {
946 file_name.clone().into()
945 file_name.clone().into()
947 } else {
946 } else {
948 entry.path()
947 entry.path()
949 };
948 };
950 let filename =
949 let filename =
951 Cow::Owned(get_bytes_from_os_string(file_name).into());
950 Cow::Owned(get_bytes_from_os_string(file_name).into());
952 let file_type = match FakeFileType::try_from(file_type) {
951 let file_type = match FakeFileType::try_from(file_type) {
953 Ok(file_type) => file_type,
952 Ok(file_type) => file_type,
954 Err(_) => continue,
953 Err(_) => continue,
955 };
954 };
956 results.push(DirEntry {
955 results.push(DirEntry {
957 hg_path: filename,
956 hg_path: filename,
958 fs_path: Cow::Owned(full_path.to_path_buf()),
957 fs_path: Cow::Owned(full_path.to_path_buf()),
959 symlink_metadata: None,
958 symlink_metadata: None,
960 file_type,
959 file_type,
961 })
960 })
962 }
961 }
963 Ok(results)
962 Ok(results)
964 }
963 }
965
964
966 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
965 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
967 match &self.symlink_metadata {
966 match &self.symlink_metadata {
968 Some(meta) => Ok(meta.clone()),
967 Some(meta) => Ok(meta.clone()),
969 None => std::fs::symlink_metadata(&self.fs_path),
968 None => std::fs::symlink_metadata(&self.fs_path),
970 }
969 }
971 }
970 }
972
971
973 fn is_dir(&self) -> bool {
972 fn is_dir(&self) -> bool {
974 self.file_type == FakeFileType::Directory
973 self.file_type == FakeFileType::Directory
975 }
974 }
976
975
977 fn is_file(&self) -> bool {
976 fn is_file(&self) -> bool {
978 self.file_type == FakeFileType::File
977 self.file_type == FakeFileType::File
979 }
978 }
980
979
981 fn is_symlink(&self) -> bool {
980 fn is_symlink(&self) -> bool {
982 self.file_type == FakeFileType::Symlink
981 self.file_type == FakeFileType::Symlink
983 }
982 }
984 }
983 }
985
984
986 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
985 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
987 /// of the give repository.
986 /// of the give repository.
988 ///
987 ///
989 /// This is similar to `SystemTime::now()`, with the result truncated to the
988 /// This is similar to `SystemTime::now()`, with the result truncated to the
990 /// same time resolution as other files’ modification times. Using `.hg`
989 /// same time resolution as other files’ modification times. Using `.hg`
991 /// instead of the system’s default temporary directory (such as `/tmp`) makes
990 /// instead of the system’s default temporary directory (such as `/tmp`) makes
992 /// it more likely the temporary file is in the same disk partition as contents
991 /// it more likely the temporary file is in the same disk partition as contents
993 /// of the working directory, which can matter since different filesystems may
992 /// of the working directory, which can matter since different filesystems may
994 /// store timestamps with different resolutions.
993 /// store timestamps with different resolutions.
995 ///
994 ///
996 /// This may fail, typically if we lack write permissions. In that case we
995 /// This may fail, typically if we lack write permissions. In that case we
997 /// should continue the `status()` algoritm anyway and consider the current
996 /// should continue the `status()` algoritm anyway and consider the current
998 /// date/time to be unknown.
997 /// date/time to be unknown.
999 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
998 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1000 tempfile::tempfile_in(repo_root.join(".hg"))?
999 tempfile::tempfile_in(repo_root.join(".hg"))?
1001 .metadata()?
1000 .metadata()?
1002 .modified()
1001 .modified()
1003 }
1002 }
@@ -1,140 +1,136 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
5 // GNU General Public License version 2 or any later version.
5 // GNU General Public License version 2 or any later version.
6
6
7 mod ancestors;
7 mod ancestors;
8 pub mod dagops;
8 pub mod dagops;
9 pub mod errors;
9 pub mod errors;
10 pub mod narrow;
10 pub mod narrow;
11 pub mod sparse;
11 pub mod sparse;
12 pub use ancestors::{AncestorsIterator, MissingAncestors};
12 pub use ancestors::{AncestorsIterator, MissingAncestors};
13 pub mod dirstate;
13 pub mod dirstate;
14 pub mod dirstate_tree;
14 pub mod dirstate_tree;
15 pub mod discovery;
15 pub mod discovery;
16 pub mod exit_codes;
16 pub mod exit_codes;
17 pub mod requirements;
17 pub mod requirements;
18 pub mod testing; // unconditionally built, for use from integration tests
18 pub mod testing; // unconditionally built, for use from integration tests
19 pub use dirstate::{
19 pub use dirstate::{
20 dirs_multiset::{DirsMultiset, DirsMultisetIter},
20 dirs_multiset::{DirsMultiset, DirsMultisetIter},
21 status::{
21 status::{
22 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
22 BadMatch, BadType, DirstateStatus, HgPathCow, StatusError,
23 StatusOptions,
23 StatusOptions,
24 },
24 },
25 DirstateEntry, DirstateParents, EntryState,
25 DirstateEntry, DirstateParents, EntryState,
26 };
26 };
27 pub mod copy_tracing;
27 pub mod copy_tracing;
28 mod filepatterns;
28 mod filepatterns;
29 pub mod matchers;
29 pub mod matchers;
30 pub mod repo;
30 pub mod repo;
31 pub mod revlog;
31 pub mod revlog;
32 pub use revlog::*;
32 pub use revlog::*;
33 pub mod checkexec;
33 pub mod checkexec;
34 pub mod config;
34 pub mod config;
35 pub mod lock;
35 pub mod lock;
36 pub mod logging;
36 pub mod logging;
37 pub mod operations;
37 pub mod operations;
38 pub mod revset;
38 pub mod revset;
39 pub mod utils;
39 pub mod utils;
40 pub mod vfs;
40 pub mod vfs;
41
41
42 use crate::utils::hg_path::{HgPathBuf, HgPathError};
42 use crate::utils::hg_path::{HgPathBuf, HgPathError};
43 pub use filepatterns::{
43 pub use filepatterns::{
44 parse_pattern_syntax, read_pattern_file, IgnorePattern,
44 parse_pattern_syntax, read_pattern_file, IgnorePattern,
45 PatternFileWarning, PatternSyntax,
45 PatternFileWarning, PatternSyntax,
46 };
46 };
47 use std::collections::HashMap;
47 use std::collections::HashMap;
48 use std::fmt;
48 use std::fmt;
49 use twox_hash::RandomXxHashBuilder64;
49 use twox_hash::RandomXxHashBuilder64;
50
50
51 /// This is a contract between the `micro-timer` crate and us, to expose
52 /// the `log` crate as `crate::log`.
53 use log;
54
55 pub type LineNumber = usize;
51 pub type LineNumber = usize;
56
52
57 /// Rust's default hasher is too slow because it tries to prevent collision
53 /// Rust's default hasher is too slow because it tries to prevent collision
58 /// attacks. We are not concerned about those: if an ill-minded person has
54 /// attacks. We are not concerned about those: if an ill-minded person has
59 /// write access to your repository, you have other issues.
55 /// write access to your repository, you have other issues.
60 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
56 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
61
57
62 // TODO: should this be the default `FastHashMap` for all of hg-core, not just
58 // TODO: should this be the default `FastHashMap` for all of hg-core, not just
63 // dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
59 // dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
64 pub type FastHashbrownMap<K, V> =
60 pub type FastHashbrownMap<K, V> =
65 hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
61 hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
66
62
67 #[derive(Debug, PartialEq)]
63 #[derive(Debug, PartialEq)]
68 pub enum DirstateMapError {
64 pub enum DirstateMapError {
69 PathNotFound(HgPathBuf),
65 PathNotFound(HgPathBuf),
70 EmptyPath,
66 EmptyPath,
71 InvalidPath(HgPathError),
67 InvalidPath(HgPathError),
72 }
68 }
73
69
74 impl fmt::Display for DirstateMapError {
70 impl fmt::Display for DirstateMapError {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 match self {
72 match self {
77 DirstateMapError::PathNotFound(_) => {
73 DirstateMapError::PathNotFound(_) => {
78 f.write_str("expected a value, found none")
74 f.write_str("expected a value, found none")
79 }
75 }
80 DirstateMapError::EmptyPath => {
76 DirstateMapError::EmptyPath => {
81 f.write_str("Overflow in dirstate.")
77 f.write_str("Overflow in dirstate.")
82 }
78 }
83 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
79 DirstateMapError::InvalidPath(path_error) => path_error.fmt(f),
84 }
80 }
85 }
81 }
86 }
82 }
87
83
88 #[derive(Debug, derive_more::From)]
84 #[derive(Debug, derive_more::From)]
89 pub enum DirstateError {
85 pub enum DirstateError {
90 Map(DirstateMapError),
86 Map(DirstateMapError),
91 Common(errors::HgError),
87 Common(errors::HgError),
92 }
88 }
93
89
94 impl fmt::Display for DirstateError {
90 impl fmt::Display for DirstateError {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96 match self {
92 match self {
97 DirstateError::Map(error) => error.fmt(f),
93 DirstateError::Map(error) => error.fmt(f),
98 DirstateError::Common(error) => error.fmt(f),
94 DirstateError::Common(error) => error.fmt(f),
99 }
95 }
100 }
96 }
101 }
97 }
102
98
103 #[derive(Debug, derive_more::From)]
99 #[derive(Debug, derive_more::From)]
104 pub enum PatternError {
100 pub enum PatternError {
105 #[from]
101 #[from]
106 Path(HgPathError),
102 Path(HgPathError),
107 UnsupportedSyntax(String),
103 UnsupportedSyntax(String),
108 UnsupportedSyntaxInFile(String, String, usize),
104 UnsupportedSyntaxInFile(String, String, usize),
109 TooLong(usize),
105 TooLong(usize),
110 #[from]
106 #[from]
111 IO(std::io::Error),
107 IO(std::io::Error),
112 /// Needed a pattern that can be turned into a regex but got one that
108 /// Needed a pattern that can be turned into a regex but got one that
113 /// can't. This should only happen through programmer error.
109 /// can't. This should only happen through programmer error.
114 NonRegexPattern(IgnorePattern),
110 NonRegexPattern(IgnorePattern),
115 }
111 }
116
112
117 impl fmt::Display for PatternError {
113 impl fmt::Display for PatternError {
118 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 match self {
115 match self {
120 PatternError::UnsupportedSyntax(syntax) => {
116 PatternError::UnsupportedSyntax(syntax) => {
121 write!(f, "Unsupported syntax {}", syntax)
117 write!(f, "Unsupported syntax {}", syntax)
122 }
118 }
123 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
119 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
124 write!(
120 write!(
125 f,
121 f,
126 "{}:{}: unsupported syntax {}",
122 "{}:{}: unsupported syntax {}",
127 file_path, line, syntax
123 file_path, line, syntax
128 )
124 )
129 }
125 }
130 PatternError::TooLong(size) => {
126 PatternError::TooLong(size) => {
131 write!(f, "matcher pattern is too long ({} bytes)", size)
127 write!(f, "matcher pattern is too long ({} bytes)", size)
132 }
128 }
133 PatternError::IO(error) => error.fmt(f),
129 PatternError::IO(error) => error.fmt(f),
134 PatternError::Path(error) => error.fmt(f),
130 PatternError::Path(error) => error.fmt(f),
135 PatternError::NonRegexPattern(pattern) => {
131 PatternError::NonRegexPattern(pattern) => {
136 write!(f, "'{:?}' cannot be turned into a regex", pattern)
132 write!(f, "'{:?}' cannot be turned into a regex", pattern)
137 }
133 }
138 }
134 }
139 }
135 }
140 }
136 }
@@ -1,1721 +1,1719 b''
1 // matchers.rs
1 // matchers.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 //! Structs and types for matching files and directories.
8 //! Structs and types for matching files and directories.
9
9
10 use crate::{
10 use crate::{
11 dirstate::dirs_multiset::DirsChildrenMultiset,
11 dirstate::dirs_multiset::DirsChildrenMultiset,
12 filepatterns::{
12 filepatterns::{
13 build_single_regex, filter_subincludes, get_patterns_from_file,
13 build_single_regex, filter_subincludes, get_patterns_from_file,
14 PatternFileWarning, PatternResult,
14 PatternFileWarning, PatternResult,
15 },
15 },
16 utils::{
16 utils::{
17 files::find_dirs,
17 files::find_dirs,
18 hg_path::{HgPath, HgPathBuf},
18 hg_path::{HgPath, HgPathBuf},
19 Escaped,
19 Escaped,
20 },
20 },
21 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
21 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
22 PatternSyntax,
22 PatternSyntax,
23 };
23 };
24
24
25 use crate::dirstate::status::IgnoreFnType;
25 use crate::dirstate::status::IgnoreFnType;
26 use crate::filepatterns::normalize_path_bytes;
26 use crate::filepatterns::normalize_path_bytes;
27 use std::borrow::ToOwned;
27 use std::borrow::ToOwned;
28 use std::collections::HashSet;
28 use std::collections::HashSet;
29 use std::fmt::{Display, Error, Formatter};
29 use std::fmt::{Display, Error, Formatter};
30 use std::ops::Deref;
30 use std::ops::Deref;
31 use std::path::{Path, PathBuf};
31 use std::path::{Path, PathBuf};
32
32
33 use micro_timer::timed;
34
35 #[derive(Debug, PartialEq)]
33 #[derive(Debug, PartialEq)]
36 pub enum VisitChildrenSet {
34 pub enum VisitChildrenSet {
37 /// Don't visit anything
35 /// Don't visit anything
38 Empty,
36 Empty,
39 /// Only visit this directory
37 /// Only visit this directory
40 This,
38 This,
41 /// Visit this directory and these subdirectories
39 /// Visit this directory and these subdirectories
42 /// TODO Should we implement a `NonEmptyHashSet`?
40 /// TODO Should we implement a `NonEmptyHashSet`?
43 Set(HashSet<HgPathBuf>),
41 Set(HashSet<HgPathBuf>),
44 /// Visit this directory and all subdirectories
42 /// Visit this directory and all subdirectories
45 Recursive,
43 Recursive,
46 }
44 }
47
45
48 pub trait Matcher: core::fmt::Debug {
46 pub trait Matcher: core::fmt::Debug {
49 /// Explicitly listed files
47 /// Explicitly listed files
50 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
48 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
51 /// Returns whether `filename` is in `file_set`
49 /// Returns whether `filename` is in `file_set`
52 fn exact_match(&self, filename: &HgPath) -> bool;
50 fn exact_match(&self, filename: &HgPath) -> bool;
53 /// Returns whether `filename` is matched by this matcher
51 /// Returns whether `filename` is matched by this matcher
54 fn matches(&self, filename: &HgPath) -> bool;
52 fn matches(&self, filename: &HgPath) -> bool;
55 /// Decides whether a directory should be visited based on whether it
53 /// Decides whether a directory should be visited based on whether it
56 /// has potential matches in it or one of its subdirectories, and
54 /// has potential matches in it or one of its subdirectories, and
57 /// potentially lists which subdirectories of that directory should be
55 /// potentially lists which subdirectories of that directory should be
58 /// visited. This is based on the match's primary, included, and excluded
56 /// visited. This is based on the match's primary, included, and excluded
59 /// patterns.
57 /// patterns.
60 ///
58 ///
61 /// # Example
59 /// # Example
62 ///
60 ///
63 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
61 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
64 /// return the following values (assuming the implementation of
62 /// return the following values (assuming the implementation of
65 /// visit_children_set is capable of recognizing this; some implementations
63 /// visit_children_set is capable of recognizing this; some implementations
66 /// are not).
64 /// are not).
67 ///
65 ///
68 /// ```text
66 /// ```text
69 /// ```ignore
67 /// ```ignore
70 /// '' -> {'foo', 'qux'}
68 /// '' -> {'foo', 'qux'}
71 /// 'baz' -> set()
69 /// 'baz' -> set()
72 /// 'foo' -> {'bar'}
70 /// 'foo' -> {'bar'}
73 /// // Ideally this would be `Recursive`, but since the prefix nature of
71 /// // Ideally this would be `Recursive`, but since the prefix nature of
74 /// // matchers is applied to the entire matcher, we have to downgrade this
72 /// // matchers is applied to the entire matcher, we have to downgrade this
75 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
73 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
76 /// // `RootFilesIn'-kind matcher being mixed in.
74 /// // `RootFilesIn'-kind matcher being mixed in.
77 /// 'foo/bar' -> 'this'
75 /// 'foo/bar' -> 'this'
78 /// 'qux' -> 'this'
76 /// 'qux' -> 'this'
79 /// ```
77 /// ```
80 /// # Important
78 /// # Important
81 ///
79 ///
82 /// Most matchers do not know if they're representing files or
80 /// Most matchers do not know if they're representing files or
83 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
81 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
84 /// file or a directory, so `visit_children_set('dir')` for most matchers
82 /// file or a directory, so `visit_children_set('dir')` for most matchers
85 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
83 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
86 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
84 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
87 /// it may return `VisitChildrenSet::This`.
85 /// it may return `VisitChildrenSet::This`.
88 /// Do not rely on the return being a `HashSet` indicating that there are
86 /// Do not rely on the return being a `HashSet` indicating that there are
89 /// no files in this dir to investigate (or equivalently that if there are
87 /// no files in this dir to investigate (or equivalently that if there are
90 /// files to investigate in 'dir' that it will always return
88 /// files to investigate in 'dir' that it will always return
91 /// `VisitChildrenSet::This`).
89 /// `VisitChildrenSet::This`).
92 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
90 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
93 /// Matcher will match everything and `files_set()` will be empty:
91 /// Matcher will match everything and `files_set()` will be empty:
94 /// optimization might be possible.
92 /// optimization might be possible.
95 fn matches_everything(&self) -> bool;
93 fn matches_everything(&self) -> bool;
96 /// Matcher will match exactly the files in `files_set()`: optimization
94 /// Matcher will match exactly the files in `files_set()`: optimization
97 /// might be possible.
95 /// might be possible.
98 fn is_exact(&self) -> bool;
96 fn is_exact(&self) -> bool;
99 }
97 }
100
98
101 /// Matches everything.
99 /// Matches everything.
102 ///```
100 ///```
103 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
101 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
104 ///
102 ///
105 /// let matcher = AlwaysMatcher;
103 /// let matcher = AlwaysMatcher;
106 ///
104 ///
107 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
105 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
108 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
106 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
109 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
107 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
110 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
108 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
111 /// ```
109 /// ```
112 #[derive(Debug)]
110 #[derive(Debug)]
113 pub struct AlwaysMatcher;
111 pub struct AlwaysMatcher;
114
112
115 impl Matcher for AlwaysMatcher {
113 impl Matcher for AlwaysMatcher {
116 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
114 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
117 None
115 None
118 }
116 }
119 fn exact_match(&self, _filename: &HgPath) -> bool {
117 fn exact_match(&self, _filename: &HgPath) -> bool {
120 false
118 false
121 }
119 }
122 fn matches(&self, _filename: &HgPath) -> bool {
120 fn matches(&self, _filename: &HgPath) -> bool {
123 true
121 true
124 }
122 }
125 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
123 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
126 VisitChildrenSet::Recursive
124 VisitChildrenSet::Recursive
127 }
125 }
128 fn matches_everything(&self) -> bool {
126 fn matches_everything(&self) -> bool {
129 true
127 true
130 }
128 }
131 fn is_exact(&self) -> bool {
129 fn is_exact(&self) -> bool {
132 false
130 false
133 }
131 }
134 }
132 }
135
133
136 /// Matches nothing.
134 /// Matches nothing.
137 #[derive(Debug)]
135 #[derive(Debug)]
138 pub struct NeverMatcher;
136 pub struct NeverMatcher;
139
137
140 impl Matcher for NeverMatcher {
138 impl Matcher for NeverMatcher {
141 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
139 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
142 None
140 None
143 }
141 }
144 fn exact_match(&self, _filename: &HgPath) -> bool {
142 fn exact_match(&self, _filename: &HgPath) -> bool {
145 false
143 false
146 }
144 }
147 fn matches(&self, _filename: &HgPath) -> bool {
145 fn matches(&self, _filename: &HgPath) -> bool {
148 false
146 false
149 }
147 }
150 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
148 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
151 VisitChildrenSet::Empty
149 VisitChildrenSet::Empty
152 }
150 }
153 fn matches_everything(&self) -> bool {
151 fn matches_everything(&self) -> bool {
154 false
152 false
155 }
153 }
156 fn is_exact(&self) -> bool {
154 fn is_exact(&self) -> bool {
157 true
155 true
158 }
156 }
159 }
157 }
160
158
161 /// Matches the input files exactly. They are interpreted as paths, not
159 /// Matches the input files exactly. They are interpreted as paths, not
162 /// patterns.
160 /// patterns.
163 ///
161 ///
164 ///```
162 ///```
165 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
163 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
166 ///
164 ///
167 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
165 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
168 /// let matcher = FileMatcher::new(files).unwrap();
166 /// let matcher = FileMatcher::new(files).unwrap();
169 ///
167 ///
170 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
168 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
171 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
169 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
172 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
170 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
173 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
171 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
174 /// ```
172 /// ```
175 #[derive(Debug)]
173 #[derive(Debug)]
176 pub struct FileMatcher {
174 pub struct FileMatcher {
177 files: HashSet<HgPathBuf>,
175 files: HashSet<HgPathBuf>,
178 dirs: DirsMultiset,
176 dirs: DirsMultiset,
179 }
177 }
180
178
181 impl FileMatcher {
179 impl FileMatcher {
182 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, DirstateMapError> {
180 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, DirstateMapError> {
183 let dirs = DirsMultiset::from_manifest(&files)?;
181 let dirs = DirsMultiset::from_manifest(&files)?;
184 Ok(Self {
182 Ok(Self {
185 files: HashSet::from_iter(files.into_iter()),
183 files: HashSet::from_iter(files.into_iter()),
186 dirs,
184 dirs,
187 })
185 })
188 }
186 }
189 fn inner_matches(&self, filename: &HgPath) -> bool {
187 fn inner_matches(&self, filename: &HgPath) -> bool {
190 self.files.contains(filename.as_ref())
188 self.files.contains(filename.as_ref())
191 }
189 }
192 }
190 }
193
191
194 impl Matcher for FileMatcher {
192 impl Matcher for FileMatcher {
195 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
193 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
196 Some(&self.files)
194 Some(&self.files)
197 }
195 }
198 fn exact_match(&self, filename: &HgPath) -> bool {
196 fn exact_match(&self, filename: &HgPath) -> bool {
199 self.inner_matches(filename)
197 self.inner_matches(filename)
200 }
198 }
201 fn matches(&self, filename: &HgPath) -> bool {
199 fn matches(&self, filename: &HgPath) -> bool {
202 self.inner_matches(filename)
200 self.inner_matches(filename)
203 }
201 }
204 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
202 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
205 if self.files.is_empty() || !self.dirs.contains(&directory) {
203 if self.files.is_empty() || !self.dirs.contains(&directory) {
206 return VisitChildrenSet::Empty;
204 return VisitChildrenSet::Empty;
207 }
205 }
208 let mut candidates: HashSet<HgPathBuf> =
206 let mut candidates: HashSet<HgPathBuf> =
209 self.dirs.iter().cloned().collect();
207 self.dirs.iter().cloned().collect();
210
208
211 candidates.extend(self.files.iter().cloned());
209 candidates.extend(self.files.iter().cloned());
212 candidates.remove(HgPath::new(b""));
210 candidates.remove(HgPath::new(b""));
213
211
214 if !directory.as_ref().is_empty() {
212 if !directory.as_ref().is_empty() {
215 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
213 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
216 candidates = candidates
214 candidates = candidates
217 .iter()
215 .iter()
218 .filter_map(|c| {
216 .filter_map(|c| {
219 if c.as_bytes().starts_with(&directory) {
217 if c.as_bytes().starts_with(&directory) {
220 Some(HgPathBuf::from_bytes(
218 Some(HgPathBuf::from_bytes(
221 &c.as_bytes()[directory.len()..],
219 &c.as_bytes()[directory.len()..],
222 ))
220 ))
223 } else {
221 } else {
224 None
222 None
225 }
223 }
226 })
224 })
227 .collect();
225 .collect();
228 }
226 }
229
227
230 // `self.dirs` includes all of the directories, recursively, so if
228 // `self.dirs` includes all of the directories, recursively, so if
231 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
229 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
232 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
230 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
233 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
231 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
234 // subdir will be in there without a slash.
232 // subdir will be in there without a slash.
235 VisitChildrenSet::Set(
233 VisitChildrenSet::Set(
236 candidates
234 candidates
237 .into_iter()
235 .into_iter()
238 .filter_map(|c| {
236 .filter_map(|c| {
239 if c.bytes().all(|b| *b != b'/') {
237 if c.bytes().all(|b| *b != b'/') {
240 Some(c)
238 Some(c)
241 } else {
239 } else {
242 None
240 None
243 }
241 }
244 })
242 })
245 .collect(),
243 .collect(),
246 )
244 )
247 }
245 }
248 fn matches_everything(&self) -> bool {
246 fn matches_everything(&self) -> bool {
249 false
247 false
250 }
248 }
251 fn is_exact(&self) -> bool {
249 fn is_exact(&self) -> bool {
252 true
250 true
253 }
251 }
254 }
252 }
255
253
256 /// Matches files that are included in the ignore rules.
254 /// Matches files that are included in the ignore rules.
257 /// ```
255 /// ```
258 /// use hg::{
256 /// use hg::{
259 /// matchers::{IncludeMatcher, Matcher},
257 /// matchers::{IncludeMatcher, Matcher},
260 /// IgnorePattern,
258 /// IgnorePattern,
261 /// PatternSyntax,
259 /// PatternSyntax,
262 /// utils::hg_path::HgPath
260 /// utils::hg_path::HgPath
263 /// };
261 /// };
264 /// use std::path::Path;
262 /// use std::path::Path;
265 /// ///
263 /// ///
266 /// let ignore_patterns =
264 /// let ignore_patterns =
267 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
265 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
268 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
266 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
269 /// ///
267 /// ///
270 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
268 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
271 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
269 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
272 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
270 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
273 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
271 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
274 /// ```
272 /// ```
275 pub struct IncludeMatcher<'a> {
273 pub struct IncludeMatcher<'a> {
276 patterns: Vec<u8>,
274 patterns: Vec<u8>,
277 match_fn: IgnoreFnType<'a>,
275 match_fn: IgnoreFnType<'a>,
278 /// Whether all the patterns match a prefix (i.e. recursively)
276 /// Whether all the patterns match a prefix (i.e. recursively)
279 prefix: bool,
277 prefix: bool,
280 roots: HashSet<HgPathBuf>,
278 roots: HashSet<HgPathBuf>,
281 dirs: HashSet<HgPathBuf>,
279 dirs: HashSet<HgPathBuf>,
282 parents: HashSet<HgPathBuf>,
280 parents: HashSet<HgPathBuf>,
283 }
281 }
284
282
285 impl core::fmt::Debug for IncludeMatcher<'_> {
283 impl core::fmt::Debug for IncludeMatcher<'_> {
286 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
284 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287 f.debug_struct("IncludeMatcher")
285 f.debug_struct("IncludeMatcher")
288 .field("patterns", &String::from_utf8_lossy(&self.patterns))
286 .field("patterns", &String::from_utf8_lossy(&self.patterns))
289 .field("prefix", &self.prefix)
287 .field("prefix", &self.prefix)
290 .field("roots", &self.roots)
288 .field("roots", &self.roots)
291 .field("dirs", &self.dirs)
289 .field("dirs", &self.dirs)
292 .field("parents", &self.parents)
290 .field("parents", &self.parents)
293 .finish()
291 .finish()
294 }
292 }
295 }
293 }
296
294
297 impl<'a> Matcher for IncludeMatcher<'a> {
295 impl<'a> Matcher for IncludeMatcher<'a> {
298 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
296 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
299 None
297 None
300 }
298 }
301
299
302 fn exact_match(&self, _filename: &HgPath) -> bool {
300 fn exact_match(&self, _filename: &HgPath) -> bool {
303 false
301 false
304 }
302 }
305
303
306 fn matches(&self, filename: &HgPath) -> bool {
304 fn matches(&self, filename: &HgPath) -> bool {
307 (self.match_fn)(filename.as_ref())
305 (self.match_fn)(filename.as_ref())
308 }
306 }
309
307
310 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
308 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
311 let dir = directory.as_ref();
309 let dir = directory.as_ref();
312 if self.prefix && self.roots.contains(dir) {
310 if self.prefix && self.roots.contains(dir) {
313 return VisitChildrenSet::Recursive;
311 return VisitChildrenSet::Recursive;
314 }
312 }
315 if self.roots.contains(HgPath::new(b""))
313 if self.roots.contains(HgPath::new(b""))
316 || self.roots.contains(dir)
314 || self.roots.contains(dir)
317 || self.dirs.contains(dir)
315 || self.dirs.contains(dir)
318 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
316 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
319 {
317 {
320 return VisitChildrenSet::This;
318 return VisitChildrenSet::This;
321 }
319 }
322
320
323 if self.parents.contains(directory.as_ref()) {
321 if self.parents.contains(directory.as_ref()) {
324 let multiset = self.get_all_parents_children();
322 let multiset = self.get_all_parents_children();
325 if let Some(children) = multiset.get(dir) {
323 if let Some(children) = multiset.get(dir) {
326 return VisitChildrenSet::Set(
324 return VisitChildrenSet::Set(
327 children.into_iter().map(HgPathBuf::from).collect(),
325 children.into_iter().map(HgPathBuf::from).collect(),
328 );
326 );
329 }
327 }
330 }
328 }
331 VisitChildrenSet::Empty
329 VisitChildrenSet::Empty
332 }
330 }
333
331
334 fn matches_everything(&self) -> bool {
332 fn matches_everything(&self) -> bool {
335 false
333 false
336 }
334 }
337
335
338 fn is_exact(&self) -> bool {
336 fn is_exact(&self) -> bool {
339 false
337 false
340 }
338 }
341 }
339 }
342
340
343 /// The union of multiple matchers. Will match if any of the matchers match.
341 /// The union of multiple matchers. Will match if any of the matchers match.
344 #[derive(Debug)]
342 #[derive(Debug)]
345 pub struct UnionMatcher {
343 pub struct UnionMatcher {
346 matchers: Vec<Box<dyn Matcher + Sync>>,
344 matchers: Vec<Box<dyn Matcher + Sync>>,
347 }
345 }
348
346
349 impl Matcher for UnionMatcher {
347 impl Matcher for UnionMatcher {
350 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
348 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
351 None
349 None
352 }
350 }
353
351
354 fn exact_match(&self, _filename: &HgPath) -> bool {
352 fn exact_match(&self, _filename: &HgPath) -> bool {
355 false
353 false
356 }
354 }
357
355
358 fn matches(&self, filename: &HgPath) -> bool {
356 fn matches(&self, filename: &HgPath) -> bool {
359 self.matchers.iter().any(|m| m.matches(filename))
357 self.matchers.iter().any(|m| m.matches(filename))
360 }
358 }
361
359
362 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
360 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
363 let mut result = HashSet::new();
361 let mut result = HashSet::new();
364 let mut this = false;
362 let mut this = false;
365 for matcher in self.matchers.iter() {
363 for matcher in self.matchers.iter() {
366 let visit = matcher.visit_children_set(directory);
364 let visit = matcher.visit_children_set(directory);
367 match visit {
365 match visit {
368 VisitChildrenSet::Empty => continue,
366 VisitChildrenSet::Empty => continue,
369 VisitChildrenSet::This => {
367 VisitChildrenSet::This => {
370 this = true;
368 this = true;
371 // Don't break, we might have an 'all' in here.
369 // Don't break, we might have an 'all' in here.
372 continue;
370 continue;
373 }
371 }
374 VisitChildrenSet::Set(set) => {
372 VisitChildrenSet::Set(set) => {
375 result.extend(set);
373 result.extend(set);
376 }
374 }
377 VisitChildrenSet::Recursive => {
375 VisitChildrenSet::Recursive => {
378 return visit;
376 return visit;
379 }
377 }
380 }
378 }
381 }
379 }
382 if this {
380 if this {
383 return VisitChildrenSet::This;
381 return VisitChildrenSet::This;
384 }
382 }
385 if result.is_empty() {
383 if result.is_empty() {
386 VisitChildrenSet::Empty
384 VisitChildrenSet::Empty
387 } else {
385 } else {
388 VisitChildrenSet::Set(result)
386 VisitChildrenSet::Set(result)
389 }
387 }
390 }
388 }
391
389
392 fn matches_everything(&self) -> bool {
390 fn matches_everything(&self) -> bool {
393 // TODO Maybe if all are AlwaysMatcher?
391 // TODO Maybe if all are AlwaysMatcher?
394 false
392 false
395 }
393 }
396
394
397 fn is_exact(&self) -> bool {
395 fn is_exact(&self) -> bool {
398 false
396 false
399 }
397 }
400 }
398 }
401
399
402 impl UnionMatcher {
400 impl UnionMatcher {
403 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
401 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
404 Self { matchers }
402 Self { matchers }
405 }
403 }
406 }
404 }
407
405
408 #[derive(Debug)]
406 #[derive(Debug)]
409 pub struct IntersectionMatcher {
407 pub struct IntersectionMatcher {
410 m1: Box<dyn Matcher + Sync>,
408 m1: Box<dyn Matcher + Sync>,
411 m2: Box<dyn Matcher + Sync>,
409 m2: Box<dyn Matcher + Sync>,
412 files: Option<HashSet<HgPathBuf>>,
410 files: Option<HashSet<HgPathBuf>>,
413 }
411 }
414
412
415 impl Matcher for IntersectionMatcher {
413 impl Matcher for IntersectionMatcher {
416 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
414 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
417 self.files.as_ref()
415 self.files.as_ref()
418 }
416 }
419
417
420 fn exact_match(&self, filename: &HgPath) -> bool {
418 fn exact_match(&self, filename: &HgPath) -> bool {
421 self.files.as_ref().map_or(false, |f| f.contains(filename))
419 self.files.as_ref().map_or(false, |f| f.contains(filename))
422 }
420 }
423
421
424 fn matches(&self, filename: &HgPath) -> bool {
422 fn matches(&self, filename: &HgPath) -> bool {
425 self.m1.matches(filename) && self.m2.matches(filename)
423 self.m1.matches(filename) && self.m2.matches(filename)
426 }
424 }
427
425
428 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
426 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
429 let m1_set = self.m1.visit_children_set(directory);
427 let m1_set = self.m1.visit_children_set(directory);
430 if m1_set == VisitChildrenSet::Empty {
428 if m1_set == VisitChildrenSet::Empty {
431 return VisitChildrenSet::Empty;
429 return VisitChildrenSet::Empty;
432 }
430 }
433 let m2_set = self.m2.visit_children_set(directory);
431 let m2_set = self.m2.visit_children_set(directory);
434 if m2_set == VisitChildrenSet::Empty {
432 if m2_set == VisitChildrenSet::Empty {
435 return VisitChildrenSet::Empty;
433 return VisitChildrenSet::Empty;
436 }
434 }
437
435
438 if m1_set == VisitChildrenSet::Recursive {
436 if m1_set == VisitChildrenSet::Recursive {
439 return m2_set;
437 return m2_set;
440 } else if m2_set == VisitChildrenSet::Recursive {
438 } else if m2_set == VisitChildrenSet::Recursive {
441 return m1_set;
439 return m1_set;
442 }
440 }
443
441
444 match (&m1_set, &m2_set) {
442 match (&m1_set, &m2_set) {
445 (VisitChildrenSet::Recursive, _) => m2_set,
443 (VisitChildrenSet::Recursive, _) => m2_set,
446 (_, VisitChildrenSet::Recursive) => m1_set,
444 (_, VisitChildrenSet::Recursive) => m1_set,
447 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
445 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
448 VisitChildrenSet::This
446 VisitChildrenSet::This
449 }
447 }
450 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
448 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
451 let set: HashSet<_> = m1.intersection(&m2).cloned().collect();
449 let set: HashSet<_> = m1.intersection(&m2).cloned().collect();
452 if set.is_empty() {
450 if set.is_empty() {
453 VisitChildrenSet::Empty
451 VisitChildrenSet::Empty
454 } else {
452 } else {
455 VisitChildrenSet::Set(set)
453 VisitChildrenSet::Set(set)
456 }
454 }
457 }
455 }
458 _ => unreachable!(),
456 _ => unreachable!(),
459 }
457 }
460 }
458 }
461
459
462 fn matches_everything(&self) -> bool {
460 fn matches_everything(&self) -> bool {
463 self.m1.matches_everything() && self.m2.matches_everything()
461 self.m1.matches_everything() && self.m2.matches_everything()
464 }
462 }
465
463
466 fn is_exact(&self) -> bool {
464 fn is_exact(&self) -> bool {
467 self.m1.is_exact() || self.m2.is_exact()
465 self.m1.is_exact() || self.m2.is_exact()
468 }
466 }
469 }
467 }
470
468
471 impl IntersectionMatcher {
469 impl IntersectionMatcher {
472 pub fn new(
470 pub fn new(
473 mut m1: Box<dyn Matcher + Sync>,
471 mut m1: Box<dyn Matcher + Sync>,
474 mut m2: Box<dyn Matcher + Sync>,
472 mut m2: Box<dyn Matcher + Sync>,
475 ) -> Self {
473 ) -> Self {
476 let files = if m1.is_exact() || m2.is_exact() {
474 let files = if m1.is_exact() || m2.is_exact() {
477 if !m1.is_exact() {
475 if !m1.is_exact() {
478 std::mem::swap(&mut m1, &mut m2);
476 std::mem::swap(&mut m1, &mut m2);
479 }
477 }
480 m1.file_set().map(|m1_files| {
478 m1.file_set().map(|m1_files| {
481 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
479 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
482 })
480 })
483 } else {
481 } else {
484 None
482 None
485 };
483 };
486 Self { m1, m2, files }
484 Self { m1, m2, files }
487 }
485 }
488 }
486 }
489
487
490 #[derive(Debug)]
488 #[derive(Debug)]
491 pub struct DifferenceMatcher {
489 pub struct DifferenceMatcher {
492 base: Box<dyn Matcher + Sync>,
490 base: Box<dyn Matcher + Sync>,
493 excluded: Box<dyn Matcher + Sync>,
491 excluded: Box<dyn Matcher + Sync>,
494 files: Option<HashSet<HgPathBuf>>,
492 files: Option<HashSet<HgPathBuf>>,
495 }
493 }
496
494
497 impl Matcher for DifferenceMatcher {
495 impl Matcher for DifferenceMatcher {
498 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
496 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
499 self.files.as_ref()
497 self.files.as_ref()
500 }
498 }
501
499
502 fn exact_match(&self, filename: &HgPath) -> bool {
500 fn exact_match(&self, filename: &HgPath) -> bool {
503 self.files.as_ref().map_or(false, |f| f.contains(filename))
501 self.files.as_ref().map_or(false, |f| f.contains(filename))
504 }
502 }
505
503
506 fn matches(&self, filename: &HgPath) -> bool {
504 fn matches(&self, filename: &HgPath) -> bool {
507 self.base.matches(filename) && !self.excluded.matches(filename)
505 self.base.matches(filename) && !self.excluded.matches(filename)
508 }
506 }
509
507
510 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
508 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
511 let excluded_set = self.excluded.visit_children_set(directory);
509 let excluded_set = self.excluded.visit_children_set(directory);
512 if excluded_set == VisitChildrenSet::Recursive {
510 if excluded_set == VisitChildrenSet::Recursive {
513 return VisitChildrenSet::Empty;
511 return VisitChildrenSet::Empty;
514 }
512 }
515 let base_set = self.base.visit_children_set(directory);
513 let base_set = self.base.visit_children_set(directory);
516 // Possible values for base: 'recursive', 'this', set(...), set()
514 // Possible values for base: 'recursive', 'this', set(...), set()
517 // Possible values for excluded: 'this', set(...), set()
515 // Possible values for excluded: 'this', set(...), set()
518 // If excluded has nothing under here that we care about, return base,
516 // If excluded has nothing under here that we care about, return base,
519 // even if it's 'recursive'.
517 // even if it's 'recursive'.
520 if excluded_set == VisitChildrenSet::Empty {
518 if excluded_set == VisitChildrenSet::Empty {
521 return base_set;
519 return base_set;
522 }
520 }
523 match base_set {
521 match base_set {
524 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
522 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
525 // Never return 'recursive' here if excluded_set is any kind of
523 // Never return 'recursive' here if excluded_set is any kind of
526 // non-empty (either 'this' or set(foo)), since excluded might
524 // non-empty (either 'this' or set(foo)), since excluded might
527 // return set() for a subdirectory.
525 // return set() for a subdirectory.
528 VisitChildrenSet::This
526 VisitChildrenSet::This
529 }
527 }
530 set => {
528 set => {
531 // Possible values for base: set(...), set()
529 // Possible values for base: set(...), set()
532 // Possible values for excluded: 'this', set(...)
530 // Possible values for excluded: 'this', set(...)
533 // We ignore excluded set results. They're possibly incorrect:
531 // We ignore excluded set results. They're possibly incorrect:
534 // base = path:dir/subdir
532 // base = path:dir/subdir
535 // excluded=rootfilesin:dir,
533 // excluded=rootfilesin:dir,
536 // visit_children_set(''):
534 // visit_children_set(''):
537 // base returns {'dir'}, excluded returns {'dir'}, if we
535 // base returns {'dir'}, excluded returns {'dir'}, if we
538 // subtracted we'd return set(), which is *not* correct, we
536 // subtracted we'd return set(), which is *not* correct, we
539 // still need to visit 'dir'!
537 // still need to visit 'dir'!
540 set
538 set
541 }
539 }
542 }
540 }
543 }
541 }
544
542
545 fn matches_everything(&self) -> bool {
543 fn matches_everything(&self) -> bool {
546 false
544 false
547 }
545 }
548
546
549 fn is_exact(&self) -> bool {
547 fn is_exact(&self) -> bool {
550 self.base.is_exact()
548 self.base.is_exact()
551 }
549 }
552 }
550 }
553
551
554 impl DifferenceMatcher {
552 impl DifferenceMatcher {
555 pub fn new(
553 pub fn new(
556 base: Box<dyn Matcher + Sync>,
554 base: Box<dyn Matcher + Sync>,
557 excluded: Box<dyn Matcher + Sync>,
555 excluded: Box<dyn Matcher + Sync>,
558 ) -> Self {
556 ) -> Self {
559 let base_is_exact = base.is_exact();
557 let base_is_exact = base.is_exact();
560 let base_files = base.file_set().map(ToOwned::to_owned);
558 let base_files = base.file_set().map(ToOwned::to_owned);
561 let mut new = Self {
559 let mut new = Self {
562 base,
560 base,
563 excluded,
561 excluded,
564 files: None,
562 files: None,
565 };
563 };
566 if base_is_exact {
564 if base_is_exact {
567 new.files = base_files.map(|files| {
565 new.files = base_files.map(|files| {
568 files.iter().cloned().filter(|f| new.matches(f)).collect()
566 files.iter().cloned().filter(|f| new.matches(f)).collect()
569 });
567 });
570 }
568 }
571 new
569 new
572 }
570 }
573 }
571 }
574
572
575 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
573 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
576 /// contexts.
574 /// contexts.
577 ///
575 ///
578 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
576 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
579 /// from many threads at once is prone to contention, probably within the
577 /// from many threads at once is prone to contention, probably within the
580 /// scratch space needed as the regex DFA is built lazily.
578 /// scratch space needed as the regex DFA is built lazily.
581 ///
579 ///
582 /// We are in the process of raising the issue upstream, but for now
580 /// We are in the process of raising the issue upstream, but for now
583 /// the workaround used here is to store the `Regex` in a lazily populated
581 /// the workaround used here is to store the `Regex` in a lazily populated
584 /// thread-local variable, sharing the initial read-only compilation, but
582 /// thread-local variable, sharing the initial read-only compilation, but
585 /// not the lazy dfa scratch space mentioned above.
583 /// not the lazy dfa scratch space mentioned above.
586 ///
584 ///
587 /// This reduces the contention observed with 16+ threads, but does not
585 /// This reduces the contention observed with 16+ threads, but does not
588 /// completely remove it. Hopefully this can be addressed upstream.
586 /// completely remove it. Hopefully this can be addressed upstream.
589 struct RegexMatcher {
587 struct RegexMatcher {
590 /// Compiled at the start of the status algorithm, used as a base for
588 /// Compiled at the start of the status algorithm, used as a base for
591 /// cloning in each thread-local `self.local`, thus sharing the expensive
589 /// cloning in each thread-local `self.local`, thus sharing the expensive
592 /// first compilation.
590 /// first compilation.
593 base: regex::bytes::Regex,
591 base: regex::bytes::Regex,
594 /// Thread-local variable that holds the `Regex` that is actually queried
592 /// Thread-local variable that holds the `Regex` that is actually queried
595 /// from each thread.
593 /// from each thread.
596 local: thread_local::ThreadLocal<regex::bytes::Regex>,
594 local: thread_local::ThreadLocal<regex::bytes::Regex>,
597 }
595 }
598
596
599 impl RegexMatcher {
597 impl RegexMatcher {
600 /// Returns whether the path matches the stored `Regex`.
598 /// Returns whether the path matches the stored `Regex`.
601 pub fn is_match(&self, path: &HgPath) -> bool {
599 pub fn is_match(&self, path: &HgPath) -> bool {
602 self.local
600 self.local
603 .get_or(|| self.base.clone())
601 .get_or(|| self.base.clone())
604 .is_match(path.as_bytes())
602 .is_match(path.as_bytes())
605 }
603 }
606 }
604 }
607
605
608 /// Returns a function that matches an `HgPath` against the given regex
606 /// Returns a function that matches an `HgPath` against the given regex
609 /// pattern.
607 /// pattern.
610 ///
608 ///
611 /// This can fail when the pattern is invalid or not supported by the
609 /// This can fail when the pattern is invalid or not supported by the
612 /// underlying engine (the `regex` crate), for instance anything with
610 /// underlying engine (the `regex` crate), for instance anything with
613 /// back-references.
611 /// back-references.
614 #[timed]
612 #[logging_timer::time("trace")]
615 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
613 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
616 use std::io::Write;
614 use std::io::Write;
617
615
618 // The `regex` crate adds `.*` to the start and end of expressions if there
616 // The `regex` crate adds `.*` to the start and end of expressions if there
619 // are no anchors, so add the start anchor.
617 // are no anchors, so add the start anchor.
620 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
618 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
621 for byte in pattern {
619 for byte in pattern {
622 if *byte > 127 {
620 if *byte > 127 {
623 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
621 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
624 } else {
622 } else {
625 escaped_bytes.push(*byte);
623 escaped_bytes.push(*byte);
626 }
624 }
627 }
625 }
628 escaped_bytes.push(b')');
626 escaped_bytes.push(b')');
629
627
630 // Avoid the cost of UTF8 checking
628 // Avoid the cost of UTF8 checking
631 //
629 //
632 // # Safety
630 // # Safety
633 // This is safe because we escaped all non-ASCII bytes.
631 // This is safe because we escaped all non-ASCII bytes.
634 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
632 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
635 let re = regex::bytes::RegexBuilder::new(&pattern_string)
633 let re = regex::bytes::RegexBuilder::new(&pattern_string)
636 .unicode(false)
634 .unicode(false)
637 // Big repos with big `.hgignore` will hit the default limit and
635 // Big repos with big `.hgignore` will hit the default limit and
638 // incur a significant performance hit. One repo's `hg status` hit
636 // incur a significant performance hit. One repo's `hg status` hit
639 // multiple *minutes*.
637 // multiple *minutes*.
640 .dfa_size_limit(50 * (1 << 20))
638 .dfa_size_limit(50 * (1 << 20))
641 .build()
639 .build()
642 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
640 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
643
641
644 Ok(RegexMatcher {
642 Ok(RegexMatcher {
645 base: re,
643 base: re,
646 local: Default::default(),
644 local: Default::default(),
647 })
645 })
648 }
646 }
649
647
650 /// Returns the regex pattern and a function that matches an `HgPath` against
648 /// Returns the regex pattern and a function that matches an `HgPath` against
651 /// said regex formed by the given ignore patterns.
649 /// said regex formed by the given ignore patterns.
652 fn build_regex_match<'a, 'b>(
650 fn build_regex_match<'a, 'b>(
653 ignore_patterns: &'a [IgnorePattern],
651 ignore_patterns: &'a [IgnorePattern],
654 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
652 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
655 let mut regexps = vec![];
653 let mut regexps = vec![];
656 let mut exact_set = HashSet::new();
654 let mut exact_set = HashSet::new();
657
655
658 for pattern in ignore_patterns {
656 for pattern in ignore_patterns {
659 if let Some(re) = build_single_regex(pattern)? {
657 if let Some(re) = build_single_regex(pattern)? {
660 regexps.push(re);
658 regexps.push(re);
661 } else {
659 } else {
662 let exact = normalize_path_bytes(&pattern.pattern);
660 let exact = normalize_path_bytes(&pattern.pattern);
663 exact_set.insert(HgPathBuf::from_bytes(&exact));
661 exact_set.insert(HgPathBuf::from_bytes(&exact));
664 }
662 }
665 }
663 }
666
664
667 let full_regex = regexps.join(&b'|');
665 let full_regex = regexps.join(&b'|');
668
666
669 // An empty pattern would cause the regex engine to incorrectly match the
667 // An empty pattern would cause the regex engine to incorrectly match the
670 // (empty) root directory
668 // (empty) root directory
671 let func = if !(regexps.is_empty()) {
669 let func = if !(regexps.is_empty()) {
672 let matcher = re_matcher(&full_regex)?;
670 let matcher = re_matcher(&full_regex)?;
673 let func = move |filename: &HgPath| {
671 let func = move |filename: &HgPath| {
674 exact_set.contains(filename) || matcher.is_match(filename)
672 exact_set.contains(filename) || matcher.is_match(filename)
675 };
673 };
676 Box::new(func) as IgnoreFnType
674 Box::new(func) as IgnoreFnType
677 } else {
675 } else {
678 let func = move |filename: &HgPath| exact_set.contains(filename);
676 let func = move |filename: &HgPath| exact_set.contains(filename);
679 Box::new(func) as IgnoreFnType
677 Box::new(func) as IgnoreFnType
680 };
678 };
681
679
682 Ok((full_regex, func))
680 Ok((full_regex, func))
683 }
681 }
684
682
685 /// Returns roots and directories corresponding to each pattern.
683 /// Returns roots and directories corresponding to each pattern.
686 ///
684 ///
687 /// This calculates the roots and directories exactly matching the patterns and
685 /// This calculates the roots and directories exactly matching the patterns and
688 /// returns a tuple of (roots, dirs). It does not return other directories
686 /// returns a tuple of (roots, dirs). It does not return other directories
689 /// which may also need to be considered, like the parent directories.
687 /// which may also need to be considered, like the parent directories.
690 fn roots_and_dirs(
688 fn roots_and_dirs(
691 ignore_patterns: &[IgnorePattern],
689 ignore_patterns: &[IgnorePattern],
692 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
690 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
693 let mut roots = Vec::new();
691 let mut roots = Vec::new();
694 let mut dirs = Vec::new();
692 let mut dirs = Vec::new();
695
693
696 for ignore_pattern in ignore_patterns {
694 for ignore_pattern in ignore_patterns {
697 let IgnorePattern {
695 let IgnorePattern {
698 syntax, pattern, ..
696 syntax, pattern, ..
699 } = ignore_pattern;
697 } = ignore_pattern;
700 match syntax {
698 match syntax {
701 PatternSyntax::RootGlob | PatternSyntax::Glob => {
699 PatternSyntax::RootGlob | PatternSyntax::Glob => {
702 let mut root = HgPathBuf::new();
700 let mut root = HgPathBuf::new();
703 for p in pattern.split(|c| *c == b'/') {
701 for p in pattern.split(|c| *c == b'/') {
704 if p.iter().any(|c| match *c {
702 if p.iter().any(|c| match *c {
705 b'[' | b'{' | b'*' | b'?' => true,
703 b'[' | b'{' | b'*' | b'?' => true,
706 _ => false,
704 _ => false,
707 }) {
705 }) {
708 break;
706 break;
709 }
707 }
710 root.push(HgPathBuf::from_bytes(p).as_ref());
708 root.push(HgPathBuf::from_bytes(p).as_ref());
711 }
709 }
712 roots.push(root);
710 roots.push(root);
713 }
711 }
714 PatternSyntax::Path | PatternSyntax::RelPath => {
712 PatternSyntax::Path | PatternSyntax::RelPath => {
715 let pat = HgPath::new(if pattern == b"." {
713 let pat = HgPath::new(if pattern == b"." {
716 &[] as &[u8]
714 &[] as &[u8]
717 } else {
715 } else {
718 pattern
716 pattern
719 });
717 });
720 roots.push(pat.to_owned());
718 roots.push(pat.to_owned());
721 }
719 }
722 PatternSyntax::RootFiles => {
720 PatternSyntax::RootFiles => {
723 let pat = if pattern == b"." {
721 let pat = if pattern == b"." {
724 &[] as &[u8]
722 &[] as &[u8]
725 } else {
723 } else {
726 pattern
724 pattern
727 };
725 };
728 dirs.push(HgPathBuf::from_bytes(pat));
726 dirs.push(HgPathBuf::from_bytes(pat));
729 }
727 }
730 _ => {
728 _ => {
731 roots.push(HgPathBuf::new());
729 roots.push(HgPathBuf::new());
732 }
730 }
733 }
731 }
734 }
732 }
735 (roots, dirs)
733 (roots, dirs)
736 }
734 }
737
735
738 /// Paths extracted from patterns
736 /// Paths extracted from patterns
739 #[derive(Debug, PartialEq)]
737 #[derive(Debug, PartialEq)]
740 struct RootsDirsAndParents {
738 struct RootsDirsAndParents {
741 /// Directories to match recursively
739 /// Directories to match recursively
742 pub roots: HashSet<HgPathBuf>,
740 pub roots: HashSet<HgPathBuf>,
743 /// Directories to match non-recursively
741 /// Directories to match non-recursively
744 pub dirs: HashSet<HgPathBuf>,
742 pub dirs: HashSet<HgPathBuf>,
745 /// Implicitly required directories to go to items in either roots or dirs
743 /// Implicitly required directories to go to items in either roots or dirs
746 pub parents: HashSet<HgPathBuf>,
744 pub parents: HashSet<HgPathBuf>,
747 }
745 }
748
746
749 /// Extract roots, dirs and parents from patterns.
747 /// Extract roots, dirs and parents from patterns.
750 fn roots_dirs_and_parents(
748 fn roots_dirs_and_parents(
751 ignore_patterns: &[IgnorePattern],
749 ignore_patterns: &[IgnorePattern],
752 ) -> PatternResult<RootsDirsAndParents> {
750 ) -> PatternResult<RootsDirsAndParents> {
753 let (roots, dirs) = roots_and_dirs(ignore_patterns);
751 let (roots, dirs) = roots_and_dirs(ignore_patterns);
754
752
755 let mut parents = HashSet::new();
753 let mut parents = HashSet::new();
756
754
757 parents.extend(
755 parents.extend(
758 DirsMultiset::from_manifest(&dirs)
756 DirsMultiset::from_manifest(&dirs)
759 .map_err(|e| match e {
757 .map_err(|e| match e {
760 DirstateMapError::InvalidPath(e) => e,
758 DirstateMapError::InvalidPath(e) => e,
761 _ => unreachable!(),
759 _ => unreachable!(),
762 })?
760 })?
763 .iter()
761 .iter()
764 .map(ToOwned::to_owned),
762 .map(ToOwned::to_owned),
765 );
763 );
766 parents.extend(
764 parents.extend(
767 DirsMultiset::from_manifest(&roots)
765 DirsMultiset::from_manifest(&roots)
768 .map_err(|e| match e {
766 .map_err(|e| match e {
769 DirstateMapError::InvalidPath(e) => e,
767 DirstateMapError::InvalidPath(e) => e,
770 _ => unreachable!(),
768 _ => unreachable!(),
771 })?
769 })?
772 .iter()
770 .iter()
773 .map(ToOwned::to_owned),
771 .map(ToOwned::to_owned),
774 );
772 );
775
773
776 Ok(RootsDirsAndParents {
774 Ok(RootsDirsAndParents {
777 roots: HashSet::from_iter(roots),
775 roots: HashSet::from_iter(roots),
778 dirs: HashSet::from_iter(dirs),
776 dirs: HashSet::from_iter(dirs),
779 parents,
777 parents,
780 })
778 })
781 }
779 }
782
780
783 /// Returns a function that checks whether a given file (in the general sense)
781 /// Returns a function that checks whether a given file (in the general sense)
784 /// should be matched.
782 /// should be matched.
785 fn build_match<'a, 'b>(
783 fn build_match<'a, 'b>(
786 ignore_patterns: Vec<IgnorePattern>,
784 ignore_patterns: Vec<IgnorePattern>,
787 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
785 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'b>)> {
788 let mut match_funcs: Vec<IgnoreFnType<'b>> = vec![];
786 let mut match_funcs: Vec<IgnoreFnType<'b>> = vec![];
789 // For debugging and printing
787 // For debugging and printing
790 let mut patterns = vec![];
788 let mut patterns = vec![];
791
789
792 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
790 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
793
791
794 if !subincludes.is_empty() {
792 if !subincludes.is_empty() {
795 // Build prefix-based matcher functions for subincludes
793 // Build prefix-based matcher functions for subincludes
796 let mut submatchers = FastHashMap::default();
794 let mut submatchers = FastHashMap::default();
797 let mut prefixes = vec![];
795 let mut prefixes = vec![];
798
796
799 for sub_include in subincludes {
797 for sub_include in subincludes {
800 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
798 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
801 let match_fn =
799 let match_fn =
802 Box::new(move |path: &HgPath| matcher.matches(path));
800 Box::new(move |path: &HgPath| matcher.matches(path));
803 prefixes.push(sub_include.prefix.clone());
801 prefixes.push(sub_include.prefix.clone());
804 submatchers.insert(sub_include.prefix.clone(), match_fn);
802 submatchers.insert(sub_include.prefix.clone(), match_fn);
805 }
803 }
806
804
807 let match_subinclude = move |filename: &HgPath| {
805 let match_subinclude = move |filename: &HgPath| {
808 for prefix in prefixes.iter() {
806 for prefix in prefixes.iter() {
809 if let Some(rel) = filename.relative_to(prefix) {
807 if let Some(rel) = filename.relative_to(prefix) {
810 if (submatchers[prefix])(rel) {
808 if (submatchers[prefix])(rel) {
811 return true;
809 return true;
812 }
810 }
813 }
811 }
814 }
812 }
815 false
813 false
816 };
814 };
817
815
818 match_funcs.push(Box::new(match_subinclude));
816 match_funcs.push(Box::new(match_subinclude));
819 }
817 }
820
818
821 if !ignore_patterns.is_empty() {
819 if !ignore_patterns.is_empty() {
822 // Either do dumb matching if all patterns are rootfiles, or match
820 // Either do dumb matching if all patterns are rootfiles, or match
823 // with a regex.
821 // with a regex.
824 if ignore_patterns
822 if ignore_patterns
825 .iter()
823 .iter()
826 .all(|k| k.syntax == PatternSyntax::RootFiles)
824 .all(|k| k.syntax == PatternSyntax::RootFiles)
827 {
825 {
828 let dirs: HashSet<_> = ignore_patterns
826 let dirs: HashSet<_> = ignore_patterns
829 .iter()
827 .iter()
830 .map(|k| k.pattern.to_owned())
828 .map(|k| k.pattern.to_owned())
831 .collect();
829 .collect();
832 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
830 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
833
831
834 let match_func = move |path: &HgPath| -> bool {
832 let match_func = move |path: &HgPath| -> bool {
835 let path = path.as_bytes();
833 let path = path.as_bytes();
836 let i = path.iter().rfind(|a| **a == b'/');
834 let i = path.iter().rfind(|a| **a == b'/');
837 let dir = if let Some(i) = i {
835 let dir = if let Some(i) = i {
838 &path[..*i as usize]
836 &path[..*i as usize]
839 } else {
837 } else {
840 b"."
838 b"."
841 };
839 };
842 dirs.contains(dir.deref())
840 dirs.contains(dir.deref())
843 };
841 };
844 match_funcs.push(Box::new(match_func));
842 match_funcs.push(Box::new(match_func));
845
843
846 patterns.extend(b"rootfilesin: ");
844 patterns.extend(b"rootfilesin: ");
847 dirs_vec.sort();
845 dirs_vec.sort();
848 patterns.extend(dirs_vec.escaped_bytes());
846 patterns.extend(dirs_vec.escaped_bytes());
849 } else {
847 } else {
850 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
848 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
851 patterns = new_re;
849 patterns = new_re;
852 match_funcs.push(match_func)
850 match_funcs.push(match_func)
853 }
851 }
854 }
852 }
855
853
856 Ok(if match_funcs.len() == 1 {
854 Ok(if match_funcs.len() == 1 {
857 (patterns, match_funcs.remove(0))
855 (patterns, match_funcs.remove(0))
858 } else {
856 } else {
859 (
857 (
860 patterns,
858 patterns,
861 Box::new(move |f: &HgPath| -> bool {
859 Box::new(move |f: &HgPath| -> bool {
862 match_funcs.iter().any(|match_func| match_func(f))
860 match_funcs.iter().any(|match_func| match_func(f))
863 }),
861 }),
864 )
862 )
865 })
863 })
866 }
864 }
867
865
868 /// Parses all "ignore" files with their recursive includes and returns a
866 /// Parses all "ignore" files with their recursive includes and returns a
869 /// function that checks whether a given file (in the general sense) should be
867 /// function that checks whether a given file (in the general sense) should be
870 /// ignored.
868 /// ignored.
871 pub fn get_ignore_matcher<'a>(
869 pub fn get_ignore_matcher<'a>(
872 mut all_pattern_files: Vec<PathBuf>,
870 mut all_pattern_files: Vec<PathBuf>,
873 root_dir: &Path,
871 root_dir: &Path,
874 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
872 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
875 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
873 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
876 let mut all_patterns = vec![];
874 let mut all_patterns = vec![];
877 let mut all_warnings = vec![];
875 let mut all_warnings = vec![];
878
876
879 // Sort to make the ordering of calls to `inspect_pattern_bytes`
877 // Sort to make the ordering of calls to `inspect_pattern_bytes`
880 // deterministic even if the ordering of `all_pattern_files` is not (such
878 // deterministic even if the ordering of `all_pattern_files` is not (such
881 // as when a iteration order of a Python dict or Rust HashMap is involved).
879 // as when a iteration order of a Python dict or Rust HashMap is involved).
882 // Sort by "string" representation instead of the default by component
880 // Sort by "string" representation instead of the default by component
883 // (with a Rust-specific definition of a component)
881 // (with a Rust-specific definition of a component)
884 all_pattern_files
882 all_pattern_files
885 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
883 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
886
884
887 for pattern_file in &all_pattern_files {
885 for pattern_file in &all_pattern_files {
888 let (patterns, warnings) = get_patterns_from_file(
886 let (patterns, warnings) = get_patterns_from_file(
889 pattern_file,
887 pattern_file,
890 root_dir,
888 root_dir,
891 inspect_pattern_bytes,
889 inspect_pattern_bytes,
892 )?;
890 )?;
893
891
894 all_patterns.extend(patterns.to_owned());
892 all_patterns.extend(patterns.to_owned());
895 all_warnings.extend(warnings);
893 all_warnings.extend(warnings);
896 }
894 }
897 let matcher = IncludeMatcher::new(all_patterns)?;
895 let matcher = IncludeMatcher::new(all_patterns)?;
898 Ok((matcher, all_warnings))
896 Ok((matcher, all_warnings))
899 }
897 }
900
898
901 /// Parses all "ignore" files with their recursive includes and returns a
899 /// Parses all "ignore" files with their recursive includes and returns a
902 /// function that checks whether a given file (in the general sense) should be
900 /// function that checks whether a given file (in the general sense) should be
903 /// ignored.
901 /// ignored.
904 pub fn get_ignore_function<'a>(
902 pub fn get_ignore_function<'a>(
905 all_pattern_files: Vec<PathBuf>,
903 all_pattern_files: Vec<PathBuf>,
906 root_dir: &Path,
904 root_dir: &Path,
907 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
905 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
908 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
906 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
909 let res =
907 let res =
910 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
908 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
911 res.map(|(matcher, all_warnings)| {
909 res.map(|(matcher, all_warnings)| {
912 let res: IgnoreFnType<'a> =
910 let res: IgnoreFnType<'a> =
913 Box::new(move |path: &HgPath| matcher.matches(path));
911 Box::new(move |path: &HgPath| matcher.matches(path));
914
912
915 (res, all_warnings)
913 (res, all_warnings)
916 })
914 })
917 }
915 }
918
916
919 impl<'a> IncludeMatcher<'a> {
917 impl<'a> IncludeMatcher<'a> {
920 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
918 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
921 let RootsDirsAndParents {
919 let RootsDirsAndParents {
922 roots,
920 roots,
923 dirs,
921 dirs,
924 parents,
922 parents,
925 } = roots_dirs_and_parents(&ignore_patterns)?;
923 } = roots_dirs_and_parents(&ignore_patterns)?;
926 let prefix = ignore_patterns.iter().all(|k| match k.syntax {
924 let prefix = ignore_patterns.iter().all(|k| match k.syntax {
927 PatternSyntax::Path | PatternSyntax::RelPath => true,
925 PatternSyntax::Path | PatternSyntax::RelPath => true,
928 _ => false,
926 _ => false,
929 });
927 });
930 let (patterns, match_fn) = build_match(ignore_patterns)?;
928 let (patterns, match_fn) = build_match(ignore_patterns)?;
931
929
932 Ok(Self {
930 Ok(Self {
933 patterns,
931 patterns,
934 match_fn,
932 match_fn,
935 prefix,
933 prefix,
936 roots,
934 roots,
937 dirs,
935 dirs,
938 parents,
936 parents,
939 })
937 })
940 }
938 }
941
939
942 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
940 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
943 // TODO cache
941 // TODO cache
944 let thing = self
942 let thing = self
945 .dirs
943 .dirs
946 .iter()
944 .iter()
947 .chain(self.roots.iter())
945 .chain(self.roots.iter())
948 .chain(self.parents.iter());
946 .chain(self.parents.iter());
949 DirsChildrenMultiset::new(thing, Some(&self.parents))
947 DirsChildrenMultiset::new(thing, Some(&self.parents))
950 }
948 }
951
949
952 pub fn debug_get_patterns(&self) -> &[u8] {
950 pub fn debug_get_patterns(&self) -> &[u8] {
953 self.patterns.as_ref()
951 self.patterns.as_ref()
954 }
952 }
955 }
953 }
956
954
957 impl<'a> Display for IncludeMatcher<'a> {
955 impl<'a> Display for IncludeMatcher<'a> {
958 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
956 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
959 // XXX What about exact matches?
957 // XXX What about exact matches?
960 // I'm not sure it's worth it to clone the HashSet and keep it
958 // I'm not sure it's worth it to clone the HashSet and keep it
961 // around just in case someone wants to display the matcher, plus
959 // around just in case someone wants to display the matcher, plus
962 // it's going to be unreadable after a few entries, but we need to
960 // it's going to be unreadable after a few entries, but we need to
963 // inform in this display that exact matches are being used and are
961 // inform in this display that exact matches are being used and are
964 // (on purpose) missing from the `includes`.
962 // (on purpose) missing from the `includes`.
965 write!(
963 write!(
966 f,
964 f,
967 "IncludeMatcher(includes='{}')",
965 "IncludeMatcher(includes='{}')",
968 String::from_utf8_lossy(&self.patterns.escaped_bytes())
966 String::from_utf8_lossy(&self.patterns.escaped_bytes())
969 )
967 )
970 }
968 }
971 }
969 }
972
970
973 #[cfg(test)]
971 #[cfg(test)]
974 mod tests {
972 mod tests {
975 use super::*;
973 use super::*;
976 use pretty_assertions::assert_eq;
974 use pretty_assertions::assert_eq;
977 use std::path::Path;
975 use std::path::Path;
978
976
979 #[test]
977 #[test]
980 fn test_roots_and_dirs() {
978 fn test_roots_and_dirs() {
981 let pats = vec![
979 let pats = vec![
982 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
980 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
983 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
981 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
984 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
982 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
985 ];
983 ];
986 let (roots, dirs) = roots_and_dirs(&pats);
984 let (roots, dirs) = roots_and_dirs(&pats);
987
985
988 assert_eq!(
986 assert_eq!(
989 roots,
987 roots,
990 vec!(
988 vec!(
991 HgPathBuf::from_bytes(b"g/h"),
989 HgPathBuf::from_bytes(b"g/h"),
992 HgPathBuf::from_bytes(b"g/h"),
990 HgPathBuf::from_bytes(b"g/h"),
993 HgPathBuf::new()
991 HgPathBuf::new()
994 ),
992 ),
995 );
993 );
996 assert_eq!(dirs, vec!());
994 assert_eq!(dirs, vec!());
997 }
995 }
998
996
999 #[test]
997 #[test]
1000 fn test_roots_dirs_and_parents() {
998 fn test_roots_dirs_and_parents() {
1001 let pats = vec![
999 let pats = vec![
1002 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1000 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1003 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1001 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1004 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1002 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1005 ];
1003 ];
1006
1004
1007 let mut roots = HashSet::new();
1005 let mut roots = HashSet::new();
1008 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1006 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1009 roots.insert(HgPathBuf::new());
1007 roots.insert(HgPathBuf::new());
1010
1008
1011 let dirs = HashSet::new();
1009 let dirs = HashSet::new();
1012
1010
1013 let mut parents = HashSet::new();
1011 let mut parents = HashSet::new();
1014 parents.insert(HgPathBuf::new());
1012 parents.insert(HgPathBuf::new());
1015 parents.insert(HgPathBuf::from_bytes(b"g"));
1013 parents.insert(HgPathBuf::from_bytes(b"g"));
1016
1014
1017 assert_eq!(
1015 assert_eq!(
1018 roots_dirs_and_parents(&pats).unwrap(),
1016 roots_dirs_and_parents(&pats).unwrap(),
1019 RootsDirsAndParents {
1017 RootsDirsAndParents {
1020 roots,
1018 roots,
1021 dirs,
1019 dirs,
1022 parents
1020 parents
1023 }
1021 }
1024 );
1022 );
1025 }
1023 }
1026
1024
1027 #[test]
1025 #[test]
1028 fn test_filematcher_visit_children_set() {
1026 fn test_filematcher_visit_children_set() {
1029 // Visitchildrenset
1027 // Visitchildrenset
1030 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1028 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1031 let matcher = FileMatcher::new(files).unwrap();
1029 let matcher = FileMatcher::new(files).unwrap();
1032
1030
1033 let mut set = HashSet::new();
1031 let mut set = HashSet::new();
1034 set.insert(HgPathBuf::from_bytes(b"dir"));
1032 set.insert(HgPathBuf::from_bytes(b"dir"));
1035 assert_eq!(
1033 assert_eq!(
1036 matcher.visit_children_set(HgPath::new(b"")),
1034 matcher.visit_children_set(HgPath::new(b"")),
1037 VisitChildrenSet::Set(set)
1035 VisitChildrenSet::Set(set)
1038 );
1036 );
1039
1037
1040 let mut set = HashSet::new();
1038 let mut set = HashSet::new();
1041 set.insert(HgPathBuf::from_bytes(b"subdir"));
1039 set.insert(HgPathBuf::from_bytes(b"subdir"));
1042 assert_eq!(
1040 assert_eq!(
1043 matcher.visit_children_set(HgPath::new(b"dir")),
1041 matcher.visit_children_set(HgPath::new(b"dir")),
1044 VisitChildrenSet::Set(set)
1042 VisitChildrenSet::Set(set)
1045 );
1043 );
1046
1044
1047 let mut set = HashSet::new();
1045 let mut set = HashSet::new();
1048 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1046 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1049 assert_eq!(
1047 assert_eq!(
1050 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1048 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1051 VisitChildrenSet::Set(set)
1049 VisitChildrenSet::Set(set)
1052 );
1050 );
1053
1051
1054 assert_eq!(
1052 assert_eq!(
1055 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1053 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1056 VisitChildrenSet::Empty
1054 VisitChildrenSet::Empty
1057 );
1055 );
1058 assert_eq!(
1056 assert_eq!(
1059 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1057 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1060 VisitChildrenSet::Empty
1058 VisitChildrenSet::Empty
1061 );
1059 );
1062 assert_eq!(
1060 assert_eq!(
1063 matcher.visit_children_set(HgPath::new(b"folder")),
1061 matcher.visit_children_set(HgPath::new(b"folder")),
1064 VisitChildrenSet::Empty
1062 VisitChildrenSet::Empty
1065 );
1063 );
1066 }
1064 }
1067
1065
1068 #[test]
1066 #[test]
1069 fn test_filematcher_visit_children_set_files_and_dirs() {
1067 fn test_filematcher_visit_children_set_files_and_dirs() {
1070 let files = vec![
1068 let files = vec![
1071 HgPathBuf::from_bytes(b"rootfile.txt"),
1069 HgPathBuf::from_bytes(b"rootfile.txt"),
1072 HgPathBuf::from_bytes(b"a/file1.txt"),
1070 HgPathBuf::from_bytes(b"a/file1.txt"),
1073 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1071 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1074 // No file in a/b/c
1072 // No file in a/b/c
1075 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1073 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1076 ];
1074 ];
1077 let matcher = FileMatcher::new(files).unwrap();
1075 let matcher = FileMatcher::new(files).unwrap();
1078
1076
1079 let mut set = HashSet::new();
1077 let mut set = HashSet::new();
1080 set.insert(HgPathBuf::from_bytes(b"a"));
1078 set.insert(HgPathBuf::from_bytes(b"a"));
1081 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1079 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1082 assert_eq!(
1080 assert_eq!(
1083 matcher.visit_children_set(HgPath::new(b"")),
1081 matcher.visit_children_set(HgPath::new(b"")),
1084 VisitChildrenSet::Set(set)
1082 VisitChildrenSet::Set(set)
1085 );
1083 );
1086
1084
1087 let mut set = HashSet::new();
1085 let mut set = HashSet::new();
1088 set.insert(HgPathBuf::from_bytes(b"b"));
1086 set.insert(HgPathBuf::from_bytes(b"b"));
1089 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1087 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1090 assert_eq!(
1088 assert_eq!(
1091 matcher.visit_children_set(HgPath::new(b"a")),
1089 matcher.visit_children_set(HgPath::new(b"a")),
1092 VisitChildrenSet::Set(set)
1090 VisitChildrenSet::Set(set)
1093 );
1091 );
1094
1092
1095 let mut set = HashSet::new();
1093 let mut set = HashSet::new();
1096 set.insert(HgPathBuf::from_bytes(b"c"));
1094 set.insert(HgPathBuf::from_bytes(b"c"));
1097 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1095 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1098 assert_eq!(
1096 assert_eq!(
1099 matcher.visit_children_set(HgPath::new(b"a/b")),
1097 matcher.visit_children_set(HgPath::new(b"a/b")),
1100 VisitChildrenSet::Set(set)
1098 VisitChildrenSet::Set(set)
1101 );
1099 );
1102
1100
1103 let mut set = HashSet::new();
1101 let mut set = HashSet::new();
1104 set.insert(HgPathBuf::from_bytes(b"d"));
1102 set.insert(HgPathBuf::from_bytes(b"d"));
1105 assert_eq!(
1103 assert_eq!(
1106 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1104 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1107 VisitChildrenSet::Set(set)
1105 VisitChildrenSet::Set(set)
1108 );
1106 );
1109 let mut set = HashSet::new();
1107 let mut set = HashSet::new();
1110 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1108 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1111 assert_eq!(
1109 assert_eq!(
1112 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1110 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1113 VisitChildrenSet::Set(set)
1111 VisitChildrenSet::Set(set)
1114 );
1112 );
1115
1113
1116 assert_eq!(
1114 assert_eq!(
1117 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1115 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1118 VisitChildrenSet::Empty
1116 VisitChildrenSet::Empty
1119 );
1117 );
1120 assert_eq!(
1118 assert_eq!(
1121 matcher.visit_children_set(HgPath::new(b"folder")),
1119 matcher.visit_children_set(HgPath::new(b"folder")),
1122 VisitChildrenSet::Empty
1120 VisitChildrenSet::Empty
1123 );
1121 );
1124 }
1122 }
1125
1123
1126 #[test]
1124 #[test]
1127 fn test_includematcher() {
1125 fn test_includematcher() {
1128 // VisitchildrensetPrefix
1126 // VisitchildrensetPrefix
1129 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1127 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1130 PatternSyntax::RelPath,
1128 PatternSyntax::RelPath,
1131 b"dir/subdir",
1129 b"dir/subdir",
1132 Path::new(""),
1130 Path::new(""),
1133 )])
1131 )])
1134 .unwrap();
1132 .unwrap();
1135
1133
1136 let mut set = HashSet::new();
1134 let mut set = HashSet::new();
1137 set.insert(HgPathBuf::from_bytes(b"dir"));
1135 set.insert(HgPathBuf::from_bytes(b"dir"));
1138 assert_eq!(
1136 assert_eq!(
1139 matcher.visit_children_set(HgPath::new(b"")),
1137 matcher.visit_children_set(HgPath::new(b"")),
1140 VisitChildrenSet::Set(set)
1138 VisitChildrenSet::Set(set)
1141 );
1139 );
1142
1140
1143 let mut set = HashSet::new();
1141 let mut set = HashSet::new();
1144 set.insert(HgPathBuf::from_bytes(b"subdir"));
1142 set.insert(HgPathBuf::from_bytes(b"subdir"));
1145 assert_eq!(
1143 assert_eq!(
1146 matcher.visit_children_set(HgPath::new(b"dir")),
1144 matcher.visit_children_set(HgPath::new(b"dir")),
1147 VisitChildrenSet::Set(set)
1145 VisitChildrenSet::Set(set)
1148 );
1146 );
1149 assert_eq!(
1147 assert_eq!(
1150 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1148 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1151 VisitChildrenSet::Recursive
1149 VisitChildrenSet::Recursive
1152 );
1150 );
1153 // OPT: This should probably be 'all' if its parent is?
1151 // OPT: This should probably be 'all' if its parent is?
1154 assert_eq!(
1152 assert_eq!(
1155 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1153 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1156 VisitChildrenSet::This
1154 VisitChildrenSet::This
1157 );
1155 );
1158 assert_eq!(
1156 assert_eq!(
1159 matcher.visit_children_set(HgPath::new(b"folder")),
1157 matcher.visit_children_set(HgPath::new(b"folder")),
1160 VisitChildrenSet::Empty
1158 VisitChildrenSet::Empty
1161 );
1159 );
1162
1160
1163 // VisitchildrensetRootfilesin
1161 // VisitchildrensetRootfilesin
1164 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1162 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1165 PatternSyntax::RootFiles,
1163 PatternSyntax::RootFiles,
1166 b"dir/subdir",
1164 b"dir/subdir",
1167 Path::new(""),
1165 Path::new(""),
1168 )])
1166 )])
1169 .unwrap();
1167 .unwrap();
1170
1168
1171 let mut set = HashSet::new();
1169 let mut set = HashSet::new();
1172 set.insert(HgPathBuf::from_bytes(b"dir"));
1170 set.insert(HgPathBuf::from_bytes(b"dir"));
1173 assert_eq!(
1171 assert_eq!(
1174 matcher.visit_children_set(HgPath::new(b"")),
1172 matcher.visit_children_set(HgPath::new(b"")),
1175 VisitChildrenSet::Set(set)
1173 VisitChildrenSet::Set(set)
1176 );
1174 );
1177
1175
1178 let mut set = HashSet::new();
1176 let mut set = HashSet::new();
1179 set.insert(HgPathBuf::from_bytes(b"subdir"));
1177 set.insert(HgPathBuf::from_bytes(b"subdir"));
1180 assert_eq!(
1178 assert_eq!(
1181 matcher.visit_children_set(HgPath::new(b"dir")),
1179 matcher.visit_children_set(HgPath::new(b"dir")),
1182 VisitChildrenSet::Set(set)
1180 VisitChildrenSet::Set(set)
1183 );
1181 );
1184
1182
1185 assert_eq!(
1183 assert_eq!(
1186 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1184 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1187 VisitChildrenSet::This
1185 VisitChildrenSet::This
1188 );
1186 );
1189 assert_eq!(
1187 assert_eq!(
1190 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1188 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1191 VisitChildrenSet::Empty
1189 VisitChildrenSet::Empty
1192 );
1190 );
1193 assert_eq!(
1191 assert_eq!(
1194 matcher.visit_children_set(HgPath::new(b"folder")),
1192 matcher.visit_children_set(HgPath::new(b"folder")),
1195 VisitChildrenSet::Empty
1193 VisitChildrenSet::Empty
1196 );
1194 );
1197
1195
1198 // VisitchildrensetGlob
1196 // VisitchildrensetGlob
1199 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1197 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1200 PatternSyntax::Glob,
1198 PatternSyntax::Glob,
1201 b"dir/z*",
1199 b"dir/z*",
1202 Path::new(""),
1200 Path::new(""),
1203 )])
1201 )])
1204 .unwrap();
1202 .unwrap();
1205
1203
1206 let mut set = HashSet::new();
1204 let mut set = HashSet::new();
1207 set.insert(HgPathBuf::from_bytes(b"dir"));
1205 set.insert(HgPathBuf::from_bytes(b"dir"));
1208 assert_eq!(
1206 assert_eq!(
1209 matcher.visit_children_set(HgPath::new(b"")),
1207 matcher.visit_children_set(HgPath::new(b"")),
1210 VisitChildrenSet::Set(set)
1208 VisitChildrenSet::Set(set)
1211 );
1209 );
1212 assert_eq!(
1210 assert_eq!(
1213 matcher.visit_children_set(HgPath::new(b"folder")),
1211 matcher.visit_children_set(HgPath::new(b"folder")),
1214 VisitChildrenSet::Empty
1212 VisitChildrenSet::Empty
1215 );
1213 );
1216 assert_eq!(
1214 assert_eq!(
1217 matcher.visit_children_set(HgPath::new(b"dir")),
1215 matcher.visit_children_set(HgPath::new(b"dir")),
1218 VisitChildrenSet::This
1216 VisitChildrenSet::This
1219 );
1217 );
1220 // OPT: these should probably be set().
1218 // OPT: these should probably be set().
1221 assert_eq!(
1219 assert_eq!(
1222 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1220 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1223 VisitChildrenSet::This
1221 VisitChildrenSet::This
1224 );
1222 );
1225 assert_eq!(
1223 assert_eq!(
1226 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1224 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1227 VisitChildrenSet::This
1225 VisitChildrenSet::This
1228 );
1226 );
1229
1227
1230 // Test multiple patterns
1228 // Test multiple patterns
1231 let matcher = IncludeMatcher::new(vec![
1229 let matcher = IncludeMatcher::new(vec![
1232 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1230 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1233 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1231 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1234 ])
1232 ])
1235 .unwrap();
1233 .unwrap();
1236
1234
1237 assert_eq!(
1235 assert_eq!(
1238 matcher.visit_children_set(HgPath::new(b"")),
1236 matcher.visit_children_set(HgPath::new(b"")),
1239 VisitChildrenSet::This
1237 VisitChildrenSet::This
1240 );
1238 );
1241
1239
1242 // Test multiple patterns
1240 // Test multiple patterns
1243 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1241 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1244 PatternSyntax::Glob,
1242 PatternSyntax::Glob,
1245 b"**/*.exe",
1243 b"**/*.exe",
1246 Path::new(""),
1244 Path::new(""),
1247 )])
1245 )])
1248 .unwrap();
1246 .unwrap();
1249
1247
1250 assert_eq!(
1248 assert_eq!(
1251 matcher.visit_children_set(HgPath::new(b"")),
1249 matcher.visit_children_set(HgPath::new(b"")),
1252 VisitChildrenSet::This
1250 VisitChildrenSet::This
1253 );
1251 );
1254 }
1252 }
1255
1253
1256 #[test]
1254 #[test]
1257 fn test_unionmatcher() {
1255 fn test_unionmatcher() {
1258 // Path + Rootfiles
1256 // Path + Rootfiles
1259 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1257 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1260 PatternSyntax::RelPath,
1258 PatternSyntax::RelPath,
1261 b"dir/subdir",
1259 b"dir/subdir",
1262 Path::new(""),
1260 Path::new(""),
1263 )])
1261 )])
1264 .unwrap();
1262 .unwrap();
1265 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1263 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1266 PatternSyntax::RootFiles,
1264 PatternSyntax::RootFiles,
1267 b"dir",
1265 b"dir",
1268 Path::new(""),
1266 Path::new(""),
1269 )])
1267 )])
1270 .unwrap();
1268 .unwrap();
1271 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1269 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1272
1270
1273 let mut set = HashSet::new();
1271 let mut set = HashSet::new();
1274 set.insert(HgPathBuf::from_bytes(b"dir"));
1272 set.insert(HgPathBuf::from_bytes(b"dir"));
1275 assert_eq!(
1273 assert_eq!(
1276 matcher.visit_children_set(HgPath::new(b"")),
1274 matcher.visit_children_set(HgPath::new(b"")),
1277 VisitChildrenSet::Set(set)
1275 VisitChildrenSet::Set(set)
1278 );
1276 );
1279 assert_eq!(
1277 assert_eq!(
1280 matcher.visit_children_set(HgPath::new(b"dir")),
1278 matcher.visit_children_set(HgPath::new(b"dir")),
1281 VisitChildrenSet::This
1279 VisitChildrenSet::This
1282 );
1280 );
1283 assert_eq!(
1281 assert_eq!(
1284 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1282 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1285 VisitChildrenSet::Recursive
1283 VisitChildrenSet::Recursive
1286 );
1284 );
1287 assert_eq!(
1285 assert_eq!(
1288 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1286 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1289 VisitChildrenSet::Empty
1287 VisitChildrenSet::Empty
1290 );
1288 );
1291 assert_eq!(
1289 assert_eq!(
1292 matcher.visit_children_set(HgPath::new(b"folder")),
1290 matcher.visit_children_set(HgPath::new(b"folder")),
1293 VisitChildrenSet::Empty
1291 VisitChildrenSet::Empty
1294 );
1292 );
1295 assert_eq!(
1293 assert_eq!(
1296 matcher.visit_children_set(HgPath::new(b"folder")),
1294 matcher.visit_children_set(HgPath::new(b"folder")),
1297 VisitChildrenSet::Empty
1295 VisitChildrenSet::Empty
1298 );
1296 );
1299
1297
1300 // OPT: These next two could be 'all' instead of 'this'.
1298 // OPT: These next two could be 'all' instead of 'this'.
1301 assert_eq!(
1299 assert_eq!(
1302 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1300 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1303 VisitChildrenSet::This
1301 VisitChildrenSet::This
1304 );
1302 );
1305 assert_eq!(
1303 assert_eq!(
1306 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1304 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1307 VisitChildrenSet::This
1305 VisitChildrenSet::This
1308 );
1306 );
1309
1307
1310 // Path + unrelated Path
1308 // Path + unrelated Path
1311 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1309 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1312 PatternSyntax::RelPath,
1310 PatternSyntax::RelPath,
1313 b"dir/subdir",
1311 b"dir/subdir",
1314 Path::new(""),
1312 Path::new(""),
1315 )])
1313 )])
1316 .unwrap();
1314 .unwrap();
1317 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1315 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1318 PatternSyntax::RelPath,
1316 PatternSyntax::RelPath,
1319 b"folder",
1317 b"folder",
1320 Path::new(""),
1318 Path::new(""),
1321 )])
1319 )])
1322 .unwrap();
1320 .unwrap();
1323 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1321 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1324
1322
1325 let mut set = HashSet::new();
1323 let mut set = HashSet::new();
1326 set.insert(HgPathBuf::from_bytes(b"folder"));
1324 set.insert(HgPathBuf::from_bytes(b"folder"));
1327 set.insert(HgPathBuf::from_bytes(b"dir"));
1325 set.insert(HgPathBuf::from_bytes(b"dir"));
1328 assert_eq!(
1326 assert_eq!(
1329 matcher.visit_children_set(HgPath::new(b"")),
1327 matcher.visit_children_set(HgPath::new(b"")),
1330 VisitChildrenSet::Set(set)
1328 VisitChildrenSet::Set(set)
1331 );
1329 );
1332 let mut set = HashSet::new();
1330 let mut set = HashSet::new();
1333 set.insert(HgPathBuf::from_bytes(b"subdir"));
1331 set.insert(HgPathBuf::from_bytes(b"subdir"));
1334 assert_eq!(
1332 assert_eq!(
1335 matcher.visit_children_set(HgPath::new(b"dir")),
1333 matcher.visit_children_set(HgPath::new(b"dir")),
1336 VisitChildrenSet::Set(set)
1334 VisitChildrenSet::Set(set)
1337 );
1335 );
1338
1336
1339 assert_eq!(
1337 assert_eq!(
1340 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1338 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1341 VisitChildrenSet::Recursive
1339 VisitChildrenSet::Recursive
1342 );
1340 );
1343 assert_eq!(
1341 assert_eq!(
1344 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1342 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1345 VisitChildrenSet::Empty
1343 VisitChildrenSet::Empty
1346 );
1344 );
1347
1345
1348 assert_eq!(
1346 assert_eq!(
1349 matcher.visit_children_set(HgPath::new(b"folder")),
1347 matcher.visit_children_set(HgPath::new(b"folder")),
1350 VisitChildrenSet::Recursive
1348 VisitChildrenSet::Recursive
1351 );
1349 );
1352 // OPT: These next two could be 'all' instead of 'this'.
1350 // OPT: These next two could be 'all' instead of 'this'.
1353 assert_eq!(
1351 assert_eq!(
1354 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1352 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1355 VisitChildrenSet::This
1353 VisitChildrenSet::This
1356 );
1354 );
1357 assert_eq!(
1355 assert_eq!(
1358 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1356 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1359 VisitChildrenSet::This
1357 VisitChildrenSet::This
1360 );
1358 );
1361
1359
1362 // Path + subpath
1360 // Path + subpath
1363 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1361 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1364 PatternSyntax::RelPath,
1362 PatternSyntax::RelPath,
1365 b"dir/subdir/x",
1363 b"dir/subdir/x",
1366 Path::new(""),
1364 Path::new(""),
1367 )])
1365 )])
1368 .unwrap();
1366 .unwrap();
1369 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1367 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1370 PatternSyntax::RelPath,
1368 PatternSyntax::RelPath,
1371 b"dir/subdir",
1369 b"dir/subdir",
1372 Path::new(""),
1370 Path::new(""),
1373 )])
1371 )])
1374 .unwrap();
1372 .unwrap();
1375 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1373 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1376
1374
1377 let mut set = HashSet::new();
1375 let mut set = HashSet::new();
1378 set.insert(HgPathBuf::from_bytes(b"dir"));
1376 set.insert(HgPathBuf::from_bytes(b"dir"));
1379 assert_eq!(
1377 assert_eq!(
1380 matcher.visit_children_set(HgPath::new(b"")),
1378 matcher.visit_children_set(HgPath::new(b"")),
1381 VisitChildrenSet::Set(set)
1379 VisitChildrenSet::Set(set)
1382 );
1380 );
1383 let mut set = HashSet::new();
1381 let mut set = HashSet::new();
1384 set.insert(HgPathBuf::from_bytes(b"subdir"));
1382 set.insert(HgPathBuf::from_bytes(b"subdir"));
1385 assert_eq!(
1383 assert_eq!(
1386 matcher.visit_children_set(HgPath::new(b"dir")),
1384 matcher.visit_children_set(HgPath::new(b"dir")),
1387 VisitChildrenSet::Set(set)
1385 VisitChildrenSet::Set(set)
1388 );
1386 );
1389
1387
1390 assert_eq!(
1388 assert_eq!(
1391 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1389 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1392 VisitChildrenSet::Recursive
1390 VisitChildrenSet::Recursive
1393 );
1391 );
1394 assert_eq!(
1392 assert_eq!(
1395 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1393 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1396 VisitChildrenSet::Empty
1394 VisitChildrenSet::Empty
1397 );
1395 );
1398
1396
1399 assert_eq!(
1397 assert_eq!(
1400 matcher.visit_children_set(HgPath::new(b"folder")),
1398 matcher.visit_children_set(HgPath::new(b"folder")),
1401 VisitChildrenSet::Empty
1399 VisitChildrenSet::Empty
1402 );
1400 );
1403 assert_eq!(
1401 assert_eq!(
1404 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1402 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1405 VisitChildrenSet::Recursive
1403 VisitChildrenSet::Recursive
1406 );
1404 );
1407 // OPT: this should probably be 'all' not 'this'.
1405 // OPT: this should probably be 'all' not 'this'.
1408 assert_eq!(
1406 assert_eq!(
1409 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1407 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1410 VisitChildrenSet::This
1408 VisitChildrenSet::This
1411 );
1409 );
1412 }
1410 }
1413
1411
1414 #[test]
1412 #[test]
1415 fn test_intersectionmatcher() {
1413 fn test_intersectionmatcher() {
1416 // Include path + Include rootfiles
1414 // Include path + Include rootfiles
1417 let m1 = Box::new(
1415 let m1 = Box::new(
1418 IncludeMatcher::new(vec![IgnorePattern::new(
1416 IncludeMatcher::new(vec![IgnorePattern::new(
1419 PatternSyntax::RelPath,
1417 PatternSyntax::RelPath,
1420 b"dir/subdir",
1418 b"dir/subdir",
1421 Path::new(""),
1419 Path::new(""),
1422 )])
1420 )])
1423 .unwrap(),
1421 .unwrap(),
1424 );
1422 );
1425 let m2 = Box::new(
1423 let m2 = Box::new(
1426 IncludeMatcher::new(vec![IgnorePattern::new(
1424 IncludeMatcher::new(vec![IgnorePattern::new(
1427 PatternSyntax::RootFiles,
1425 PatternSyntax::RootFiles,
1428 b"dir",
1426 b"dir",
1429 Path::new(""),
1427 Path::new(""),
1430 )])
1428 )])
1431 .unwrap(),
1429 .unwrap(),
1432 );
1430 );
1433 let matcher = IntersectionMatcher::new(m1, m2);
1431 let matcher = IntersectionMatcher::new(m1, m2);
1434
1432
1435 let mut set = HashSet::new();
1433 let mut set = HashSet::new();
1436 set.insert(HgPathBuf::from_bytes(b"dir"));
1434 set.insert(HgPathBuf::from_bytes(b"dir"));
1437 assert_eq!(
1435 assert_eq!(
1438 matcher.visit_children_set(HgPath::new(b"")),
1436 matcher.visit_children_set(HgPath::new(b"")),
1439 VisitChildrenSet::Set(set)
1437 VisitChildrenSet::Set(set)
1440 );
1438 );
1441 assert_eq!(
1439 assert_eq!(
1442 matcher.visit_children_set(HgPath::new(b"dir")),
1440 matcher.visit_children_set(HgPath::new(b"dir")),
1443 VisitChildrenSet::This
1441 VisitChildrenSet::This
1444 );
1442 );
1445 assert_eq!(
1443 assert_eq!(
1446 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1444 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1447 VisitChildrenSet::Empty
1445 VisitChildrenSet::Empty
1448 );
1446 );
1449 assert_eq!(
1447 assert_eq!(
1450 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1448 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1451 VisitChildrenSet::Empty
1449 VisitChildrenSet::Empty
1452 );
1450 );
1453 assert_eq!(
1451 assert_eq!(
1454 matcher.visit_children_set(HgPath::new(b"folder")),
1452 matcher.visit_children_set(HgPath::new(b"folder")),
1455 VisitChildrenSet::Empty
1453 VisitChildrenSet::Empty
1456 );
1454 );
1457 assert_eq!(
1455 assert_eq!(
1458 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1456 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1459 VisitChildrenSet::Empty
1457 VisitChildrenSet::Empty
1460 );
1458 );
1461 assert_eq!(
1459 assert_eq!(
1462 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1460 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1463 VisitChildrenSet::Empty
1461 VisitChildrenSet::Empty
1464 );
1462 );
1465
1463
1466 // Non intersecting paths
1464 // Non intersecting paths
1467 let m1 = Box::new(
1465 let m1 = Box::new(
1468 IncludeMatcher::new(vec![IgnorePattern::new(
1466 IncludeMatcher::new(vec![IgnorePattern::new(
1469 PatternSyntax::RelPath,
1467 PatternSyntax::RelPath,
1470 b"dir/subdir",
1468 b"dir/subdir",
1471 Path::new(""),
1469 Path::new(""),
1472 )])
1470 )])
1473 .unwrap(),
1471 .unwrap(),
1474 );
1472 );
1475 let m2 = Box::new(
1473 let m2 = Box::new(
1476 IncludeMatcher::new(vec![IgnorePattern::new(
1474 IncludeMatcher::new(vec![IgnorePattern::new(
1477 PatternSyntax::RelPath,
1475 PatternSyntax::RelPath,
1478 b"folder",
1476 b"folder",
1479 Path::new(""),
1477 Path::new(""),
1480 )])
1478 )])
1481 .unwrap(),
1479 .unwrap(),
1482 );
1480 );
1483 let matcher = IntersectionMatcher::new(m1, m2);
1481 let matcher = IntersectionMatcher::new(m1, m2);
1484
1482
1485 assert_eq!(
1483 assert_eq!(
1486 matcher.visit_children_set(HgPath::new(b"")),
1484 matcher.visit_children_set(HgPath::new(b"")),
1487 VisitChildrenSet::Empty
1485 VisitChildrenSet::Empty
1488 );
1486 );
1489 assert_eq!(
1487 assert_eq!(
1490 matcher.visit_children_set(HgPath::new(b"dir")),
1488 matcher.visit_children_set(HgPath::new(b"dir")),
1491 VisitChildrenSet::Empty
1489 VisitChildrenSet::Empty
1492 );
1490 );
1493 assert_eq!(
1491 assert_eq!(
1494 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1492 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1495 VisitChildrenSet::Empty
1493 VisitChildrenSet::Empty
1496 );
1494 );
1497 assert_eq!(
1495 assert_eq!(
1498 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1496 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1499 VisitChildrenSet::Empty
1497 VisitChildrenSet::Empty
1500 );
1498 );
1501 assert_eq!(
1499 assert_eq!(
1502 matcher.visit_children_set(HgPath::new(b"folder")),
1500 matcher.visit_children_set(HgPath::new(b"folder")),
1503 VisitChildrenSet::Empty
1501 VisitChildrenSet::Empty
1504 );
1502 );
1505 assert_eq!(
1503 assert_eq!(
1506 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1504 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1507 VisitChildrenSet::Empty
1505 VisitChildrenSet::Empty
1508 );
1506 );
1509 assert_eq!(
1507 assert_eq!(
1510 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1508 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1511 VisitChildrenSet::Empty
1509 VisitChildrenSet::Empty
1512 );
1510 );
1513
1511
1514 // Nested paths
1512 // Nested paths
1515 let m1 = Box::new(
1513 let m1 = Box::new(
1516 IncludeMatcher::new(vec![IgnorePattern::new(
1514 IncludeMatcher::new(vec![IgnorePattern::new(
1517 PatternSyntax::RelPath,
1515 PatternSyntax::RelPath,
1518 b"dir/subdir/x",
1516 b"dir/subdir/x",
1519 Path::new(""),
1517 Path::new(""),
1520 )])
1518 )])
1521 .unwrap(),
1519 .unwrap(),
1522 );
1520 );
1523 let m2 = Box::new(
1521 let m2 = Box::new(
1524 IncludeMatcher::new(vec![IgnorePattern::new(
1522 IncludeMatcher::new(vec![IgnorePattern::new(
1525 PatternSyntax::RelPath,
1523 PatternSyntax::RelPath,
1526 b"dir/subdir",
1524 b"dir/subdir",
1527 Path::new(""),
1525 Path::new(""),
1528 )])
1526 )])
1529 .unwrap(),
1527 .unwrap(),
1530 );
1528 );
1531 let matcher = IntersectionMatcher::new(m1, m2);
1529 let matcher = IntersectionMatcher::new(m1, m2);
1532
1530
1533 let mut set = HashSet::new();
1531 let mut set = HashSet::new();
1534 set.insert(HgPathBuf::from_bytes(b"dir"));
1532 set.insert(HgPathBuf::from_bytes(b"dir"));
1535 assert_eq!(
1533 assert_eq!(
1536 matcher.visit_children_set(HgPath::new(b"")),
1534 matcher.visit_children_set(HgPath::new(b"")),
1537 VisitChildrenSet::Set(set)
1535 VisitChildrenSet::Set(set)
1538 );
1536 );
1539
1537
1540 let mut set = HashSet::new();
1538 let mut set = HashSet::new();
1541 set.insert(HgPathBuf::from_bytes(b"subdir"));
1539 set.insert(HgPathBuf::from_bytes(b"subdir"));
1542 assert_eq!(
1540 assert_eq!(
1543 matcher.visit_children_set(HgPath::new(b"dir")),
1541 matcher.visit_children_set(HgPath::new(b"dir")),
1544 VisitChildrenSet::Set(set)
1542 VisitChildrenSet::Set(set)
1545 );
1543 );
1546 let mut set = HashSet::new();
1544 let mut set = HashSet::new();
1547 set.insert(HgPathBuf::from_bytes(b"x"));
1545 set.insert(HgPathBuf::from_bytes(b"x"));
1548 assert_eq!(
1546 assert_eq!(
1549 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1547 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1550 VisitChildrenSet::Set(set)
1548 VisitChildrenSet::Set(set)
1551 );
1549 );
1552 assert_eq!(
1550 assert_eq!(
1553 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1551 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1554 VisitChildrenSet::Empty
1552 VisitChildrenSet::Empty
1555 );
1553 );
1556 assert_eq!(
1554 assert_eq!(
1557 matcher.visit_children_set(HgPath::new(b"folder")),
1555 matcher.visit_children_set(HgPath::new(b"folder")),
1558 VisitChildrenSet::Empty
1556 VisitChildrenSet::Empty
1559 );
1557 );
1560 assert_eq!(
1558 assert_eq!(
1561 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1559 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1562 VisitChildrenSet::Empty
1560 VisitChildrenSet::Empty
1563 );
1561 );
1564 // OPT: this should probably be 'all' not 'this'.
1562 // OPT: this should probably be 'all' not 'this'.
1565 assert_eq!(
1563 assert_eq!(
1566 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1564 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1567 VisitChildrenSet::This
1565 VisitChildrenSet::This
1568 );
1566 );
1569
1567
1570 // Diverging paths
1568 // Diverging paths
1571 let m1 = Box::new(
1569 let m1 = Box::new(
1572 IncludeMatcher::new(vec![IgnorePattern::new(
1570 IncludeMatcher::new(vec![IgnorePattern::new(
1573 PatternSyntax::RelPath,
1571 PatternSyntax::RelPath,
1574 b"dir/subdir/x",
1572 b"dir/subdir/x",
1575 Path::new(""),
1573 Path::new(""),
1576 )])
1574 )])
1577 .unwrap(),
1575 .unwrap(),
1578 );
1576 );
1579 let m2 = Box::new(
1577 let m2 = Box::new(
1580 IncludeMatcher::new(vec![IgnorePattern::new(
1578 IncludeMatcher::new(vec![IgnorePattern::new(
1581 PatternSyntax::RelPath,
1579 PatternSyntax::RelPath,
1582 b"dir/subdir/z",
1580 b"dir/subdir/z",
1583 Path::new(""),
1581 Path::new(""),
1584 )])
1582 )])
1585 .unwrap(),
1583 .unwrap(),
1586 );
1584 );
1587 let matcher = IntersectionMatcher::new(m1, m2);
1585 let matcher = IntersectionMatcher::new(m1, m2);
1588
1586
1589 // OPT: these next two could probably be Empty as well.
1587 // OPT: these next two could probably be Empty as well.
1590 let mut set = HashSet::new();
1588 let mut set = HashSet::new();
1591 set.insert(HgPathBuf::from_bytes(b"dir"));
1589 set.insert(HgPathBuf::from_bytes(b"dir"));
1592 assert_eq!(
1590 assert_eq!(
1593 matcher.visit_children_set(HgPath::new(b"")),
1591 matcher.visit_children_set(HgPath::new(b"")),
1594 VisitChildrenSet::Set(set)
1592 VisitChildrenSet::Set(set)
1595 );
1593 );
1596 // OPT: these next two could probably be Empty as well.
1594 // OPT: these next two could probably be Empty as well.
1597 let mut set = HashSet::new();
1595 let mut set = HashSet::new();
1598 set.insert(HgPathBuf::from_bytes(b"subdir"));
1596 set.insert(HgPathBuf::from_bytes(b"subdir"));
1599 assert_eq!(
1597 assert_eq!(
1600 matcher.visit_children_set(HgPath::new(b"dir")),
1598 matcher.visit_children_set(HgPath::new(b"dir")),
1601 VisitChildrenSet::Set(set)
1599 VisitChildrenSet::Set(set)
1602 );
1600 );
1603 assert_eq!(
1601 assert_eq!(
1604 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1602 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1605 VisitChildrenSet::Empty
1603 VisitChildrenSet::Empty
1606 );
1604 );
1607 assert_eq!(
1605 assert_eq!(
1608 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1606 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1609 VisitChildrenSet::Empty
1607 VisitChildrenSet::Empty
1610 );
1608 );
1611 assert_eq!(
1609 assert_eq!(
1612 matcher.visit_children_set(HgPath::new(b"folder")),
1610 matcher.visit_children_set(HgPath::new(b"folder")),
1613 VisitChildrenSet::Empty
1611 VisitChildrenSet::Empty
1614 );
1612 );
1615 assert_eq!(
1613 assert_eq!(
1616 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1614 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1617 VisitChildrenSet::Empty
1615 VisitChildrenSet::Empty
1618 );
1616 );
1619 assert_eq!(
1617 assert_eq!(
1620 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1618 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1621 VisitChildrenSet::Empty
1619 VisitChildrenSet::Empty
1622 );
1620 );
1623 }
1621 }
1624
1622
1625 #[test]
1623 #[test]
1626 fn test_differencematcher() {
1624 fn test_differencematcher() {
1627 // Two alwaysmatchers should function like a nevermatcher
1625 // Two alwaysmatchers should function like a nevermatcher
1628 let m1 = AlwaysMatcher;
1626 let m1 = AlwaysMatcher;
1629 let m2 = AlwaysMatcher;
1627 let m2 = AlwaysMatcher;
1630 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1628 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1631
1629
1632 for case in &[
1630 for case in &[
1633 &b""[..],
1631 &b""[..],
1634 b"dir",
1632 b"dir",
1635 b"dir/subdir",
1633 b"dir/subdir",
1636 b"dir/subdir/z",
1634 b"dir/subdir/z",
1637 b"dir/foo",
1635 b"dir/foo",
1638 b"dir/subdir/x",
1636 b"dir/subdir/x",
1639 b"folder",
1637 b"folder",
1640 ] {
1638 ] {
1641 assert_eq!(
1639 assert_eq!(
1642 matcher.visit_children_set(HgPath::new(case)),
1640 matcher.visit_children_set(HgPath::new(case)),
1643 VisitChildrenSet::Empty
1641 VisitChildrenSet::Empty
1644 );
1642 );
1645 }
1643 }
1646
1644
1647 // One always and one never should behave the same as an always
1645 // One always and one never should behave the same as an always
1648 let m1 = AlwaysMatcher;
1646 let m1 = AlwaysMatcher;
1649 let m2 = NeverMatcher;
1647 let m2 = NeverMatcher;
1650 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1648 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
1651
1649
1652 for case in &[
1650 for case in &[
1653 &b""[..],
1651 &b""[..],
1654 b"dir",
1652 b"dir",
1655 b"dir/subdir",
1653 b"dir/subdir",
1656 b"dir/subdir/z",
1654 b"dir/subdir/z",
1657 b"dir/foo",
1655 b"dir/foo",
1658 b"dir/subdir/x",
1656 b"dir/subdir/x",
1659 b"folder",
1657 b"folder",
1660 ] {
1658 ] {
1661 assert_eq!(
1659 assert_eq!(
1662 matcher.visit_children_set(HgPath::new(case)),
1660 matcher.visit_children_set(HgPath::new(case)),
1663 VisitChildrenSet::Recursive
1661 VisitChildrenSet::Recursive
1664 );
1662 );
1665 }
1663 }
1666
1664
1667 // Two include matchers
1665 // Two include matchers
1668 let m1 = Box::new(
1666 let m1 = Box::new(
1669 IncludeMatcher::new(vec![IgnorePattern::new(
1667 IncludeMatcher::new(vec![IgnorePattern::new(
1670 PatternSyntax::RelPath,
1668 PatternSyntax::RelPath,
1671 b"dir/subdir",
1669 b"dir/subdir",
1672 Path::new("/repo"),
1670 Path::new("/repo"),
1673 )])
1671 )])
1674 .unwrap(),
1672 .unwrap(),
1675 );
1673 );
1676 let m2 = Box::new(
1674 let m2 = Box::new(
1677 IncludeMatcher::new(vec![IgnorePattern::new(
1675 IncludeMatcher::new(vec![IgnorePattern::new(
1678 PatternSyntax::RootFiles,
1676 PatternSyntax::RootFiles,
1679 b"dir",
1677 b"dir",
1680 Path::new("/repo"),
1678 Path::new("/repo"),
1681 )])
1679 )])
1682 .unwrap(),
1680 .unwrap(),
1683 );
1681 );
1684
1682
1685 let matcher = DifferenceMatcher::new(m1, m2);
1683 let matcher = DifferenceMatcher::new(m1, m2);
1686
1684
1687 let mut set = HashSet::new();
1685 let mut set = HashSet::new();
1688 set.insert(HgPathBuf::from_bytes(b"dir"));
1686 set.insert(HgPathBuf::from_bytes(b"dir"));
1689 assert_eq!(
1687 assert_eq!(
1690 matcher.visit_children_set(HgPath::new(b"")),
1688 matcher.visit_children_set(HgPath::new(b"")),
1691 VisitChildrenSet::Set(set)
1689 VisitChildrenSet::Set(set)
1692 );
1690 );
1693
1691
1694 let mut set = HashSet::new();
1692 let mut set = HashSet::new();
1695 set.insert(HgPathBuf::from_bytes(b"subdir"));
1693 set.insert(HgPathBuf::from_bytes(b"subdir"));
1696 assert_eq!(
1694 assert_eq!(
1697 matcher.visit_children_set(HgPath::new(b"dir")),
1695 matcher.visit_children_set(HgPath::new(b"dir")),
1698 VisitChildrenSet::Set(set)
1696 VisitChildrenSet::Set(set)
1699 );
1697 );
1700 assert_eq!(
1698 assert_eq!(
1701 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1699 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1702 VisitChildrenSet::Recursive
1700 VisitChildrenSet::Recursive
1703 );
1701 );
1704 assert_eq!(
1702 assert_eq!(
1705 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1703 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1706 VisitChildrenSet::Empty
1704 VisitChildrenSet::Empty
1707 );
1705 );
1708 assert_eq!(
1706 assert_eq!(
1709 matcher.visit_children_set(HgPath::new(b"folder")),
1707 matcher.visit_children_set(HgPath::new(b"folder")),
1710 VisitChildrenSet::Empty
1708 VisitChildrenSet::Empty
1711 );
1709 );
1712 assert_eq!(
1710 assert_eq!(
1713 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1711 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1714 VisitChildrenSet::This
1712 VisitChildrenSet::This
1715 );
1713 );
1716 assert_eq!(
1714 assert_eq!(
1717 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1715 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1718 VisitChildrenSet::This
1716 VisitChildrenSet::This
1719 );
1717 );
1720 }
1718 }
1721 }
1719 }
@@ -1,25 +1,25 b''
1 [package]
1 [package]
2 name = "rhg"
2 name = "rhg"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = [
4 authors = [
5 "Antoine Cezar <antoine.cezar@octobus.net>",
5 "Antoine Cezar <antoine.cezar@octobus.net>",
6 "Raphaël Gomès <raphael.gomes@octobus.net>",
6 "Raphaël Gomès <raphael.gomes@octobus.net>",
7 ]
7 ]
8 edition = "2021"
8 edition = "2021"
9
9
10 [dependencies]
10 [dependencies]
11 atty = "0.2.14"
11 atty = "0.2.14"
12 hg-core = { path = "../hg-core"}
12 hg-core = { path = "../hg-core"}
13 chrono = "0.4.23"
13 chrono = "0.4.23"
14 clap = { version = "4.0.24", features = ["cargo"] }
14 clap = { version = "4.0.24", features = ["cargo"] }
15 derive_more = "0.99.17"
15 derive_more = "0.99.17"
16 home = "0.5.4"
16 home = "0.5.4"
17 lazy_static = "1.4.0"
17 lazy_static = "1.4.0"
18 log = "0.4.17"
18 log = "0.4.17"
19 micro-timer = "0.4.0"
19 logging_timer = "1.1.0"
20 regex = "1.7.0"
20 regex = "1.7.0"
21 env_logger = "0.9.3"
21 env_logger = "0.9.3"
22 format-bytes = "0.3.0"
22 format-bytes = "0.3.0"
23 users = "0.11.0"
23 users = "0.11.0"
24 which = "4.3.0"
24 which = "4.3.0"
25 rayon = "1.5.3"
25 rayon = "1.5.3"
@@ -1,118 +1,117 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use clap::Arg;
2 use clap::Arg;
3 use format_bytes::format_bytes;
3 use format_bytes::format_bytes;
4 use hg::operations::cat;
4 use hg::operations::cat;
5 use hg::utils::hg_path::HgPathBuf;
5 use hg::utils::hg_path::HgPathBuf;
6 use micro_timer::timed;
7 use std::ffi::OsString;
6 use std::ffi::OsString;
8 use std::os::unix::prelude::OsStrExt;
7 use std::os::unix::prelude::OsStrExt;
9
8
10 pub const HELP_TEXT: &str = "
9 pub const HELP_TEXT: &str = "
11 Output the current or given revision of files
10 Output the current or given revision of files
12 ";
11 ";
13
12
14 pub fn args() -> clap::Command {
13 pub fn args() -> clap::Command {
15 clap::command!("cat")
14 clap::command!("cat")
16 .arg(
15 .arg(
17 Arg::new("rev")
16 Arg::new("rev")
18 .help("search the repository as it is in REV")
17 .help("search the repository as it is in REV")
19 .short('r')
18 .short('r')
20 .long("rev")
19 .long("rev")
21 .value_name("REV"),
20 .value_name("REV"),
22 )
21 )
23 .arg(
22 .arg(
24 clap::Arg::new("files")
23 clap::Arg::new("files")
25 .required(true)
24 .required(true)
26 .num_args(1..)
25 .num_args(1..)
27 .value_name("FILE")
26 .value_name("FILE")
28 .value_parser(clap::value_parser!(std::ffi::OsString))
27 .value_parser(clap::value_parser!(std::ffi::OsString))
29 .help("Files to output"),
28 .help("Files to output"),
30 )
29 )
31 .about(HELP_TEXT)
30 .about(HELP_TEXT)
32 }
31 }
33
32
34 #[timed]
33 #[logging_timer::time("trace")]
35 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
36 let cat_enabled_default = true;
35 let cat_enabled_default = true;
37 let cat_enabled = invocation.config.get_option(b"rhg", b"cat")?;
36 let cat_enabled = invocation.config.get_option(b"rhg", b"cat")?;
38 if !cat_enabled.unwrap_or(cat_enabled_default) {
37 if !cat_enabled.unwrap_or(cat_enabled_default) {
39 return Err(CommandError::unsupported(
38 return Err(CommandError::unsupported(
40 "cat is disabled in rhg (enable it with 'rhg.cat = true' \
39 "cat is disabled in rhg (enable it with 'rhg.cat = true' \
41 or enable fallback with 'rhg.on-unsupported = fallback')",
40 or enable fallback with 'rhg.on-unsupported = fallback')",
42 ));
41 ));
43 }
42 }
44
43
45 let rev = invocation.subcommand_args.get_one::<String>("rev");
44 let rev = invocation.subcommand_args.get_one::<String>("rev");
46 let file_args =
45 let file_args =
47 match invocation.subcommand_args.get_many::<OsString>("files") {
46 match invocation.subcommand_args.get_many::<OsString>("files") {
48 Some(files) => files
47 Some(files) => files
49 .filter(|s| !s.is_empty())
48 .filter(|s| !s.is_empty())
50 .map(|s| s.as_os_str())
49 .map(|s| s.as_os_str())
51 .collect(),
50 .collect(),
52 None => vec![],
51 None => vec![],
53 };
52 };
54
53
55 let repo = invocation.repo?;
54 let repo = invocation.repo?;
56 let cwd = hg::utils::current_dir()?;
55 let cwd = hg::utils::current_dir()?;
57 let working_directory = repo.working_directory_path();
56 let working_directory = repo.working_directory_path();
58 let working_directory = cwd.join(working_directory); // Make it absolute
57 let working_directory = cwd.join(working_directory); // Make it absolute
59
58
60 let mut files = vec![];
59 let mut files = vec![];
61 for file in file_args {
60 for file in file_args {
62 if file.as_bytes().starts_with(b"set:") {
61 if file.as_bytes().starts_with(b"set:") {
63 let message = "fileset";
62 let message = "fileset";
64 return Err(CommandError::unsupported(message));
63 return Err(CommandError::unsupported(message));
65 }
64 }
66
65
67 let normalized = cwd.join(&file);
66 let normalized = cwd.join(&file);
68 // TODO: actually normalize `..` path segments etc?
67 // TODO: actually normalize `..` path segments etc?
69 let dotted = normalized.components().any(|c| c.as_os_str() == "..");
68 let dotted = normalized.components().any(|c| c.as_os_str() == "..");
70 if file.as_bytes() == b"." || dotted {
69 if file.as_bytes() == b"." || dotted {
71 let message = "`..` or `.` path segment";
70 let message = "`..` or `.` path segment";
72 return Err(CommandError::unsupported(message));
71 return Err(CommandError::unsupported(message));
73 }
72 }
74 let relative_path = working_directory
73 let relative_path = working_directory
75 .strip_prefix(&cwd)
74 .strip_prefix(&cwd)
76 .unwrap_or(&working_directory);
75 .unwrap_or(&working_directory);
77 let stripped = normalized
76 let stripped = normalized
78 .strip_prefix(&working_directory)
77 .strip_prefix(&working_directory)
79 .map_err(|_| {
78 .map_err(|_| {
80 CommandError::abort(format!(
79 CommandError::abort(format!(
81 "abort: {} not under root '{}'\n(consider using '--cwd {}')",
80 "abort: {} not under root '{}'\n(consider using '--cwd {}')",
82 String::from_utf8_lossy(file.as_bytes()),
81 String::from_utf8_lossy(file.as_bytes()),
83 working_directory.display(),
82 working_directory.display(),
84 relative_path.display(),
83 relative_path.display(),
85 ))
84 ))
86 })?;
85 })?;
87 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
86 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
88 .map_err(|e| CommandError::abort(e.to_string()))?;
87 .map_err(|e| CommandError::abort(e.to_string()))?;
89 files.push(hg_file);
88 files.push(hg_file);
90 }
89 }
91 let files = files.iter().map(|file| file.as_ref()).collect();
90 let files = files.iter().map(|file| file.as_ref()).collect();
92 // TODO probably move this to a util function like `repo.default_rev` or
91 // TODO probably move this to a util function like `repo.default_rev` or
93 // something when it's used somewhere else
92 // something when it's used somewhere else
94 let rev = match rev {
93 let rev = match rev {
95 Some(r) => r.to_string(),
94 Some(r) => r.to_string(),
96 None => format!("{:x}", repo.dirstate_parents()?.p1),
95 None => format!("{:x}", repo.dirstate_parents()?.p1),
97 };
96 };
98
97
99 let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?;
98 let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?;
100 for (_file, contents) in output.results {
99 for (_file, contents) in output.results {
101 invocation.ui.write_stdout(&contents)?;
100 invocation.ui.write_stdout(&contents)?;
102 }
101 }
103 if !output.missing.is_empty() {
102 if !output.missing.is_empty() {
104 let short = format!("{:x}", output.node.short()).into_bytes();
103 let short = format!("{:x}", output.node.short()).into_bytes();
105 for path in &output.missing {
104 for path in &output.missing {
106 invocation.ui.write_stderr(&format_bytes!(
105 invocation.ui.write_stderr(&format_bytes!(
107 b"{}: no such file in rev {}\n",
106 b"{}: no such file in rev {}\n",
108 path.as_bytes(),
107 path.as_bytes(),
109 short
108 short
110 ))?;
109 ))?;
111 }
110 }
112 }
111 }
113 if output.found_any {
112 if output.found_any {
114 Ok(())
113 Ok(())
115 } else {
114 } else {
116 Err(CommandError::Unsuccessful)
115 Err(CommandError::Unsuccessful)
117 }
116 }
118 }
117 }
@@ -1,72 +1,71 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use clap::Arg;
2 use clap::Arg;
3 use clap::ArgGroup;
3 use clap::ArgGroup;
4 use hg::operations::{debug_data, DebugDataKind};
4 use hg::operations::{debug_data, DebugDataKind};
5 use micro_timer::timed;
6
5
7 pub const HELP_TEXT: &str = "
6 pub const HELP_TEXT: &str = "
8 Dump the contents of a data file revision
7 Dump the contents of a data file revision
9 ";
8 ";
10
9
11 pub fn args() -> clap::Command {
10 pub fn args() -> clap::Command {
12 clap::command!("debugdata")
11 clap::command!("debugdata")
13 .arg(
12 .arg(
14 Arg::new("changelog")
13 Arg::new("changelog")
15 .help("open changelog")
14 .help("open changelog")
16 .short('c')
15 .short('c')
17 .action(clap::ArgAction::SetTrue),
16 .action(clap::ArgAction::SetTrue),
18 )
17 )
19 .arg(
18 .arg(
20 Arg::new("manifest")
19 Arg::new("manifest")
21 .help("open manifest")
20 .help("open manifest")
22 .short('m')
21 .short('m')
23 .action(clap::ArgAction::SetTrue),
22 .action(clap::ArgAction::SetTrue),
24 )
23 )
25 .group(
24 .group(
26 ArgGroup::new("revlog")
25 ArgGroup::new("revlog")
27 .args(&["changelog", "manifest"])
26 .args(&["changelog", "manifest"])
28 .required(true),
27 .required(true),
29 )
28 )
30 .arg(
29 .arg(
31 Arg::new("rev")
30 Arg::new("rev")
32 .help("revision")
31 .help("revision")
33 .required(true)
32 .required(true)
34 .value_name("REV"),
33 .value_name("REV"),
35 )
34 )
36 .about(HELP_TEXT)
35 .about(HELP_TEXT)
37 }
36 }
38
37
39 #[timed]
38 #[logging_timer::time("trace")]
40 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
39 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
41 let args = invocation.subcommand_args;
40 let args = invocation.subcommand_args;
42 let rev = args
41 let rev = args
43 .get_one::<String>("rev")
42 .get_one::<String>("rev")
44 .expect("rev should be a required argument");
43 .expect("rev should be a required argument");
45 let kind = match (
44 let kind = match (
46 args.get_one::<bool>("changelog").unwrap(),
45 args.get_one::<bool>("changelog").unwrap(),
47 args.get_one::<bool>("manifest").unwrap(),
46 args.get_one::<bool>("manifest").unwrap(),
48 ) {
47 ) {
49 (true, false) => DebugDataKind::Changelog,
48 (true, false) => DebugDataKind::Changelog,
50 (false, true) => DebugDataKind::Manifest,
49 (false, true) => DebugDataKind::Manifest,
51 (true, true) => {
50 (true, true) => {
52 unreachable!("Should not happen since options are exclusive")
51 unreachable!("Should not happen since options are exclusive")
53 }
52 }
54 (false, false) => {
53 (false, false) => {
55 unreachable!("Should not happen since options are required")
54 unreachable!("Should not happen since options are required")
56 }
55 }
57 };
56 };
58
57
59 let repo = invocation.repo?;
58 let repo = invocation.repo?;
60 if repo.has_narrow() {
59 if repo.has_narrow() {
61 return Err(CommandError::unsupported(
60 return Err(CommandError::unsupported(
62 "support for ellipsis nodes is missing and repo has narrow enabled",
61 "support for ellipsis nodes is missing and repo has narrow enabled",
63 ));
62 ));
64 }
63 }
65 let data = debug_data(repo, rev, kind).map_err(|e| (e, rev.as_ref()))?;
64 let data = debug_data(repo, rev, kind).map_err(|e| (e, rev.as_ref()))?;
66
65
67 let mut stdout = invocation.ui.stdout_buffer();
66 let mut stdout = invocation.ui.stdout_buffer();
68 stdout.write_all(&data)?;
67 stdout.write_all(&data)?;
69 stdout.flush()?;
68 stdout.flush()?;
70
69
71 Ok(())
70 Ok(())
72 }
71 }
General Comments 0
You need to be logged in to leave comments. Login now