##// END OF EJS Templates
rust: fix unsound `OwningDirstateMap`...
Raphaël Gomès -
r50249:dd6b67d5 stable
parent child Browse files
Show More
@@ -1,1153 +1,1213 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"
7 version = "0.11.4"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10
11 [[package]]
6 name = "adler"
12 name = "adler"
7 version = "0.2.3"
13 version = "0.2.3"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
15 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
10
16
11 [[package]]
17 [[package]]
12 name = "aho-corasick"
18 name = "aho-corasick"
13 version = "0.7.15"
19 version = "0.7.15"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
15 checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
21 checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
16 dependencies = [
22 dependencies = [
17 "memchr",
23 "memchr",
18 ]
24 ]
19
25
20 [[package]]
26 [[package]]
27 name = "aliasable"
28 version = "0.1.3"
29 source = "registry+https://github.com/rust-lang/crates.io-index"
30 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
31
32 [[package]]
21 name = "ansi_term"
33 name = "ansi_term"
22 version = "0.11.0"
34 version = "0.11.0"
23 source = "registry+https://github.com/rust-lang/crates.io-index"
35 source = "registry+https://github.com/rust-lang/crates.io-index"
24 checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
36 checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
25 dependencies = [
37 dependencies = [
26 "winapi",
38 "winapi",
27 ]
39 ]
28
40
29 [[package]]
41 [[package]]
30 name = "atty"
42 name = "atty"
31 version = "0.2.14"
43 version = "0.2.14"
32 source = "registry+https://github.com/rust-lang/crates.io-index"
44 source = "registry+https://github.com/rust-lang/crates.io-index"
33 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
45 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
34 dependencies = [
46 dependencies = [
35 "hermit-abi",
47 "hermit-abi",
36 "libc",
48 "libc",
37 "winapi",
49 "winapi",
38 ]
50 ]
39
51
40 [[package]]
52 [[package]]
41 name = "autocfg"
53 name = "autocfg"
42 version = "1.0.1"
54 version = "1.0.1"
43 source = "registry+https://github.com/rust-lang/crates.io-index"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
44 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
56 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
45
57
46 [[package]]
58 [[package]]
47 name = "bitflags"
59 name = "bitflags"
48 version = "1.2.1"
60 version = "1.2.1"
49 source = "registry+https://github.com/rust-lang/crates.io-index"
61 source = "registry+https://github.com/rust-lang/crates.io-index"
50 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
62 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
51
63
52 [[package]]
64 [[package]]
53 name = "bitmaps"
65 name = "bitmaps"
54 version = "2.1.0"
66 version = "2.1.0"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
67 source = "registry+https://github.com/rust-lang/crates.io-index"
56 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
68 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
57 dependencies = [
69 dependencies = [
58 "typenum",
70 "typenum",
59 ]
71 ]
60
72
61 [[package]]
73 [[package]]
62 name = "block-buffer"
74 name = "block-buffer"
63 version = "0.9.0"
75 version = "0.9.0"
64 source = "registry+https://github.com/rust-lang/crates.io-index"
76 source = "registry+https://github.com/rust-lang/crates.io-index"
65 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
77 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
66 dependencies = [
78 dependencies = [
67 "generic-array",
79 "generic-array",
68 ]
80 ]
69
81
70 [[package]]
82 [[package]]
71 name = "byteorder"
83 name = "byteorder"
72 version = "1.3.4"
84 version = "1.3.4"
73 source = "registry+https://github.com/rust-lang/crates.io-index"
85 source = "registry+https://github.com/rust-lang/crates.io-index"
74 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
86 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
75
87
76 [[package]]
88 [[package]]
77 name = "bytes-cast"
89 name = "bytes-cast"
78 version = "0.2.0"
90 version = "0.2.0"
79 source = "registry+https://github.com/rust-lang/crates.io-index"
91 source = "registry+https://github.com/rust-lang/crates.io-index"
80 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
92 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
81 dependencies = [
93 dependencies = [
82 "bytes-cast-derive",
94 "bytes-cast-derive",
83 ]
95 ]
84
96
85 [[package]]
97 [[package]]
86 name = "bytes-cast-derive"
98 name = "bytes-cast-derive"
87 version = "0.1.0"
99 version = "0.1.0"
88 source = "registry+https://github.com/rust-lang/crates.io-index"
100 source = "registry+https://github.com/rust-lang/crates.io-index"
89 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
101 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
90 dependencies = [
102 dependencies = [
91 "proc-macro2",
103 "proc-macro2",
92 "quote",
104 "quote",
93 "syn",
105 "syn",
94 ]
106 ]
95
107
96 [[package]]
108 [[package]]
97 name = "cc"
109 name = "cc"
98 version = "1.0.66"
110 version = "1.0.66"
99 source = "registry+https://github.com/rust-lang/crates.io-index"
111 source = "registry+https://github.com/rust-lang/crates.io-index"
100 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
112 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
101 dependencies = [
113 dependencies = [
102 "jobserver",
114 "jobserver",
103 ]
115 ]
104
116
105 [[package]]
117 [[package]]
106 name = "cfg-if"
118 name = "cfg-if"
107 version = "0.1.10"
119 version = "0.1.10"
108 source = "registry+https://github.com/rust-lang/crates.io-index"
120 source = "registry+https://github.com/rust-lang/crates.io-index"
109 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
121 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
110
122
111 [[package]]
123 [[package]]
112 name = "cfg-if"
124 name = "cfg-if"
113 version = "1.0.0"
125 version = "1.0.0"
114 source = "registry+https://github.com/rust-lang/crates.io-index"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
115 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
127 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
116
128
117 [[package]]
129 [[package]]
118 name = "chrono"
130 name = "chrono"
119 version = "0.4.19"
131 version = "0.4.19"
120 source = "registry+https://github.com/rust-lang/crates.io-index"
132 source = "registry+https://github.com/rust-lang/crates.io-index"
121 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
133 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
122 dependencies = [
134 dependencies = [
123 "libc",
135 "libc",
124 "num-integer",
136 "num-integer",
125 "num-traits",
137 "num-traits",
126 "time",
138 "time",
127 "winapi",
139 "winapi",
128 ]
140 ]
129
141
130 [[package]]
142 [[package]]
131 name = "clap"
143 name = "clap"
132 version = "2.33.3"
144 version = "2.33.3"
133 source = "registry+https://github.com/rust-lang/crates.io-index"
145 source = "registry+https://github.com/rust-lang/crates.io-index"
134 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
146 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
135 dependencies = [
147 dependencies = [
136 "ansi_term",
148 "ansi_term",
137 "atty",
149 "atty",
138 "bitflags",
150 "bitflags",
139 "strsim",
151 "strsim",
140 "textwrap",
152 "textwrap",
141 "unicode-width",
153 "unicode-width",
142 "vec_map",
154 "vec_map",
143 ]
155 ]
144
156
145 [[package]]
157 [[package]]
146 name = "const_fn"
158 name = "const_fn"
147 version = "0.4.4"
159 version = "0.4.4"
148 source = "registry+https://github.com/rust-lang/crates.io-index"
160 source = "registry+https://github.com/rust-lang/crates.io-index"
149 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
161 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
150
162
151 [[package]]
163 [[package]]
152 name = "cpufeatures"
164 name = "cpufeatures"
153 version = "0.1.4"
165 version = "0.1.4"
154 source = "registry+https://github.com/rust-lang/crates.io-index"
166 source = "registry+https://github.com/rust-lang/crates.io-index"
155 checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
167 checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
156 dependencies = [
168 dependencies = [
157 "libc",
169 "libc",
158 ]
170 ]
159
171
160 [[package]]
172 [[package]]
161 name = "cpython"
173 name = "cpython"
162 version = "0.7.0"
174 version = "0.7.0"
163 source = "registry+https://github.com/rust-lang/crates.io-index"
175 source = "registry+https://github.com/rust-lang/crates.io-index"
164 checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664"
176 checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664"
165 dependencies = [
177 dependencies = [
166 "libc",
178 "libc",
167 "num-traits",
179 "num-traits",
168 "paste",
180 "paste",
169 "python27-sys",
181 "python27-sys",
170 "python3-sys",
182 "python3-sys",
171 ]
183 ]
172
184
173 [[package]]
185 [[package]]
174 name = "crc32fast"
186 name = "crc32fast"
175 version = "1.2.1"
187 version = "1.2.1"
176 source = "registry+https://github.com/rust-lang/crates.io-index"
188 source = "registry+https://github.com/rust-lang/crates.io-index"
177 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
189 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
178 dependencies = [
190 dependencies = [
179 "cfg-if 1.0.0",
191 "cfg-if 1.0.0",
180 ]
192 ]
181
193
182 [[package]]
194 [[package]]
183 name = "crossbeam-channel"
195 name = "crossbeam-channel"
184 version = "0.4.4"
196 version = "0.4.4"
185 source = "registry+https://github.com/rust-lang/crates.io-index"
197 source = "registry+https://github.com/rust-lang/crates.io-index"
186 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
198 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
187 dependencies = [
199 dependencies = [
188 "crossbeam-utils 0.7.2",
200 "crossbeam-utils 0.7.2",
189 "maybe-uninit",
201 "maybe-uninit",
190 ]
202 ]
191
203
192 [[package]]
204 [[package]]
193 name = "crossbeam-channel"
205 name = "crossbeam-channel"
194 version = "0.5.0"
206 version = "0.5.0"
195 source = "registry+https://github.com/rust-lang/crates.io-index"
207 source = "registry+https://github.com/rust-lang/crates.io-index"
196 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
208 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
197 dependencies = [
209 dependencies = [
198 "cfg-if 1.0.0",
210 "cfg-if 1.0.0",
199 "crossbeam-utils 0.8.1",
211 "crossbeam-utils 0.8.1",
200 ]
212 ]
201
213
202 [[package]]
214 [[package]]
203 name = "crossbeam-deque"
215 name = "crossbeam-deque"
204 version = "0.8.0"
216 version = "0.8.0"
205 source = "registry+https://github.com/rust-lang/crates.io-index"
217 source = "registry+https://github.com/rust-lang/crates.io-index"
206 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
218 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
207 dependencies = [
219 dependencies = [
208 "cfg-if 1.0.0",
220 "cfg-if 1.0.0",
209 "crossbeam-epoch",
221 "crossbeam-epoch",
210 "crossbeam-utils 0.8.1",
222 "crossbeam-utils 0.8.1",
211 ]
223 ]
212
224
213 [[package]]
225 [[package]]
214 name = "crossbeam-epoch"
226 name = "crossbeam-epoch"
215 version = "0.9.1"
227 version = "0.9.1"
216 source = "registry+https://github.com/rust-lang/crates.io-index"
228 source = "registry+https://github.com/rust-lang/crates.io-index"
217 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
229 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
218 dependencies = [
230 dependencies = [
219 "cfg-if 1.0.0",
231 "cfg-if 1.0.0",
220 "const_fn",
232 "const_fn",
221 "crossbeam-utils 0.8.1",
233 "crossbeam-utils 0.8.1",
222 "lazy_static",
234 "lazy_static",
223 "memoffset",
235 "memoffset",
224 "scopeguard",
236 "scopeguard",
225 ]
237 ]
226
238
227 [[package]]
239 [[package]]
228 name = "crossbeam-utils"
240 name = "crossbeam-utils"
229 version = "0.7.2"
241 version = "0.7.2"
230 source = "registry+https://github.com/rust-lang/crates.io-index"
242 source = "registry+https://github.com/rust-lang/crates.io-index"
231 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
243 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
232 dependencies = [
244 dependencies = [
233 "autocfg",
245 "autocfg",
234 "cfg-if 0.1.10",
246 "cfg-if 0.1.10",
235 "lazy_static",
247 "lazy_static",
236 ]
248 ]
237
249
238 [[package]]
250 [[package]]
239 name = "crossbeam-utils"
251 name = "crossbeam-utils"
240 version = "0.8.1"
252 version = "0.8.1"
241 source = "registry+https://github.com/rust-lang/crates.io-index"
253 source = "registry+https://github.com/rust-lang/crates.io-index"
242 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
254 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
243 dependencies = [
255 dependencies = [
244 "autocfg",
256 "autocfg",
245 "cfg-if 1.0.0",
257 "cfg-if 1.0.0",
246 "lazy_static",
258 "lazy_static",
247 ]
259 ]
248
260
249 [[package]]
261 [[package]]
250 name = "ctor"
262 name = "ctor"
251 version = "0.1.16"
263 version = "0.1.16"
252 source = "registry+https://github.com/rust-lang/crates.io-index"
264 source = "registry+https://github.com/rust-lang/crates.io-index"
253 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
265 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
254 dependencies = [
266 dependencies = [
255 "quote",
267 "quote",
256 "syn",
268 "syn",
257 ]
269 ]
258
270
259 [[package]]
271 [[package]]
260 name = "derive_more"
272 name = "derive_more"
261 version = "0.99.11"
273 version = "0.99.11"
262 source = "registry+https://github.com/rust-lang/crates.io-index"
274 source = "registry+https://github.com/rust-lang/crates.io-index"
263 checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
275 checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
264 dependencies = [
276 dependencies = [
265 "proc-macro2",
277 "proc-macro2",
266 "quote",
278 "quote",
267 "syn",
279 "syn",
268 ]
280 ]
269
281
270 [[package]]
282 [[package]]
271 name = "difference"
283 name = "difference"
272 version = "2.0.0"
284 version = "2.0.0"
273 source = "registry+https://github.com/rust-lang/crates.io-index"
285 source = "registry+https://github.com/rust-lang/crates.io-index"
274 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
286 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
275
287
276 [[package]]
288 [[package]]
277 name = "digest"
289 name = "digest"
278 version = "0.9.0"
290 version = "0.9.0"
279 source = "registry+https://github.com/rust-lang/crates.io-index"
291 source = "registry+https://github.com/rust-lang/crates.io-index"
280 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
292 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
281 dependencies = [
293 dependencies = [
282 "generic-array",
294 "generic-array",
283 ]
295 ]
284
296
285 [[package]]
297 [[package]]
286 name = "either"
298 name = "either"
287 version = "1.6.1"
299 version = "1.6.1"
288 source = "registry+https://github.com/rust-lang/crates.io-index"
300 source = "registry+https://github.com/rust-lang/crates.io-index"
289 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
301 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
290
302
291 [[package]]
303 [[package]]
292 name = "env_logger"
304 name = "env_logger"
293 version = "0.7.1"
305 version = "0.7.1"
294 source = "registry+https://github.com/rust-lang/crates.io-index"
306 source = "registry+https://github.com/rust-lang/crates.io-index"
295 checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
307 checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
296 dependencies = [
308 dependencies = [
297 "atty",
309 "atty",
298 "humantime",
310 "humantime",
299 "log",
311 "log",
300 "regex",
312 "regex",
301 "termcolor",
313 "termcolor",
302 ]
314 ]
303
315
304 [[package]]
316 [[package]]
305 name = "flate2"
317 name = "flate2"
306 version = "1.0.19"
318 version = "1.0.19"
307 source = "registry+https://github.com/rust-lang/crates.io-index"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
308 checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
320 checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
309 dependencies = [
321 dependencies = [
310 "cfg-if 1.0.0",
322 "cfg-if 1.0.0",
311 "crc32fast",
323 "crc32fast",
312 "libc",
324 "libc",
313 "libz-sys",
325 "libz-sys",
314 "miniz_oxide",
326 "miniz_oxide",
315 ]
327 ]
316
328
317 [[package]]
329 [[package]]
318 name = "format-bytes"
330 name = "format-bytes"
319 version = "0.3.0"
331 version = "0.3.0"
320 source = "registry+https://github.com/rust-lang/crates.io-index"
332 source = "registry+https://github.com/rust-lang/crates.io-index"
321 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
333 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
322 dependencies = [
334 dependencies = [
323 "format-bytes-macros",
335 "format-bytes-macros",
324 ]
336 ]
325
337
326 [[package]]
338 [[package]]
327 name = "format-bytes-macros"
339 name = "format-bytes-macros"
328 version = "0.4.0"
340 version = "0.4.0"
329 source = "registry+https://github.com/rust-lang/crates.io-index"
341 source = "registry+https://github.com/rust-lang/crates.io-index"
330 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
342 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
331 dependencies = [
343 dependencies = [
332 "proc-macro2",
344 "proc-macro2",
333 "quote",
345 "quote",
334 "syn",
346 "syn",
335 ]
347 ]
336
348
337 [[package]]
349 [[package]]
338 name = "generic-array"
350 name = "generic-array"
339 version = "0.14.4"
351 version = "0.14.4"
340 source = "registry+https://github.com/rust-lang/crates.io-index"
352 source = "registry+https://github.com/rust-lang/crates.io-index"
341 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
353 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
342 dependencies = [
354 dependencies = [
343 "typenum",
355 "typenum",
344 "version_check",
356 "version_check",
345 ]
357 ]
346
358
347 [[package]]
359 [[package]]
348 name = "getrandom"
360 name = "getrandom"
349 version = "0.1.15"
361 version = "0.1.15"
350 source = "registry+https://github.com/rust-lang/crates.io-index"
362 source = "registry+https://github.com/rust-lang/crates.io-index"
351 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
363 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
352 dependencies = [
364 dependencies = [
353 "cfg-if 0.1.10",
365 "cfg-if 0.1.10",
354 "libc",
366 "libc",
355 "wasi 0.9.0+wasi-snapshot-preview1",
367 "wasi 0.9.0+wasi-snapshot-preview1",
356 ]
368 ]
357
369
358 [[package]]
370 [[package]]
359 name = "getrandom"
371 name = "getrandom"
360 version = "0.2.4"
372 version = "0.2.4"
361 source = "registry+https://github.com/rust-lang/crates.io-index"
373 source = "registry+https://github.com/rust-lang/crates.io-index"
362 checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
374 checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
363 dependencies = [
375 dependencies = [
364 "cfg-if 1.0.0",
376 "cfg-if 1.0.0",
365 "libc",
377 "libc",
366 "wasi 0.10.0+wasi-snapshot-preview1",
378 "wasi 0.10.0+wasi-snapshot-preview1",
367 ]
379 ]
368
380
369 [[package]]
381 [[package]]
370 name = "glob"
382 name = "glob"
371 version = "0.3.0"
383 version = "0.3.0"
372 source = "registry+https://github.com/rust-lang/crates.io-index"
384 source = "registry+https://github.com/rust-lang/crates.io-index"
373 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
385 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
374
386
375 [[package]]
387 [[package]]
376 name = "hermit-abi"
388 name = "hermit-abi"
377 version = "0.1.17"
389 version = "0.1.17"
378 source = "registry+https://github.com/rust-lang/crates.io-index"
390 source = "registry+https://github.com/rust-lang/crates.io-index"
379 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
391 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
380 dependencies = [
392 dependencies = [
381 "libc",
393 "libc",
382 ]
394 ]
383
395
384 [[package]]
396 [[package]]
385 name = "hex"
397 name = "hex"
386 version = "0.4.3"
398 version = "0.4.3"
387 source = "registry+https://github.com/rust-lang/crates.io-index"
399 source = "registry+https://github.com/rust-lang/crates.io-index"
388 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
400 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
389
401
390 [[package]]
402 [[package]]
391 name = "hg-core"
403 name = "hg-core"
392 version = "0.1.0"
404 version = "0.1.0"
393 dependencies = [
405 dependencies = [
394 "bitflags",
406 "bitflags",
395 "byteorder",
407 "byteorder",
396 "bytes-cast",
408 "bytes-cast",
397 "clap",
409 "clap",
398 "crossbeam-channel 0.4.4",
410 "crossbeam-channel 0.4.4",
399 "derive_more",
411 "derive_more",
400 "flate2",
412 "flate2",
401 "format-bytes",
413 "format-bytes",
402 "home",
414 "home",
403 "im-rc",
415 "im-rc",
404 "itertools",
416 "itertools",
405 "lazy_static",
417 "lazy_static",
406 "libc",
418 "libc",
407 "log",
419 "log",
408 "memmap2",
420 "memmap2",
409 "micro-timer",
421 "micro-timer",
422 "ouroboros",
410 "pretty_assertions",
423 "pretty_assertions",
411 "rand 0.8.4",
424 "rand 0.8.4",
412 "rand_distr",
425 "rand_distr",
413 "rand_pcg",
426 "rand_pcg",
414 "rayon",
427 "rayon",
415 "regex",
428 "regex",
416 "same-file",
429 "same-file",
417 "sha-1",
430 "sha-1",
418 "stable_deref_trait",
419 "tempfile",
431 "tempfile",
420 "twox-hash",
432 "twox-hash",
421 "zstd",
433 "zstd",
422 ]
434 ]
423
435
424 [[package]]
436 [[package]]
425 name = "hg-cpython"
437 name = "hg-cpython"
426 version = "0.1.0"
438 version = "0.1.0"
427 dependencies = [
439 dependencies = [
428 "cpython",
440 "cpython",
429 "crossbeam-channel 0.4.4",
441 "crossbeam-channel 0.4.4",
430 "env_logger",
442 "env_logger",
431 "hg-core",
443 "hg-core",
432 "libc",
444 "libc",
433 "log",
445 "log",
434 "stable_deref_trait",
446 "stable_deref_trait",
435 "vcsgraph",
447 "vcsgraph",
436 ]
448 ]
437
449
438 [[package]]
450 [[package]]
439 name = "home"
451 name = "home"
440 version = "0.5.3"
452 version = "0.5.3"
441 source = "registry+https://github.com/rust-lang/crates.io-index"
453 source = "registry+https://github.com/rust-lang/crates.io-index"
442 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
454 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
443 dependencies = [
455 dependencies = [
444 "winapi",
456 "winapi",
445 ]
457 ]
446
458
447 [[package]]
459 [[package]]
448 name = "humantime"
460 name = "humantime"
449 version = "1.3.0"
461 version = "1.3.0"
450 source = "registry+https://github.com/rust-lang/crates.io-index"
462 source = "registry+https://github.com/rust-lang/crates.io-index"
451 checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
463 checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
452 dependencies = [
464 dependencies = [
453 "quick-error",
465 "quick-error",
454 ]
466 ]
455
467
456 [[package]]
468 [[package]]
457 name = "im-rc"
469 name = "im-rc"
458 version = "15.0.0"
470 version = "15.0.0"
459 source = "registry+https://github.com/rust-lang/crates.io-index"
471 source = "registry+https://github.com/rust-lang/crates.io-index"
460 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
472 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
461 dependencies = [
473 dependencies = [
462 "bitmaps",
474 "bitmaps",
463 "rand_core 0.5.1",
475 "rand_core 0.5.1",
464 "rand_xoshiro",
476 "rand_xoshiro",
465 "sized-chunks",
477 "sized-chunks",
466 "typenum",
478 "typenum",
467 "version_check",
479 "version_check",
468 ]
480 ]
469
481
470 [[package]]
482 [[package]]
471 name = "itertools"
483 name = "itertools"
472 version = "0.9.0"
484 version = "0.9.0"
473 source = "registry+https://github.com/rust-lang/crates.io-index"
485 source = "registry+https://github.com/rust-lang/crates.io-index"
474 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
486 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
475 dependencies = [
487 dependencies = [
476 "either",
488 "either",
477 ]
489 ]
478
490
479 [[package]]
491 [[package]]
480 name = "jobserver"
492 name = "jobserver"
481 version = "0.1.21"
493 version = "0.1.21"
482 source = "registry+https://github.com/rust-lang/crates.io-index"
494 source = "registry+https://github.com/rust-lang/crates.io-index"
483 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
495 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
484 dependencies = [
496 dependencies = [
485 "libc",
497 "libc",
486 ]
498 ]
487
499
488 [[package]]
500 [[package]]
489 name = "lazy_static"
501 name = "lazy_static"
490 version = "1.4.0"
502 version = "1.4.0"
491 source = "registry+https://github.com/rust-lang/crates.io-index"
503 source = "registry+https://github.com/rust-lang/crates.io-index"
492 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
504 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
493
505
494 [[package]]
506 [[package]]
495 name = "libc"
507 name = "libc"
496 version = "0.2.81"
508 version = "0.2.81"
497 source = "registry+https://github.com/rust-lang/crates.io-index"
509 source = "registry+https://github.com/rust-lang/crates.io-index"
498 checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
510 checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
499
511
500 [[package]]
512 [[package]]
501 name = "libm"
513 name = "libm"
502 version = "0.2.1"
514 version = "0.2.1"
503 source = "registry+https://github.com/rust-lang/crates.io-index"
515 source = "registry+https://github.com/rust-lang/crates.io-index"
504 checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
516 checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
505
517
506 [[package]]
518 [[package]]
507 name = "libz-sys"
519 name = "libz-sys"
508 version = "1.1.2"
520 version = "1.1.2"
509 source = "registry+https://github.com/rust-lang/crates.io-index"
521 source = "registry+https://github.com/rust-lang/crates.io-index"
510 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
522 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
511 dependencies = [
523 dependencies = [
512 "cc",
524 "cc",
513 "pkg-config",
525 "pkg-config",
514 "vcpkg",
526 "vcpkg",
515 ]
527 ]
516
528
517 [[package]]
529 [[package]]
518 name = "log"
530 name = "log"
519 version = "0.4.11"
531 version = "0.4.11"
520 source = "registry+https://github.com/rust-lang/crates.io-index"
532 source = "registry+https://github.com/rust-lang/crates.io-index"
521 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
533 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
522 dependencies = [
534 dependencies = [
523 "cfg-if 0.1.10",
535 "cfg-if 0.1.10",
524 ]
536 ]
525
537
526 [[package]]
538 [[package]]
527 name = "maybe-uninit"
539 name = "maybe-uninit"
528 version = "2.0.0"
540 version = "2.0.0"
529 source = "registry+https://github.com/rust-lang/crates.io-index"
541 source = "registry+https://github.com/rust-lang/crates.io-index"
530 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
542 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
531
543
532 [[package]]
544 [[package]]
533 name = "memchr"
545 name = "memchr"
534 version = "2.3.4"
546 version = "2.3.4"
535 source = "registry+https://github.com/rust-lang/crates.io-index"
547 source = "registry+https://github.com/rust-lang/crates.io-index"
536 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
548 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
537
549
538 [[package]]
550 [[package]]
539 name = "memmap2"
551 name = "memmap2"
540 version = "0.4.0"
552 version = "0.4.0"
541 source = "registry+https://github.com/rust-lang/crates.io-index"
553 source = "registry+https://github.com/rust-lang/crates.io-index"
542 checksum = "de5d3112c080d58ce560081baeaab7e1e864ca21795ddbf533d5b1842bb1ecf8"
554 checksum = "de5d3112c080d58ce560081baeaab7e1e864ca21795ddbf533d5b1842bb1ecf8"
543 dependencies = [
555 dependencies = [
544 "libc",
556 "libc",
545 "stable_deref_trait",
557 "stable_deref_trait",
546 ]
558 ]
547
559
548 [[package]]
560 [[package]]
549 name = "memoffset"
561 name = "memoffset"
550 version = "0.6.1"
562 version = "0.6.1"
551 source = "registry+https://github.com/rust-lang/crates.io-index"
563 source = "registry+https://github.com/rust-lang/crates.io-index"
552 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
564 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
553 dependencies = [
565 dependencies = [
554 "autocfg",
566 "autocfg",
555 ]
567 ]
556
568
557 [[package]]
569 [[package]]
558 name = "micro-timer"
570 name = "micro-timer"
559 version = "0.3.1"
571 version = "0.3.1"
560 source = "registry+https://github.com/rust-lang/crates.io-index"
572 source = "registry+https://github.com/rust-lang/crates.io-index"
561 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
573 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
562 dependencies = [
574 dependencies = [
563 "micro-timer-macros",
575 "micro-timer-macros",
564 "scopeguard",
576 "scopeguard",
565 ]
577 ]
566
578
567 [[package]]
579 [[package]]
568 name = "micro-timer-macros"
580 name = "micro-timer-macros"
569 version = "0.3.1"
581 version = "0.3.1"
570 source = "registry+https://github.com/rust-lang/crates.io-index"
582 source = "registry+https://github.com/rust-lang/crates.io-index"
571 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
583 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
572 dependencies = [
584 dependencies = [
573 "proc-macro2",
585 "proc-macro2",
574 "quote",
586 "quote",
575 "scopeguard",
587 "scopeguard",
576 "syn",
588 "syn",
577 ]
589 ]
578
590
579 [[package]]
591 [[package]]
580 name = "miniz_oxide"
592 name = "miniz_oxide"
581 version = "0.4.3"
593 version = "0.4.3"
582 source = "registry+https://github.com/rust-lang/crates.io-index"
594 source = "registry+https://github.com/rust-lang/crates.io-index"
583 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
595 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
584 dependencies = [
596 dependencies = [
585 "adler",
597 "adler",
586 "autocfg",
598 "autocfg",
587 ]
599 ]
588
600
589 [[package]]
601 [[package]]
590 name = "num-integer"
602 name = "num-integer"
591 version = "0.1.44"
603 version = "0.1.44"
592 source = "registry+https://github.com/rust-lang/crates.io-index"
604 source = "registry+https://github.com/rust-lang/crates.io-index"
593 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
605 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
594 dependencies = [
606 dependencies = [
595 "autocfg",
607 "autocfg",
596 "num-traits",
608 "num-traits",
597 ]
609 ]
598
610
599 [[package]]
611 [[package]]
600 name = "num-traits"
612 name = "num-traits"
601 version = "0.2.14"
613 version = "0.2.14"
602 source = "registry+https://github.com/rust-lang/crates.io-index"
614 source = "registry+https://github.com/rust-lang/crates.io-index"
603 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
615 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
604 dependencies = [
616 dependencies = [
605 "autocfg",
617 "autocfg",
606 "libm",
618 "libm",
607 ]
619 ]
608
620
609 [[package]]
621 [[package]]
610 name = "num_cpus"
622 name = "num_cpus"
611 version = "1.13.0"
623 version = "1.13.0"
612 source = "registry+https://github.com/rust-lang/crates.io-index"
624 source = "registry+https://github.com/rust-lang/crates.io-index"
613 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
625 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
614 dependencies = [
626 dependencies = [
615 "hermit-abi",
627 "hermit-abi",
616 "libc",
628 "libc",
617 ]
629 ]
618
630
619 [[package]]
631 [[package]]
620 name = "opaque-debug"
632 name = "opaque-debug"
621 version = "0.3.0"
633 version = "0.3.0"
622 source = "registry+https://github.com/rust-lang/crates.io-index"
634 source = "registry+https://github.com/rust-lang/crates.io-index"
623 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
635 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
624
636
625 [[package]]
637 [[package]]
638 name = "ouroboros"
639 version = "0.15.0"
640 source = "registry+https://github.com/rust-lang/crates.io-index"
641 checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf"
642 dependencies = [
643 "aliasable",
644 "ouroboros_macro",
645 "stable_deref_trait",
646 ]
647
648 [[package]]
649 name = "ouroboros_macro"
650 version = "0.15.0"
651 source = "registry+https://github.com/rust-lang/crates.io-index"
652 checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408"
653 dependencies = [
654 "Inflector",
655 "proc-macro-error",
656 "proc-macro2",
657 "quote",
658 "syn",
659 ]
660
661 [[package]]
626 name = "output_vt100"
662 name = "output_vt100"
627 version = "0.1.2"
663 version = "0.1.2"
628 source = "registry+https://github.com/rust-lang/crates.io-index"
664 source = "registry+https://github.com/rust-lang/crates.io-index"
629 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
665 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
630 dependencies = [
666 dependencies = [
631 "winapi",
667 "winapi",
632 ]
668 ]
633
669
634 [[package]]
670 [[package]]
635 name = "paste"
671 name = "paste"
636 version = "1.0.5"
672 version = "1.0.5"
637 source = "registry+https://github.com/rust-lang/crates.io-index"
673 source = "registry+https://github.com/rust-lang/crates.io-index"
638 checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
674 checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
639
675
640 [[package]]
676 [[package]]
641 name = "pkg-config"
677 name = "pkg-config"
642 version = "0.3.19"
678 version = "0.3.19"
643 source = "registry+https://github.com/rust-lang/crates.io-index"
679 source = "registry+https://github.com/rust-lang/crates.io-index"
644 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
680 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
645
681
646 [[package]]
682 [[package]]
647 name = "ppv-lite86"
683 name = "ppv-lite86"
648 version = "0.2.10"
684 version = "0.2.10"
649 source = "registry+https://github.com/rust-lang/crates.io-index"
685 source = "registry+https://github.com/rust-lang/crates.io-index"
650 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
686 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
651
687
652 [[package]]
688 [[package]]
653 name = "pretty_assertions"
689 name = "pretty_assertions"
654 version = "0.6.1"
690 version = "0.6.1"
655 source = "registry+https://github.com/rust-lang/crates.io-index"
691 source = "registry+https://github.com/rust-lang/crates.io-index"
656 checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
692 checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
657 dependencies = [
693 dependencies = [
658 "ansi_term",
694 "ansi_term",
659 "ctor",
695 "ctor",
660 "difference",
696 "difference",
661 "output_vt100",
697 "output_vt100",
662 ]
698 ]
663
699
664 [[package]]
700 [[package]]
701 name = "proc-macro-error"
702 version = "1.0.4"
703 source = "registry+https://github.com/rust-lang/crates.io-index"
704 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
705 dependencies = [
706 "proc-macro-error-attr",
707 "proc-macro2",
708 "quote",
709 "syn",
710 "version_check",
711 ]
712
713 [[package]]
714 name = "proc-macro-error-attr"
715 version = "1.0.4"
716 source = "registry+https://github.com/rust-lang/crates.io-index"
717 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
718 dependencies = [
719 "proc-macro2",
720 "quote",
721 "version_check",
722 ]
723
724 [[package]]
665 name = "proc-macro2"
725 name = "proc-macro2"
666 version = "1.0.24"
726 version = "1.0.24"
667 source = "registry+https://github.com/rust-lang/crates.io-index"
727 source = "registry+https://github.com/rust-lang/crates.io-index"
668 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
728 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
669 dependencies = [
729 dependencies = [
670 "unicode-xid",
730 "unicode-xid",
671 ]
731 ]
672
732
673 [[package]]
733 [[package]]
674 name = "python27-sys"
734 name = "python27-sys"
675 version = "0.7.0"
735 version = "0.7.0"
676 source = "registry+https://github.com/rust-lang/crates.io-index"
736 source = "registry+https://github.com/rust-lang/crates.io-index"
677 checksum = "94670354e264300dde81a5864cbb6bfc9d56ac3dcf3a278c32cb52f816f4dfd1"
737 checksum = "94670354e264300dde81a5864cbb6bfc9d56ac3dcf3a278c32cb52f816f4dfd1"
678 dependencies = [
738 dependencies = [
679 "libc",
739 "libc",
680 "regex",
740 "regex",
681 ]
741 ]
682
742
683 [[package]]
743 [[package]]
684 name = "python3-sys"
744 name = "python3-sys"
685 version = "0.7.0"
745 version = "0.7.0"
686 source = "registry+https://github.com/rust-lang/crates.io-index"
746 source = "registry+https://github.com/rust-lang/crates.io-index"
687 checksum = "b18b32e64c103d5045f44644d7ddddd65336f7a0521f6fde673240a9ecceb77e"
747 checksum = "b18b32e64c103d5045f44644d7ddddd65336f7a0521f6fde673240a9ecceb77e"
688 dependencies = [
748 dependencies = [
689 "libc",
749 "libc",
690 "regex",
750 "regex",
691 ]
751 ]
692
752
693 [[package]]
753 [[package]]
694 name = "quick-error"
754 name = "quick-error"
695 version = "1.2.3"
755 version = "1.2.3"
696 source = "registry+https://github.com/rust-lang/crates.io-index"
756 source = "registry+https://github.com/rust-lang/crates.io-index"
697 checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
757 checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
698
758
699 [[package]]
759 [[package]]
700 name = "quote"
760 name = "quote"
701 version = "1.0.7"
761 version = "1.0.7"
702 source = "registry+https://github.com/rust-lang/crates.io-index"
762 source = "registry+https://github.com/rust-lang/crates.io-index"
703 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
763 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
704 dependencies = [
764 dependencies = [
705 "proc-macro2",
765 "proc-macro2",
706 ]
766 ]
707
767
708 [[package]]
768 [[package]]
709 name = "rand"
769 name = "rand"
710 version = "0.7.3"
770 version = "0.7.3"
711 source = "registry+https://github.com/rust-lang/crates.io-index"
771 source = "registry+https://github.com/rust-lang/crates.io-index"
712 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
772 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
713 dependencies = [
773 dependencies = [
714 "getrandom 0.1.15",
774 "getrandom 0.1.15",
715 "libc",
775 "libc",
716 "rand_chacha 0.2.2",
776 "rand_chacha 0.2.2",
717 "rand_core 0.5.1",
777 "rand_core 0.5.1",
718 "rand_hc 0.2.0",
778 "rand_hc 0.2.0",
719 ]
779 ]
720
780
721 [[package]]
781 [[package]]
722 name = "rand"
782 name = "rand"
723 version = "0.8.4"
783 version = "0.8.4"
724 source = "registry+https://github.com/rust-lang/crates.io-index"
784 source = "registry+https://github.com/rust-lang/crates.io-index"
725 checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
785 checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
726 dependencies = [
786 dependencies = [
727 "libc",
787 "libc",
728 "rand_chacha 0.3.1",
788 "rand_chacha 0.3.1",
729 "rand_core 0.6.3",
789 "rand_core 0.6.3",
730 "rand_hc 0.3.1",
790 "rand_hc 0.3.1",
731 ]
791 ]
732
792
733 [[package]]
793 [[package]]
734 name = "rand_chacha"
794 name = "rand_chacha"
735 version = "0.2.2"
795 version = "0.2.2"
736 source = "registry+https://github.com/rust-lang/crates.io-index"
796 source = "registry+https://github.com/rust-lang/crates.io-index"
737 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
797 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
738 dependencies = [
798 dependencies = [
739 "ppv-lite86",
799 "ppv-lite86",
740 "rand_core 0.5.1",
800 "rand_core 0.5.1",
741 ]
801 ]
742
802
743 [[package]]
803 [[package]]
744 name = "rand_chacha"
804 name = "rand_chacha"
745 version = "0.3.1"
805 version = "0.3.1"
746 source = "registry+https://github.com/rust-lang/crates.io-index"
806 source = "registry+https://github.com/rust-lang/crates.io-index"
747 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
807 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
748 dependencies = [
808 dependencies = [
749 "ppv-lite86",
809 "ppv-lite86",
750 "rand_core 0.6.3",
810 "rand_core 0.6.3",
751 ]
811 ]
752
812
753 [[package]]
813 [[package]]
754 name = "rand_core"
814 name = "rand_core"
755 version = "0.5.1"
815 version = "0.5.1"
756 source = "registry+https://github.com/rust-lang/crates.io-index"
816 source = "registry+https://github.com/rust-lang/crates.io-index"
757 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
817 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
758 dependencies = [
818 dependencies = [
759 "getrandom 0.1.15",
819 "getrandom 0.1.15",
760 ]
820 ]
761
821
762 [[package]]
822 [[package]]
763 name = "rand_core"
823 name = "rand_core"
764 version = "0.6.3"
824 version = "0.6.3"
765 source = "registry+https://github.com/rust-lang/crates.io-index"
825 source = "registry+https://github.com/rust-lang/crates.io-index"
766 checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
826 checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
767 dependencies = [
827 dependencies = [
768 "getrandom 0.2.4",
828 "getrandom 0.2.4",
769 ]
829 ]
770
830
771 [[package]]
831 [[package]]
772 name = "rand_distr"
832 name = "rand_distr"
773 version = "0.4.2"
833 version = "0.4.2"
774 source = "registry+https://github.com/rust-lang/crates.io-index"
834 source = "registry+https://github.com/rust-lang/crates.io-index"
775 checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f"
835 checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f"
776 dependencies = [
836 dependencies = [
777 "num-traits",
837 "num-traits",
778 "rand 0.8.4",
838 "rand 0.8.4",
779 ]
839 ]
780
840
781 [[package]]
841 [[package]]
782 name = "rand_hc"
842 name = "rand_hc"
783 version = "0.2.0"
843 version = "0.2.0"
784 source = "registry+https://github.com/rust-lang/crates.io-index"
844 source = "registry+https://github.com/rust-lang/crates.io-index"
785 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
845 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
786 dependencies = [
846 dependencies = [
787 "rand_core 0.5.1",
847 "rand_core 0.5.1",
788 ]
848 ]
789
849
790 [[package]]
850 [[package]]
791 name = "rand_hc"
851 name = "rand_hc"
792 version = "0.3.1"
852 version = "0.3.1"
793 source = "registry+https://github.com/rust-lang/crates.io-index"
853 source = "registry+https://github.com/rust-lang/crates.io-index"
794 checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
854 checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
795 dependencies = [
855 dependencies = [
796 "rand_core 0.6.3",
856 "rand_core 0.6.3",
797 ]
857 ]
798
858
799 [[package]]
859 [[package]]
800 name = "rand_pcg"
860 name = "rand_pcg"
801 version = "0.3.1"
861 version = "0.3.1"
802 source = "registry+https://github.com/rust-lang/crates.io-index"
862 source = "registry+https://github.com/rust-lang/crates.io-index"
803 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
863 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
804 dependencies = [
864 dependencies = [
805 "rand_core 0.6.3",
865 "rand_core 0.6.3",
806 ]
866 ]
807
867
808 [[package]]
868 [[package]]
809 name = "rand_xoshiro"
869 name = "rand_xoshiro"
810 version = "0.4.0"
870 version = "0.4.0"
811 source = "registry+https://github.com/rust-lang/crates.io-index"
871 source = "registry+https://github.com/rust-lang/crates.io-index"
812 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
872 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
813 dependencies = [
873 dependencies = [
814 "rand_core 0.5.1",
874 "rand_core 0.5.1",
815 ]
875 ]
816
876
817 [[package]]
877 [[package]]
818 name = "rayon"
878 name = "rayon"
819 version = "1.5.0"
879 version = "1.5.0"
820 source = "registry+https://github.com/rust-lang/crates.io-index"
880 source = "registry+https://github.com/rust-lang/crates.io-index"
821 checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
881 checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
822 dependencies = [
882 dependencies = [
823 "autocfg",
883 "autocfg",
824 "crossbeam-deque",
884 "crossbeam-deque",
825 "either",
885 "either",
826 "rayon-core",
886 "rayon-core",
827 ]
887 ]
828
888
829 [[package]]
889 [[package]]
830 name = "rayon-core"
890 name = "rayon-core"
831 version = "1.9.0"
891 version = "1.9.0"
832 source = "registry+https://github.com/rust-lang/crates.io-index"
892 source = "registry+https://github.com/rust-lang/crates.io-index"
833 checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
893 checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
834 dependencies = [
894 dependencies = [
835 "crossbeam-channel 0.5.0",
895 "crossbeam-channel 0.5.0",
836 "crossbeam-deque",
896 "crossbeam-deque",
837 "crossbeam-utils 0.8.1",
897 "crossbeam-utils 0.8.1",
838 "lazy_static",
898 "lazy_static",
839 "num_cpus",
899 "num_cpus",
840 ]
900 ]
841
901
842 [[package]]
902 [[package]]
843 name = "redox_syscall"
903 name = "redox_syscall"
844 version = "0.1.57"
904 version = "0.1.57"
845 source = "registry+https://github.com/rust-lang/crates.io-index"
905 source = "registry+https://github.com/rust-lang/crates.io-index"
846 checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
906 checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
847
907
848 [[package]]
908 [[package]]
849 name = "regex"
909 name = "regex"
850 version = "1.4.2"
910 version = "1.4.2"
851 source = "registry+https://github.com/rust-lang/crates.io-index"
911 source = "registry+https://github.com/rust-lang/crates.io-index"
852 checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
912 checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
853 dependencies = [
913 dependencies = [
854 "aho-corasick",
914 "aho-corasick",
855 "memchr",
915 "memchr",
856 "regex-syntax",
916 "regex-syntax",
857 "thread_local",
917 "thread_local",
858 ]
918 ]
859
919
860 [[package]]
920 [[package]]
861 name = "regex-syntax"
921 name = "regex-syntax"
862 version = "0.6.21"
922 version = "0.6.21"
863 source = "registry+https://github.com/rust-lang/crates.io-index"
923 source = "registry+https://github.com/rust-lang/crates.io-index"
864 checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
924 checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
865
925
866 [[package]]
926 [[package]]
867 name = "remove_dir_all"
927 name = "remove_dir_all"
868 version = "0.5.3"
928 version = "0.5.3"
869 source = "registry+https://github.com/rust-lang/crates.io-index"
929 source = "registry+https://github.com/rust-lang/crates.io-index"
870 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
930 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
871 dependencies = [
931 dependencies = [
872 "winapi",
932 "winapi",
873 ]
933 ]
874
934
875 [[package]]
935 [[package]]
876 name = "rhg"
936 name = "rhg"
877 version = "0.1.0"
937 version = "0.1.0"
878 dependencies = [
938 dependencies = [
879 "atty",
939 "atty",
880 "chrono",
940 "chrono",
881 "clap",
941 "clap",
882 "derive_more",
942 "derive_more",
883 "env_logger",
943 "env_logger",
884 "format-bytes",
944 "format-bytes",
885 "hg-core",
945 "hg-core",
886 "home",
946 "home",
887 "lazy_static",
947 "lazy_static",
888 "log",
948 "log",
889 "micro-timer",
949 "micro-timer",
890 "regex",
950 "regex",
891 "users",
951 "users",
892 ]
952 ]
893
953
894 [[package]]
954 [[package]]
895 name = "same-file"
955 name = "same-file"
896 version = "1.0.6"
956 version = "1.0.6"
897 source = "registry+https://github.com/rust-lang/crates.io-index"
957 source = "registry+https://github.com/rust-lang/crates.io-index"
898 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
958 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
899 dependencies = [
959 dependencies = [
900 "winapi-util",
960 "winapi-util",
901 ]
961 ]
902
962
903 [[package]]
963 [[package]]
904 name = "scopeguard"
964 name = "scopeguard"
905 version = "1.1.0"
965 version = "1.1.0"
906 source = "registry+https://github.com/rust-lang/crates.io-index"
966 source = "registry+https://github.com/rust-lang/crates.io-index"
907 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
967 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
908
968
909 [[package]]
969 [[package]]
910 name = "sha-1"
970 name = "sha-1"
911 version = "0.9.6"
971 version = "0.9.6"
912 source = "registry+https://github.com/rust-lang/crates.io-index"
972 source = "registry+https://github.com/rust-lang/crates.io-index"
913 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
973 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
914 dependencies = [
974 dependencies = [
915 "block-buffer",
975 "block-buffer",
916 "cfg-if 1.0.0",
976 "cfg-if 1.0.0",
917 "cpufeatures",
977 "cpufeatures",
918 "digest",
978 "digest",
919 "opaque-debug",
979 "opaque-debug",
920 ]
980 ]
921
981
922 [[package]]
982 [[package]]
923 name = "sized-chunks"
983 name = "sized-chunks"
924 version = "0.6.2"
984 version = "0.6.2"
925 source = "registry+https://github.com/rust-lang/crates.io-index"
985 source = "registry+https://github.com/rust-lang/crates.io-index"
926 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
986 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
927 dependencies = [
987 dependencies = [
928 "bitmaps",
988 "bitmaps",
929 "typenum",
989 "typenum",
930 ]
990 ]
931
991
932 [[package]]
992 [[package]]
933 name = "stable_deref_trait"
993 name = "stable_deref_trait"
934 version = "1.2.0"
994 version = "1.2.0"
935 source = "registry+https://github.com/rust-lang/crates.io-index"
995 source = "registry+https://github.com/rust-lang/crates.io-index"
936 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
996 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
937
997
938 [[package]]
998 [[package]]
939 name = "static_assertions"
999 name = "static_assertions"
940 version = "1.1.0"
1000 version = "1.1.0"
941 source = "registry+https://github.com/rust-lang/crates.io-index"
1001 source = "registry+https://github.com/rust-lang/crates.io-index"
942 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1002 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
943
1003
944 [[package]]
1004 [[package]]
945 name = "strsim"
1005 name = "strsim"
946 version = "0.8.0"
1006 version = "0.8.0"
947 source = "registry+https://github.com/rust-lang/crates.io-index"
1007 source = "registry+https://github.com/rust-lang/crates.io-index"
948 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
1008 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
949
1009
950 [[package]]
1010 [[package]]
951 name = "syn"
1011 name = "syn"
952 version = "1.0.54"
1012 version = "1.0.54"
953 source = "registry+https://github.com/rust-lang/crates.io-index"
1013 source = "registry+https://github.com/rust-lang/crates.io-index"
954 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
1014 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
955 dependencies = [
1015 dependencies = [
956 "proc-macro2",
1016 "proc-macro2",
957 "quote",
1017 "quote",
958 "unicode-xid",
1018 "unicode-xid",
959 ]
1019 ]
960
1020
961 [[package]]
1021 [[package]]
962 name = "tempfile"
1022 name = "tempfile"
963 version = "3.1.0"
1023 version = "3.1.0"
964 source = "registry+https://github.com/rust-lang/crates.io-index"
1024 source = "registry+https://github.com/rust-lang/crates.io-index"
965 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
1025 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
966 dependencies = [
1026 dependencies = [
967 "cfg-if 0.1.10",
1027 "cfg-if 0.1.10",
968 "libc",
1028 "libc",
969 "rand 0.7.3",
1029 "rand 0.7.3",
970 "redox_syscall",
1030 "redox_syscall",
971 "remove_dir_all",
1031 "remove_dir_all",
972 "winapi",
1032 "winapi",
973 ]
1033 ]
974
1034
975 [[package]]
1035 [[package]]
976 name = "termcolor"
1036 name = "termcolor"
977 version = "1.1.2"
1037 version = "1.1.2"
978 source = "registry+https://github.com/rust-lang/crates.io-index"
1038 source = "registry+https://github.com/rust-lang/crates.io-index"
979 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
1039 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
980 dependencies = [
1040 dependencies = [
981 "winapi-util",
1041 "winapi-util",
982 ]
1042 ]
983
1043
984 [[package]]
1044 [[package]]
985 name = "textwrap"
1045 name = "textwrap"
986 version = "0.11.0"
1046 version = "0.11.0"
987 source = "registry+https://github.com/rust-lang/crates.io-index"
1047 source = "registry+https://github.com/rust-lang/crates.io-index"
988 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1048 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
989 dependencies = [
1049 dependencies = [
990 "unicode-width",
1050 "unicode-width",
991 ]
1051 ]
992
1052
993 [[package]]
1053 [[package]]
994 name = "thread_local"
1054 name = "thread_local"
995 version = "1.0.1"
1055 version = "1.0.1"
996 source = "registry+https://github.com/rust-lang/crates.io-index"
1056 source = "registry+https://github.com/rust-lang/crates.io-index"
997 checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
1057 checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
998 dependencies = [
1058 dependencies = [
999 "lazy_static",
1059 "lazy_static",
1000 ]
1060 ]
1001
1061
1002 [[package]]
1062 [[package]]
1003 name = "time"
1063 name = "time"
1004 version = "0.1.44"
1064 version = "0.1.44"
1005 source = "registry+https://github.com/rust-lang/crates.io-index"
1065 source = "registry+https://github.com/rust-lang/crates.io-index"
1006 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1066 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1007 dependencies = [
1067 dependencies = [
1008 "libc",
1068 "libc",
1009 "wasi 0.10.0+wasi-snapshot-preview1",
1069 "wasi 0.10.0+wasi-snapshot-preview1",
1010 "winapi",
1070 "winapi",
1011 ]
1071 ]
1012
1072
1013 [[package]]
1073 [[package]]
1014 name = "twox-hash"
1074 name = "twox-hash"
1015 version = "1.6.0"
1075 version = "1.6.0"
1016 source = "registry+https://github.com/rust-lang/crates.io-index"
1076 source = "registry+https://github.com/rust-lang/crates.io-index"
1017 checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
1077 checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
1018 dependencies = [
1078 dependencies = [
1019 "cfg-if 0.1.10",
1079 "cfg-if 0.1.10",
1020 "rand 0.7.3",
1080 "rand 0.7.3",
1021 "static_assertions",
1081 "static_assertions",
1022 ]
1082 ]
1023
1083
1024 [[package]]
1084 [[package]]
1025 name = "typenum"
1085 name = "typenum"
1026 version = "1.12.0"
1086 version = "1.12.0"
1027 source = "registry+https://github.com/rust-lang/crates.io-index"
1087 source = "registry+https://github.com/rust-lang/crates.io-index"
1028 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1088 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1029
1089
1030 [[package]]
1090 [[package]]
1031 name = "unicode-width"
1091 name = "unicode-width"
1032 version = "0.1.8"
1092 version = "0.1.8"
1033 source = "registry+https://github.com/rust-lang/crates.io-index"
1093 source = "registry+https://github.com/rust-lang/crates.io-index"
1034 checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1094 checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1035
1095
1036 [[package]]
1096 [[package]]
1037 name = "unicode-xid"
1097 name = "unicode-xid"
1038 version = "0.2.1"
1098 version = "0.2.1"
1039 source = "registry+https://github.com/rust-lang/crates.io-index"
1099 source = "registry+https://github.com/rust-lang/crates.io-index"
1040 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1100 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1041
1101
1042 [[package]]
1102 [[package]]
1043 name = "users"
1103 name = "users"
1044 version = "0.11.0"
1104 version = "0.11.0"
1045 source = "registry+https://github.com/rust-lang/crates.io-index"
1105 source = "registry+https://github.com/rust-lang/crates.io-index"
1046 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1106 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1047 dependencies = [
1107 dependencies = [
1048 "libc",
1108 "libc",
1049 "log",
1109 "log",
1050 ]
1110 ]
1051
1111
1052 [[package]]
1112 [[package]]
1053 name = "vcpkg"
1113 name = "vcpkg"
1054 version = "0.2.11"
1114 version = "0.2.11"
1055 source = "registry+https://github.com/rust-lang/crates.io-index"
1115 source = "registry+https://github.com/rust-lang/crates.io-index"
1056 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1116 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1057
1117
1058 [[package]]
1118 [[package]]
1059 name = "vcsgraph"
1119 name = "vcsgraph"
1060 version = "0.2.0"
1120 version = "0.2.0"
1061 source = "registry+https://github.com/rust-lang/crates.io-index"
1121 source = "registry+https://github.com/rust-lang/crates.io-index"
1062 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1122 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1063 dependencies = [
1123 dependencies = [
1064 "hex",
1124 "hex",
1065 "rand 0.7.3",
1125 "rand 0.7.3",
1066 "sha-1",
1126 "sha-1",
1067 ]
1127 ]
1068
1128
1069 [[package]]
1129 [[package]]
1070 name = "vec_map"
1130 name = "vec_map"
1071 version = "0.8.2"
1131 version = "0.8.2"
1072 source = "registry+https://github.com/rust-lang/crates.io-index"
1132 source = "registry+https://github.com/rust-lang/crates.io-index"
1073 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1133 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1074
1134
1075 [[package]]
1135 [[package]]
1076 name = "version_check"
1136 name = "version_check"
1077 version = "0.9.2"
1137 version = "0.9.2"
1078 source = "registry+https://github.com/rust-lang/crates.io-index"
1138 source = "registry+https://github.com/rust-lang/crates.io-index"
1079 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1139 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1080
1140
1081 [[package]]
1141 [[package]]
1082 name = "wasi"
1142 name = "wasi"
1083 version = "0.9.0+wasi-snapshot-preview1"
1143 version = "0.9.0+wasi-snapshot-preview1"
1084 source = "registry+https://github.com/rust-lang/crates.io-index"
1144 source = "registry+https://github.com/rust-lang/crates.io-index"
1085 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1145 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1086
1146
1087 [[package]]
1147 [[package]]
1088 name = "wasi"
1148 name = "wasi"
1089 version = "0.10.0+wasi-snapshot-preview1"
1149 version = "0.10.0+wasi-snapshot-preview1"
1090 source = "registry+https://github.com/rust-lang/crates.io-index"
1150 source = "registry+https://github.com/rust-lang/crates.io-index"
1091 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1151 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1092
1152
1093 [[package]]
1153 [[package]]
1094 name = "winapi"
1154 name = "winapi"
1095 version = "0.3.9"
1155 version = "0.3.9"
1096 source = "registry+https://github.com/rust-lang/crates.io-index"
1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1097 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1157 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1098 dependencies = [
1158 dependencies = [
1099 "winapi-i686-pc-windows-gnu",
1159 "winapi-i686-pc-windows-gnu",
1100 "winapi-x86_64-pc-windows-gnu",
1160 "winapi-x86_64-pc-windows-gnu",
1101 ]
1161 ]
1102
1162
1103 [[package]]
1163 [[package]]
1104 name = "winapi-i686-pc-windows-gnu"
1164 name = "winapi-i686-pc-windows-gnu"
1105 version = "0.4.0"
1165 version = "0.4.0"
1106 source = "registry+https://github.com/rust-lang/crates.io-index"
1166 source = "registry+https://github.com/rust-lang/crates.io-index"
1107 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1167 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1108
1168
1109 [[package]]
1169 [[package]]
1110 name = "winapi-util"
1170 name = "winapi-util"
1111 version = "0.1.5"
1171 version = "0.1.5"
1112 source = "registry+https://github.com/rust-lang/crates.io-index"
1172 source = "registry+https://github.com/rust-lang/crates.io-index"
1113 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1173 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1114 dependencies = [
1174 dependencies = [
1115 "winapi",
1175 "winapi",
1116 ]
1176 ]
1117
1177
1118 [[package]]
1178 [[package]]
1119 name = "winapi-x86_64-pc-windows-gnu"
1179 name = "winapi-x86_64-pc-windows-gnu"
1120 version = "0.4.0"
1180 version = "0.4.0"
1121 source = "registry+https://github.com/rust-lang/crates.io-index"
1181 source = "registry+https://github.com/rust-lang/crates.io-index"
1122 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1182 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1123
1183
1124 [[package]]
1184 [[package]]
1125 name = "zstd"
1185 name = "zstd"
1126 version = "0.5.3+zstd.1.4.5"
1186 version = "0.5.3+zstd.1.4.5"
1127 source = "registry+https://github.com/rust-lang/crates.io-index"
1187 source = "registry+https://github.com/rust-lang/crates.io-index"
1128 checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
1188 checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
1129 dependencies = [
1189 dependencies = [
1130 "zstd-safe",
1190 "zstd-safe",
1131 ]
1191 ]
1132
1192
1133 [[package]]
1193 [[package]]
1134 name = "zstd-safe"
1194 name = "zstd-safe"
1135 version = "2.0.5+zstd.1.4.5"
1195 version = "2.0.5+zstd.1.4.5"
1136 source = "registry+https://github.com/rust-lang/crates.io-index"
1196 source = "registry+https://github.com/rust-lang/crates.io-index"
1137 checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
1197 checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
1138 dependencies = [
1198 dependencies = [
1139 "libc",
1199 "libc",
1140 "zstd-sys",
1200 "zstd-sys",
1141 ]
1201 ]
1142
1202
1143 [[package]]
1203 [[package]]
1144 name = "zstd-sys"
1204 name = "zstd-sys"
1145 version = "1.4.17+zstd.1.4.5"
1205 version = "1.4.17+zstd.1.4.5"
1146 source = "registry+https://github.com/rust-lang/crates.io-index"
1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1147 checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
1207 checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
1148 dependencies = [
1208 dependencies = [
1149 "cc",
1209 "cc",
1150 "glob",
1210 "glob",
1151 "itertools",
1211 "itertools",
1152 "libc",
1212 "libc",
1153 ]
1213 ]
@@ -1,47 +1,47 b''
1 [package]
1 [package]
2 name = "hg-core"
2 name = "hg-core"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
6 edition = "2018"
6 edition = "2018"
7
7
8 [lib]
8 [lib]
9 name = "hg"
9 name = "hg"
10
10
11 [dependencies]
11 [dependencies]
12 bitflags = "1.2"
12 bitflags = "1.2"
13 bytes-cast = "0.2"
13 bytes-cast = "0.2"
14 byteorder = "1.3.4"
14 byteorder = "1.3.4"
15 derive_more = "0.99"
15 derive_more = "0.99"
16 home = "0.5"
16 home = "0.5"
17 im-rc = "15.0.*"
17 im-rc = "15.0.*"
18 itertools = "0.9"
18 itertools = "0.9"
19 lazy_static = "1.4.0"
19 lazy_static = "1.4.0"
20 libc = "0.2"
20 libc = "0.2"
21 ouroboros = "0.15.0"
21 rand = "0.8.4"
22 rand = "0.8.4"
22 rand_pcg = "0.3.1"
23 rand_pcg = "0.3.1"
23 rand_distr = "0.4.2"
24 rand_distr = "0.4.2"
24 rayon = "1.3.0"
25 rayon = "1.3.0"
25 regex = "1.3.9"
26 regex = "1.3.9"
26 sha-1 = "0.9.6"
27 sha-1 = "0.9.6"
27 twox-hash = "1.5.0"
28 twox-hash = "1.5.0"
28 same-file = "1.0.6"
29 same-file = "1.0.6"
29 stable_deref_trait = "1.2.0"
30 tempfile = "3.1.0"
30 tempfile = "3.1.0"
31 crossbeam-channel = "0.4"
31 crossbeam-channel = "0.4"
32 micro-timer = "0.3.0"
32 micro-timer = "0.3.0"
33 log = "0.4.8"
33 log = "0.4.8"
34 memmap2 = {version = "0.4", features = ["stable_deref_trait"]}
34 memmap2 = {version = "0.4", features = ["stable_deref_trait"]}
35 zstd = "0.5.3"
35 zstd = "0.5.3"
36 format-bytes = "0.3.0"
36 format-bytes = "0.3.0"
37
37
38 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
38 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
39 # we have a clearer view of which backend is the fastest.
39 # we have a clearer view of which backend is the fastest.
40 [dependencies.flate2]
40 [dependencies.flate2]
41 version = "1.0.16"
41 version = "1.0.16"
42 features = ["zlib"]
42 features = ["zlib"]
43 default-features = false
43 default-features = false
44
44
45 [dev-dependencies]
45 [dev-dependencies]
46 clap = "*"
46 clap = "*"
47 pretty_assertions = "0.6.1"
47 pretty_assertions = "0.6.1"
@@ -1,151 +1,149 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::dirstate::entry::TruncatedTimestamp;
12 use crate::dirstate::entry::TruncatedTimestamp;
13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
14 use crate::{
14 use crate::{
15 utils::hg_path::{HgPath, HgPathError},
15 utils::hg_path::{HgPath, HgPathError},
16 PatternError,
16 PatternError,
17 };
17 };
18
18
19 use std::{borrow::Cow, fmt};
19 use std::{borrow::Cow, fmt};
20
20
21 /// Wrong type of file from a `BadMatch`
21 /// Wrong type of file from a `BadMatch`
22 /// Note: a lot of those don't exist on all platforms.
22 /// Note: a lot of those don't exist on all platforms.
23 #[derive(Debug, Copy, Clone)]
23 #[derive(Debug, Copy, Clone)]
24 pub enum BadType {
24 pub enum BadType {
25 CharacterDevice,
25 CharacterDevice,
26 BlockDevice,
26 BlockDevice,
27 FIFO,
27 FIFO,
28 Socket,
28 Socket,
29 Directory,
29 Directory,
30 Unknown,
30 Unknown,
31 }
31 }
32
32
33 impl fmt::Display for BadType {
33 impl fmt::Display for BadType {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 f.write_str(match self {
35 f.write_str(match self {
36 BadType::CharacterDevice => "character device",
36 BadType::CharacterDevice => "character device",
37 BadType::BlockDevice => "block device",
37 BadType::BlockDevice => "block device",
38 BadType::FIFO => "fifo",
38 BadType::FIFO => "fifo",
39 BadType::Socket => "socket",
39 BadType::Socket => "socket",
40 BadType::Directory => "directory",
40 BadType::Directory => "directory",
41 BadType::Unknown => "unknown",
41 BadType::Unknown => "unknown",
42 })
42 })
43 }
43 }
44 }
44 }
45
45
46 /// Was explicitly matched but cannot be found/accessed
46 /// Was explicitly matched but cannot be found/accessed
47 #[derive(Debug, Copy, Clone)]
47 #[derive(Debug, Copy, Clone)]
48 pub enum BadMatch {
48 pub enum BadMatch {
49 OsError(i32),
49 OsError(i32),
50 BadType(BadType),
50 BadType(BadType),
51 }
51 }
52
52
53 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
53 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
54 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
54 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
55 pub type IgnoreFnType<'a> =
55 pub type IgnoreFnType<'a> =
56 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
56 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
57
57
58 /// We have a good mix of owned (from directory traversal) and borrowed (from
58 /// We have a good mix of owned (from directory traversal) and borrowed (from
59 /// the dirstate/explicit) paths, this comes up a lot.
59 /// the dirstate/explicit) paths, this comes up a lot.
60 pub type HgPathCow<'a> = Cow<'a, HgPath>;
60 pub type HgPathCow<'a> = Cow<'a, HgPath>;
61
61
62 #[derive(Debug, Copy, Clone)]
62 #[derive(Debug, Copy, Clone)]
63 pub struct StatusOptions {
63 pub struct StatusOptions {
64 /// Whether we are on a filesystem with UNIX-like exec flags
64 /// Whether we are on a filesystem with UNIX-like exec flags
65 pub check_exec: bool,
65 pub check_exec: bool,
66 pub list_clean: bool,
66 pub list_clean: bool,
67 pub list_unknown: bool,
67 pub list_unknown: bool,
68 pub list_ignored: bool,
68 pub list_ignored: bool,
69 /// Whether to populate `StatusPath::copy_source`
69 /// Whether to populate `StatusPath::copy_source`
70 pub list_copies: bool,
70 pub list_copies: bool,
71 /// Whether to collect traversed dirs for applying a callback later.
71 /// Whether to collect traversed dirs for applying a callback later.
72 /// Used by `hg purge` for example.
72 /// Used by `hg purge` for example.
73 pub collect_traversed_dirs: bool,
73 pub collect_traversed_dirs: bool,
74 }
74 }
75
75
76 #[derive(Default)]
76 #[derive(Default)]
77 pub struct DirstateStatus<'a> {
77 pub struct DirstateStatus<'a> {
78 /// The current time at the start of the `status()` algorithm, as measured
78 /// The current time at the start of the `status()` algorithm, as measured
79 /// and possibly truncated by the filesystem.
79 /// and possibly truncated by the filesystem.
80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
81
81
82 /// Tracked files whose contents have changed since the parent revision
82 /// Tracked files whose contents have changed since the parent revision
83 pub modified: Vec<StatusPath<'a>>,
83 pub modified: Vec<StatusPath<'a>>,
84
84
85 /// Newly-tracked files that were not present in the parent
85 /// Newly-tracked files that were not present in the parent
86 pub added: Vec<StatusPath<'a>>,
86 pub added: Vec<StatusPath<'a>>,
87
87
88 /// Previously-tracked files that have been (re)moved with an hg command
88 /// Previously-tracked files that have been (re)moved with an hg command
89 pub removed: Vec<StatusPath<'a>>,
89 pub removed: Vec<StatusPath<'a>>,
90
90
91 /// (Still) tracked files that are missing, (re)moved with an non-hg
91 /// (Still) tracked files that are missing, (re)moved with an non-hg
92 /// command
92 /// command
93 pub deleted: Vec<StatusPath<'a>>,
93 pub deleted: Vec<StatusPath<'a>>,
94
94
95 /// Tracked files that are up to date with the parent.
95 /// Tracked files that are up to date with the parent.
96 /// Only pupulated if `StatusOptions::list_clean` is true.
96 /// Only pupulated if `StatusOptions::list_clean` is true.
97 pub clean: Vec<StatusPath<'a>>,
97 pub clean: Vec<StatusPath<'a>>,
98
98
99 /// Files in the working directory that are ignored with `.hgignore`.
99 /// Files in the working directory that are ignored with `.hgignore`.
100 /// Only pupulated if `StatusOptions::list_ignored` is true.
100 /// Only pupulated if `StatusOptions::list_ignored` is true.
101 pub ignored: Vec<StatusPath<'a>>,
101 pub ignored: Vec<StatusPath<'a>>,
102
102
103 /// Files in the working directory that are neither tracked nor ignored.
103 /// Files in the working directory that are neither tracked nor ignored.
104 /// Only pupulated if `StatusOptions::list_unknown` is true.
104 /// Only pupulated if `StatusOptions::list_unknown` is true.
105 pub unknown: Vec<StatusPath<'a>>,
105 pub unknown: Vec<StatusPath<'a>>,
106
106
107 /// Was explicitly matched but cannot be found/accessed
107 /// Was explicitly matched but cannot be found/accessed
108 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
108 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
109
109
110 /// Either clean or modified, but we can’t tell from filesystem metadata
110 /// Either clean or modified, but we can’t tell from filesystem metadata
111 /// alone. The file contents need to be read and compared with that in
111 /// alone. The file contents need to be read and compared with that in
112 /// the parent.
112 /// the parent.
113 pub unsure: Vec<StatusPath<'a>>,
113 pub unsure: Vec<StatusPath<'a>>,
114
114
115 /// Only filled if `collect_traversed_dirs` is `true`
115 /// Only filled if `collect_traversed_dirs` is `true`
116 pub traversed: Vec<HgPathCow<'a>>,
116 pub traversed: Vec<HgPathCow<'a>>,
117
117
118 /// Whether `status()` made changed to the `DirstateMap` that should be
118 /// Whether `status()` made changed to the `DirstateMap` that should be
119 /// written back to disk
119 /// written back to disk
120 pub dirty: bool,
120 pub dirty: bool,
121 }
121 }
122
122
123 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
123 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
124 pub struct StatusPath<'a> {
124 pub struct StatusPath<'a> {
125 pub path: HgPathCow<'a>,
125 pub path: HgPathCow<'a>,
126 pub copy_source: Option<HgPathCow<'a>>,
126 pub copy_source: Option<HgPathCow<'a>>,
127 }
127 }
128
128
129 #[derive(Debug, derive_more::From)]
129 #[derive(Debug, derive_more::From)]
130 pub enum StatusError {
130 pub enum StatusError {
131 /// An invalid path that cannot be represented in Mercurial was found
131 /// An invalid path that cannot be represented in Mercurial was found
132 Path(HgPathError),
132 Path(HgPathError),
133 /// An invalid "ignore" pattern was found
133 /// An invalid "ignore" pattern was found
134 Pattern(PatternError),
134 Pattern(PatternError),
135 /// Corrupted dirstate
135 /// Corrupted dirstate
136 DirstateV2ParseError(DirstateV2ParseError),
136 DirstateV2ParseError(DirstateV2ParseError),
137 }
137 }
138
138
139 pub type StatusResult<T> = Result<T, StatusError>;
140
141 impl fmt::Display for StatusError {
139 impl fmt::Display for StatusError {
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 match self {
141 match self {
144 StatusError::Path(error) => error.fmt(f),
142 StatusError::Path(error) => error.fmt(f),
145 StatusError::Pattern(error) => error.fmt(f),
143 StatusError::Pattern(error) => error.fmt(f),
146 StatusError::DirstateV2ParseError(_) => {
144 StatusError::DirstateV2ParseError(_) => {
147 f.write_str("dirstate-v2 parse error")
145 f.write_str("dirstate-v2 parse error")
148 }
146 }
149 }
147 }
150 }
148 }
151 }
149 }
@@ -1,1158 +1,1180 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::path::PathBuf;
4 use std::path::PathBuf;
5
5
6 use super::on_disk;
6 use super::on_disk;
7 use super::on_disk::DirstateV2ParseError;
7 use super::on_disk::DirstateV2ParseError;
8 use super::owning::OwningDirstateMap;
8 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::CopyMapIter;
13 use crate::dirstate::CopyMapIter;
14 use crate::dirstate::StateMapIter;
14 use crate::dirstate::StateMapIter;
15 use crate::dirstate::TruncatedTimestamp;
15 use crate::dirstate::TruncatedTimestamp;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_NON_NORMAL;
17 use crate::dirstate::SIZE_NON_NORMAL;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
20 use crate::DirstateEntry;
21 use crate::DirstateError;
21 use crate::DirstateError;
22 use crate::DirstateParents;
22 use crate::DirstateParents;
23 use crate::DirstateStatus;
23 use crate::DirstateStatus;
24 use crate::EntryState;
24 use crate::EntryState;
25 use crate::FastHashMap;
25 use crate::FastHashMap;
26 use crate::PatternFileWarning;
26 use crate::PatternFileWarning;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// 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.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 pub struct DirstateMap<'on_disk> {
34 pub struct DirstateMap<'on_disk> {
35 /// Contents of the `.hg/dirstate` file
35 /// Contents of the `.hg/dirstate` file
36 pub(super) on_disk: &'on_disk [u8],
36 pub(super) on_disk: &'on_disk [u8],
37
37
38 pub(super) root: ChildNodes<'on_disk>,
38 pub(super) root: ChildNodes<'on_disk>,
39
39
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 pub(super) nodes_with_entry_count: u32,
41 pub(super) nodes_with_entry_count: u32,
42
42
43 /// Number of nodes anywhere in the tree that have
43 /// Number of nodes anywhere in the tree that have
44 /// `.copy_source.is_some()`.
44 /// `.copy_source.is_some()`.
45 pub(super) nodes_with_copy_source_count: u32,
45 pub(super) nodes_with_copy_source_count: u32,
46
46
47 /// See on_disk::Header
47 /// See on_disk::Header
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49
49
50 /// How many bytes of `on_disk` are not used anymore
50 /// How many bytes of `on_disk` are not used anymore
51 pub(super) unreachable_bytes: u32,
51 pub(super) unreachable_bytes: u32,
52 }
52 }
53
53
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// map key would also work: all paths in a given map have the same parent
55 /// map key would also work: all paths in a given map have the same parent
56 /// path, so comparing full paths gives the same result as comparing base
56 /// path, so comparing full paths gives the same result as comparing base
57 /// names. However `HashMap` would waste time always re-hashing the same
57 /// names. However `HashMap` would waste time always re-hashing the same
58 /// string prefix.
58 /// string prefix.
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60
60
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 InMemory(&'tree HgPathBuf),
64 InMemory(&'tree HgPathBuf),
65 OnDisk(&'on_disk HgPath),
65 OnDisk(&'on_disk HgPath),
66 }
66 }
67
67
68 pub(super) enum ChildNodes<'on_disk> {
68 pub(super) enum ChildNodes<'on_disk> {
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
70 OnDisk(&'on_disk [on_disk::Node]),
71 }
71 }
72
72
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 OnDisk(&'on_disk [on_disk::Node]),
75 OnDisk(&'on_disk [on_disk::Node]),
76 }
76 }
77
77
78 pub(super) enum NodeRef<'tree, 'on_disk> {
78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 OnDisk(&'on_disk on_disk::Node),
80 OnDisk(&'on_disk on_disk::Node),
81 }
81 }
82
82
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 match *self {
85 match *self {
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 }
88 }
89 }
89 }
90 }
90 }
91
91
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 type Target = HgPath;
93 type Target = HgPath;
94
94
95 fn deref(&self) -> &HgPath {
95 fn deref(&self) -> &HgPath {
96 match *self {
96 match *self {
97 BorrowedPath::InMemory(in_memory) => in_memory,
97 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 }
99 }
100 }
100 }
101 }
101 }
102
102
103 impl Default for ChildNodes<'_> {
103 impl Default for ChildNodes<'_> {
104 fn default() -> Self {
104 fn default() -> Self {
105 ChildNodes::InMemory(Default::default())
105 ChildNodes::InMemory(Default::default())
106 }
106 }
107 }
107 }
108
108
109 impl<'on_disk> ChildNodes<'on_disk> {
109 impl<'on_disk> ChildNodes<'on_disk> {
110 pub(super) fn as_ref<'tree>(
110 pub(super) fn as_ref<'tree>(
111 &'tree self,
111 &'tree self,
112 ) -> ChildNodesRef<'tree, 'on_disk> {
112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 match self {
113 match self {
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 }
116 }
117 }
117 }
118
118
119 pub(super) fn is_empty(&self) -> bool {
119 pub(super) fn is_empty(&self) -> bool {
120 match self {
120 match self {
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 }
123 }
124 }
124 }
125
125
126 fn make_mut(
126 fn make_mut(
127 &mut self,
127 &mut self,
128 on_disk: &'on_disk [u8],
128 on_disk: &'on_disk [u8],
129 unreachable_bytes: &mut u32,
129 unreachable_bytes: &mut u32,
130 ) -> Result<
130 ) -> Result<
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 DirstateV2ParseError,
132 DirstateV2ParseError,
133 > {
133 > {
134 match self {
134 match self {
135 ChildNodes::InMemory(nodes) => Ok(nodes),
135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::OnDisk(nodes) => {
136 ChildNodes::OnDisk(nodes) => {
137 *unreachable_bytes +=
137 *unreachable_bytes +=
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 let nodes = nodes
139 let nodes = nodes
140 .iter()
140 .iter()
141 .map(|node| {
141 .map(|node| {
142 Ok((
142 Ok((
143 node.path(on_disk)?,
143 node.path(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
145 ))
145 ))
146 })
146 })
147 .collect::<Result<_, _>>()?;
147 .collect::<Result<_, _>>()?;
148 *self = ChildNodes::InMemory(nodes);
148 *self = ChildNodes::InMemory(nodes);
149 match self {
149 match self {
150 ChildNodes::InMemory(nodes) => Ok(nodes),
150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::OnDisk(_) => unreachable!(),
151 ChildNodes::OnDisk(_) => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157
157
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 pub(super) fn get(
159 pub(super) fn get(
160 &self,
160 &self,
161 base_name: &HgPath,
161 base_name: &HgPath,
162 on_disk: &'on_disk [u8],
162 on_disk: &'on_disk [u8],
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 match self {
164 match self {
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 .get_key_value(base_name)
166 .get_key_value(base_name)
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 ChildNodesRef::OnDisk(nodes) => {
168 ChildNodesRef::OnDisk(nodes) => {
169 let mut parse_result = Ok(());
169 let mut parse_result = Ok(());
170 let search_result = nodes.binary_search_by(|node| {
170 let search_result = nodes.binary_search_by(|node| {
171 match node.base_name(on_disk) {
171 match node.base_name(on_disk) {
172 Ok(node_base_name) => node_base_name.cmp(base_name),
172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Err(e) => {
173 Err(e) => {
174 parse_result = Err(e);
174 parse_result = Err(e);
175 // Dummy comparison result, `search_result` won’t
175 // Dummy comparison result, `search_result` won’t
176 // be used since `parse_result` is an error
176 // be used since `parse_result` is an error
177 std::cmp::Ordering::Equal
177 std::cmp::Ordering::Equal
178 }
178 }
179 }
179 }
180 });
180 });
181 parse_result.map(|()| {
181 parse_result.map(|()| {
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 })
183 })
184 }
184 }
185 }
185 }
186 }
186 }
187
187
188 /// Iterate in undefined order
188 /// Iterate in undefined order
189 pub(super) fn iter(
189 pub(super) fn iter(
190 &self,
190 &self,
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 match self {
192 match self {
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 ),
195 ),
196 ChildNodesRef::OnDisk(nodes) => {
196 ChildNodesRef::OnDisk(nodes) => {
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 }
198 }
199 }
199 }
200 }
200 }
201
201
202 /// Iterate in parallel in undefined order
202 /// Iterate in parallel in undefined order
203 pub(super) fn par_iter(
203 pub(super) fn par_iter(
204 &self,
204 &self,
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 {
206 {
207 use rayon::prelude::*;
207 use rayon::prelude::*;
208 match self {
208 match self {
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 ),
211 ),
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 nodes.par_iter().map(NodeRef::OnDisk),
213 nodes.par_iter().map(NodeRef::OnDisk),
214 ),
214 ),
215 }
215 }
216 }
216 }
217
217
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 match self {
219 match self {
220 ChildNodesRef::InMemory(nodes) => {
220 ChildNodesRef::InMemory(nodes) => {
221 let mut vec: Vec<_> = nodes
221 let mut vec: Vec<_> = nodes
222 .iter()
222 .iter()
223 .map(|(k, v)| NodeRef::InMemory(k, v))
223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .collect();
224 .collect();
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 match node {
226 match node {
227 NodeRef::InMemory(path, _node) => path.base_name(),
227 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::OnDisk(_) => unreachable!(),
228 NodeRef::OnDisk(_) => unreachable!(),
229 }
229 }
230 }
230 }
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // value: https://github.com/rust-lang/rust/issues/34162
232 // value: https://github.com/rust-lang/rust/issues/34162
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec
234 vec
235 }
235 }
236 ChildNodesRef::OnDisk(nodes) => {
236 ChildNodesRef::OnDisk(nodes) => {
237 // Nodes on disk are already sorted
237 // Nodes on disk are already sorted
238 nodes.iter().map(NodeRef::OnDisk).collect()
238 nodes.iter().map(NodeRef::OnDisk).collect()
239 }
239 }
240 }
240 }
241 }
241 }
242 }
242 }
243
243
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 pub(super) fn full_path(
245 pub(super) fn full_path(
246 &self,
246 &self,
247 on_disk: &'on_disk [u8],
247 on_disk: &'on_disk [u8],
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 match self {
249 match self {
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 }
252 }
253 }
253 }
254
254
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// HgPath>` detached from `'tree`
256 /// HgPath>` detached from `'tree`
257 pub(super) fn full_path_borrowed(
257 pub(super) fn full_path_borrowed(
258 &self,
258 &self,
259 on_disk: &'on_disk [u8],
259 on_disk: &'on_disk [u8],
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 match self {
261 match self {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 },
265 },
266 NodeRef::OnDisk(node) => {
266 NodeRef::OnDisk(node) => {
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 }
268 }
269 }
269 }
270 }
270 }
271
271
272 pub(super) fn base_name(
272 pub(super) fn base_name(
273 &self,
273 &self,
274 on_disk: &'on_disk [u8],
274 on_disk: &'on_disk [u8],
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 match self {
276 match self {
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 }
279 }
280 }
280 }
281
281
282 pub(super) fn children(
282 pub(super) fn children(
283 &self,
283 &self,
284 on_disk: &'on_disk [u8],
284 on_disk: &'on_disk [u8],
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 match self {
286 match self {
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::OnDisk(node) => {
288 NodeRef::OnDisk(node) => {
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 }
290 }
291 }
291 }
292 }
292 }
293
293
294 pub(super) fn has_copy_source(&self) -> bool {
294 pub(super) fn has_copy_source(&self) -> bool {
295 match self {
295 match self {
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 }
298 }
299 }
299 }
300
300
301 pub(super) fn copy_source(
301 pub(super) fn copy_source(
302 &self,
302 &self,
303 on_disk: &'on_disk [u8],
303 on_disk: &'on_disk [u8],
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 match self {
305 match self {
306 NodeRef::InMemory(_path, node) => {
306 NodeRef::InMemory(_path, node) => {
307 Ok(node.copy_source.as_ref().map(|s| &**s))
307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 }
308 }
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 }
310 }
311 }
311 }
312 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
312 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
313 /// HgPath>` detached from `'tree`
313 /// HgPath>` detached from `'tree`
314 pub(super) fn copy_source_borrowed(
314 pub(super) fn copy_source_borrowed(
315 &self,
315 &self,
316 on_disk: &'on_disk [u8],
316 on_disk: &'on_disk [u8],
317 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
317 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
318 {
318 {
319 Ok(match self {
319 Ok(match self {
320 NodeRef::InMemory(_path, node) => {
320 NodeRef::InMemory(_path, node) => {
321 node.copy_source.as_ref().map(|source| match source {
321 node.copy_source.as_ref().map(|source| match source {
322 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
322 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
323 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
323 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
324 })
324 })
325 }
325 }
326 NodeRef::OnDisk(node) => node
326 NodeRef::OnDisk(node) => node
327 .copy_source(on_disk)?
327 .copy_source(on_disk)?
328 .map(|source| BorrowedPath::OnDisk(source)),
328 .map(|source| BorrowedPath::OnDisk(source)),
329 })
329 })
330 }
330 }
331
331
332 pub(super) fn entry(
332 pub(super) fn entry(
333 &self,
333 &self,
334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
335 match self {
335 match self {
336 NodeRef::InMemory(_path, node) => {
336 NodeRef::InMemory(_path, node) => {
337 Ok(node.data.as_entry().copied())
337 Ok(node.data.as_entry().copied())
338 }
338 }
339 NodeRef::OnDisk(node) => node.entry(),
339 NodeRef::OnDisk(node) => node.entry(),
340 }
340 }
341 }
341 }
342
342
343 pub(super) fn state(
343 pub(super) fn state(
344 &self,
344 &self,
345 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
345 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
346 Ok(self.entry()?.map(|e| e.state()))
346 Ok(self.entry()?.map(|e| e.state()))
347 }
347 }
348
348
349 pub(super) fn cached_directory_mtime(
349 pub(super) fn cached_directory_mtime(
350 &self,
350 &self,
351 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
351 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
352 match self {
352 match self {
353 NodeRef::InMemory(_path, node) => Ok(match node.data {
353 NodeRef::InMemory(_path, node) => Ok(match node.data {
354 NodeData::CachedDirectory { mtime } => Some(mtime),
354 NodeData::CachedDirectory { mtime } => Some(mtime),
355 _ => None,
355 _ => None,
356 }),
356 }),
357 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
357 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
358 }
358 }
359 }
359 }
360
360
361 pub(super) fn descendants_with_entry_count(&self) -> u32 {
361 pub(super) fn descendants_with_entry_count(&self) -> u32 {
362 match self {
362 match self {
363 NodeRef::InMemory(_path, node) => {
363 NodeRef::InMemory(_path, node) => {
364 node.descendants_with_entry_count
364 node.descendants_with_entry_count
365 }
365 }
366 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
366 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
367 }
367 }
368 }
368 }
369
369
370 pub(super) fn tracked_descendants_count(&self) -> u32 {
370 pub(super) fn tracked_descendants_count(&self) -> u32 {
371 match self {
371 match self {
372 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
372 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
373 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
373 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
374 }
374 }
375 }
375 }
376 }
376 }
377
377
378 /// Represents a file or a directory
378 /// Represents a file or a directory
379 #[derive(Default)]
379 #[derive(Default)]
380 pub(super) struct Node<'on_disk> {
380 pub(super) struct Node<'on_disk> {
381 pub(super) data: NodeData,
381 pub(super) data: NodeData,
382
382
383 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
383 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
384
384
385 pub(super) children: ChildNodes<'on_disk>,
385 pub(super) children: ChildNodes<'on_disk>,
386
386
387 /// How many (non-inclusive) descendants of this node have an entry.
387 /// How many (non-inclusive) descendants of this node have an entry.
388 pub(super) descendants_with_entry_count: u32,
388 pub(super) descendants_with_entry_count: u32,
389
389
390 /// How many (non-inclusive) descendants of this node have an entry whose
390 /// How many (non-inclusive) descendants of this node have an entry whose
391 /// state is "tracked".
391 /// state is "tracked".
392 pub(super) tracked_descendants_count: u32,
392 pub(super) tracked_descendants_count: u32,
393 }
393 }
394
394
395 pub(super) enum NodeData {
395 pub(super) enum NodeData {
396 Entry(DirstateEntry),
396 Entry(DirstateEntry),
397 CachedDirectory { mtime: TruncatedTimestamp },
397 CachedDirectory { mtime: TruncatedTimestamp },
398 None,
398 None,
399 }
399 }
400
400
401 impl Default for NodeData {
401 impl Default for NodeData {
402 fn default() -> Self {
402 fn default() -> Self {
403 NodeData::None
403 NodeData::None
404 }
404 }
405 }
405 }
406
406
407 impl NodeData {
407 impl NodeData {
408 fn has_entry(&self) -> bool {
408 fn has_entry(&self) -> bool {
409 match self {
409 match self {
410 NodeData::Entry(_) => true,
410 NodeData::Entry(_) => true,
411 _ => false,
411 _ => false,
412 }
412 }
413 }
413 }
414
414
415 fn as_entry(&self) -> Option<&DirstateEntry> {
415 fn as_entry(&self) -> Option<&DirstateEntry> {
416 match self {
416 match self {
417 NodeData::Entry(entry) => Some(entry),
417 NodeData::Entry(entry) => Some(entry),
418 _ => None,
418 _ => None,
419 }
419 }
420 }
420 }
421 }
421 }
422
422
423 impl<'on_disk> DirstateMap<'on_disk> {
423 impl<'on_disk> DirstateMap<'on_disk> {
424 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
424 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
425 Self {
425 Self {
426 on_disk,
426 on_disk,
427 root: ChildNodes::default(),
427 root: ChildNodes::default(),
428 nodes_with_entry_count: 0,
428 nodes_with_entry_count: 0,
429 nodes_with_copy_source_count: 0,
429 nodes_with_copy_source_count: 0,
430 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
430 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
431 unreachable_bytes: 0,
431 unreachable_bytes: 0,
432 }
432 }
433 }
433 }
434
434
435 #[timed]
435 #[timed]
436 pub fn new_v2(
436 pub fn new_v2(
437 on_disk: &'on_disk [u8],
437 on_disk: &'on_disk [u8],
438 data_size: usize,
438 data_size: usize,
439 metadata: &[u8],
439 metadata: &[u8],
440 ) -> Result<Self, DirstateError> {
440 ) -> Result<Self, DirstateError> {
441 if let Some(data) = on_disk.get(..data_size) {
441 if let Some(data) = on_disk.get(..data_size) {
442 Ok(on_disk::read(data, metadata)?)
442 Ok(on_disk::read(data, metadata)?)
443 } else {
443 } else {
444 Err(DirstateV2ParseError.into())
444 Err(DirstateV2ParseError.into())
445 }
445 }
446 }
446 }
447
447
448 #[timed]
448 #[timed]
449 pub fn new_v1(
449 pub fn new_v1(
450 on_disk: &'on_disk [u8],
450 on_disk: &'on_disk [u8],
451 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
451 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
452 let mut map = Self::empty(on_disk);
452 let mut map = Self::empty(on_disk);
453 if map.on_disk.is_empty() {
453 if map.on_disk.is_empty() {
454 return Ok((map, None));
454 return Ok((map, None));
455 }
455 }
456
456
457 let parents = parse_dirstate_entries(
457 let parents = parse_dirstate_entries(
458 map.on_disk,
458 map.on_disk,
459 |path, entry, copy_source| {
459 |path, entry, copy_source| {
460 let tracked = entry.state().is_tracked();
460 let tracked = entry.state().is_tracked();
461 let node = Self::get_or_insert_node(
461 let node = Self::get_or_insert_node(
462 map.on_disk,
462 map.on_disk,
463 &mut map.unreachable_bytes,
463 &mut map.unreachable_bytes,
464 &mut map.root,
464 &mut map.root,
465 path,
465 path,
466 WithBasename::to_cow_borrowed,
466 WithBasename::to_cow_borrowed,
467 |ancestor| {
467 |ancestor| {
468 if tracked {
468 if tracked {
469 ancestor.tracked_descendants_count += 1
469 ancestor.tracked_descendants_count += 1
470 }
470 }
471 ancestor.descendants_with_entry_count += 1
471 ancestor.descendants_with_entry_count += 1
472 },
472 },
473 )?;
473 )?;
474 assert!(
474 assert!(
475 !node.data.has_entry(),
475 !node.data.has_entry(),
476 "duplicate dirstate entry in read"
476 "duplicate dirstate entry in read"
477 );
477 );
478 assert!(
478 assert!(
479 node.copy_source.is_none(),
479 node.copy_source.is_none(),
480 "duplicate dirstate entry in read"
480 "duplicate dirstate entry in read"
481 );
481 );
482 node.data = NodeData::Entry(*entry);
482 node.data = NodeData::Entry(*entry);
483 node.copy_source = copy_source.map(Cow::Borrowed);
483 node.copy_source = copy_source.map(Cow::Borrowed);
484 map.nodes_with_entry_count += 1;
484 map.nodes_with_entry_count += 1;
485 if copy_source.is_some() {
485 if copy_source.is_some() {
486 map.nodes_with_copy_source_count += 1
486 map.nodes_with_copy_source_count += 1
487 }
487 }
488 Ok(())
488 Ok(())
489 },
489 },
490 )?;
490 )?;
491 let parents = Some(parents.clone());
491 let parents = Some(parents.clone());
492
492
493 Ok((map, parents))
493 Ok((map, parents))
494 }
494 }
495
495
496 /// Assuming dirstate-v2 format, returns whether the next write should
496 /// Assuming dirstate-v2 format, returns whether the next write should
497 /// append to the existing data file that contains `self.on_disk` (true),
497 /// append to the existing data file that contains `self.on_disk` (true),
498 /// or create a new data file from scratch (false).
498 /// or create a new data file from scratch (false).
499 pub(super) fn write_should_append(&self) -> bool {
499 pub(super) fn write_should_append(&self) -> bool {
500 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
500 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
501 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
501 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
502 }
502 }
503
503
504 fn get_node<'tree>(
504 fn get_node<'tree>(
505 &'tree self,
505 &'tree self,
506 path: &HgPath,
506 path: &HgPath,
507 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
507 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
508 let mut children = self.root.as_ref();
508 let mut children = self.root.as_ref();
509 let mut components = path.components();
509 let mut components = path.components();
510 let mut component =
510 let mut component =
511 components.next().expect("expected at least one components");
511 components.next().expect("expected at least one components");
512 loop {
512 loop {
513 if let Some(child) = children.get(component, self.on_disk)? {
513 if let Some(child) = children.get(component, self.on_disk)? {
514 if let Some(next_component) = components.next() {
514 if let Some(next_component) = components.next() {
515 component = next_component;
515 component = next_component;
516 children = child.children(self.on_disk)?;
516 children = child.children(self.on_disk)?;
517 } else {
517 } else {
518 return Ok(Some(child));
518 return Ok(Some(child));
519 }
519 }
520 } else {
520 } else {
521 return Ok(None);
521 return Ok(None);
522 }
522 }
523 }
523 }
524 }
524 }
525
525
526 /// Returns a mutable reference to the node at `path` if it exists
526 /// Returns a mutable reference to the node at `path` if it exists
527 ///
527 ///
528 /// This takes `root` instead of `&mut self` so that callers can mutate
528 /// This takes `root` instead of `&mut self` so that callers can mutate
529 /// other fields while the returned borrow is still valid
529 /// other fields while the returned borrow is still valid
530 fn get_node_mut<'tree>(
530 fn get_node_mut<'tree>(
531 on_disk: &'on_disk [u8],
531 on_disk: &'on_disk [u8],
532 unreachable_bytes: &mut u32,
532 unreachable_bytes: &mut u32,
533 root: &'tree mut ChildNodes<'on_disk>,
533 root: &'tree mut ChildNodes<'on_disk>,
534 path: &HgPath,
534 path: &HgPath,
535 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
535 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
536 let mut children = root;
536 let mut children = root;
537 let mut components = path.components();
537 let mut components = path.components();
538 let mut component =
538 let mut component =
539 components.next().expect("expected at least one components");
539 components.next().expect("expected at least one components");
540 loop {
540 loop {
541 if let Some(child) = children
541 if let Some(child) = children
542 .make_mut(on_disk, unreachable_bytes)?
542 .make_mut(on_disk, unreachable_bytes)?
543 .get_mut(component)
543 .get_mut(component)
544 {
544 {
545 if let Some(next_component) = components.next() {
545 if let Some(next_component) = components.next() {
546 component = next_component;
546 component = next_component;
547 children = &mut child.children;
547 children = &mut child.children;
548 } else {
548 } else {
549 return Ok(Some(child));
549 return Ok(Some(child));
550 }
550 }
551 } else {
551 } else {
552 return Ok(None);
552 return Ok(None);
553 }
553 }
554 }
554 }
555 }
555 }
556
556
557 pub(super) fn get_or_insert<'tree, 'path>(
557 pub(super) fn get_or_insert<'tree, 'path>(
558 &'tree mut self,
558 &'tree mut self,
559 path: &HgPath,
559 path: &HgPath,
560 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
560 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
561 Self::get_or_insert_node(
561 Self::get_or_insert_node(
562 self.on_disk,
562 self.on_disk,
563 &mut self.unreachable_bytes,
563 &mut self.unreachable_bytes,
564 &mut self.root,
564 &mut self.root,
565 path,
565 path,
566 WithBasename::to_cow_owned,
566 WithBasename::to_cow_owned,
567 |_| {},
567 |_| {},
568 )
568 )
569 }
569 }
570
570
571 fn get_or_insert_node<'tree, 'path>(
571 fn get_or_insert_node<'tree, 'path>(
572 on_disk: &'on_disk [u8],
572 on_disk: &'on_disk [u8],
573 unreachable_bytes: &mut u32,
573 unreachable_bytes: &mut u32,
574 root: &'tree mut ChildNodes<'on_disk>,
574 root: &'tree mut ChildNodes<'on_disk>,
575 path: &'path HgPath,
575 path: &'path HgPath,
576 to_cow: impl Fn(
576 to_cow: impl Fn(
577 WithBasename<&'path HgPath>,
577 WithBasename<&'path HgPath>,
578 ) -> WithBasename<Cow<'on_disk, HgPath>>,
578 ) -> WithBasename<Cow<'on_disk, HgPath>>,
579 mut each_ancestor: impl FnMut(&mut Node),
579 mut each_ancestor: impl FnMut(&mut Node),
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
581 let mut child_nodes = root;
581 let mut child_nodes = root;
582 let mut inclusive_ancestor_paths =
582 let mut inclusive_ancestor_paths =
583 WithBasename::inclusive_ancestors_of(path);
583 WithBasename::inclusive_ancestors_of(path);
584 let mut ancestor_path = inclusive_ancestor_paths
584 let mut ancestor_path = inclusive_ancestor_paths
585 .next()
585 .next()
586 .expect("expected at least one inclusive ancestor");
586 .expect("expected at least one inclusive ancestor");
587 loop {
587 loop {
588 // TODO: can we avoid allocating an owned key in cases where the
588 // TODO: can we avoid allocating an owned key in cases where the
589 // map already contains that key, without introducing double
589 // map already contains that key, without introducing double
590 // lookup?
590 // lookup?
591 let child_node = child_nodes
591 let child_node = child_nodes
592 .make_mut(on_disk, unreachable_bytes)?
592 .make_mut(on_disk, unreachable_bytes)?
593 .entry(to_cow(ancestor_path))
593 .entry(to_cow(ancestor_path))
594 .or_default();
594 .or_default();
595 if let Some(next) = inclusive_ancestor_paths.next() {
595 if let Some(next) = inclusive_ancestor_paths.next() {
596 each_ancestor(child_node);
596 each_ancestor(child_node);
597 ancestor_path = next;
597 ancestor_path = next;
598 child_nodes = &mut child_node.children;
598 child_nodes = &mut child_node.children;
599 } else {
599 } else {
600 return Ok(child_node);
600 return Ok(child_node);
601 }
601 }
602 }
602 }
603 }
603 }
604
604
605 fn add_or_remove_file(
605 fn add_or_remove_file(
606 &mut self,
606 &mut self,
607 path: &HgPath,
607 path: &HgPath,
608 old_state: Option<EntryState>,
608 old_state: Option<EntryState>,
609 new_entry: DirstateEntry,
609 new_entry: DirstateEntry,
610 ) -> Result<(), DirstateV2ParseError> {
610 ) -> Result<(), DirstateV2ParseError> {
611 let had_entry = old_state.is_some();
611 let had_entry = old_state.is_some();
612 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
612 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
613 let tracked_count_increment =
613 let tracked_count_increment =
614 match (was_tracked, new_entry.state().is_tracked()) {
614 match (was_tracked, new_entry.state().is_tracked()) {
615 (false, true) => 1,
615 (false, true) => 1,
616 (true, false) => -1,
616 (true, false) => -1,
617 _ => 0,
617 _ => 0,
618 };
618 };
619
619
620 let node = Self::get_or_insert_node(
620 let node = Self::get_or_insert_node(
621 self.on_disk,
621 self.on_disk,
622 &mut self.unreachable_bytes,
622 &mut self.unreachable_bytes,
623 &mut self.root,
623 &mut self.root,
624 path,
624 path,
625 WithBasename::to_cow_owned,
625 WithBasename::to_cow_owned,
626 |ancestor| {
626 |ancestor| {
627 if !had_entry {
627 if !had_entry {
628 ancestor.descendants_with_entry_count += 1;
628 ancestor.descendants_with_entry_count += 1;
629 }
629 }
630
630
631 // We can’t use `+= increment` because the counter is unsigned,
631 // We can’t use `+= increment` because the counter is unsigned,
632 // and we want debug builds to detect accidental underflow
632 // and we want debug builds to detect accidental underflow
633 // through zero
633 // through zero
634 match tracked_count_increment {
634 match tracked_count_increment {
635 1 => ancestor.tracked_descendants_count += 1,
635 1 => ancestor.tracked_descendants_count += 1,
636 -1 => ancestor.tracked_descendants_count -= 1,
636 -1 => ancestor.tracked_descendants_count -= 1,
637 _ => {}
637 _ => {}
638 }
638 }
639 },
639 },
640 )?;
640 )?;
641 if !had_entry {
641 if !had_entry {
642 self.nodes_with_entry_count += 1
642 self.nodes_with_entry_count += 1
643 }
643 }
644 node.data = NodeData::Entry(new_entry);
644 node.data = NodeData::Entry(new_entry);
645 Ok(())
645 Ok(())
646 }
646 }
647
647
648 fn iter_nodes<'tree>(
648 fn iter_nodes<'tree>(
649 &'tree self,
649 &'tree self,
650 ) -> impl Iterator<
650 ) -> impl Iterator<
651 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
651 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
652 > + 'tree {
652 > + 'tree {
653 // Depth first tree traversal.
653 // Depth first tree traversal.
654 //
654 //
655 // If we could afford internal iteration and recursion,
655 // If we could afford internal iteration and recursion,
656 // this would look like:
656 // this would look like:
657 //
657 //
658 // ```
658 // ```
659 // fn traverse_children(
659 // fn traverse_children(
660 // children: &ChildNodes,
660 // children: &ChildNodes,
661 // each: &mut impl FnMut(&Node),
661 // each: &mut impl FnMut(&Node),
662 // ) {
662 // ) {
663 // for child in children.values() {
663 // for child in children.values() {
664 // traverse_children(&child.children, each);
664 // traverse_children(&child.children, each);
665 // each(child);
665 // each(child);
666 // }
666 // }
667 // }
667 // }
668 // ```
668 // ```
669 //
669 //
670 // However we want an external iterator and therefore can’t use the
670 // However we want an external iterator and therefore can’t use the
671 // call stack. Use an explicit stack instead:
671 // call stack. Use an explicit stack instead:
672 let mut stack = Vec::new();
672 let mut stack = Vec::new();
673 let mut iter = self.root.as_ref().iter();
673 let mut iter = self.root.as_ref().iter();
674 std::iter::from_fn(move || {
674 std::iter::from_fn(move || {
675 while let Some(child_node) = iter.next() {
675 while let Some(child_node) = iter.next() {
676 let children = match child_node.children(self.on_disk) {
676 let children = match child_node.children(self.on_disk) {
677 Ok(children) => children,
677 Ok(children) => children,
678 Err(error) => return Some(Err(error)),
678 Err(error) => return Some(Err(error)),
679 };
679 };
680 // Pseudo-recursion
680 // Pseudo-recursion
681 let new_iter = children.iter();
681 let new_iter = children.iter();
682 let old_iter = std::mem::replace(&mut iter, new_iter);
682 let old_iter = std::mem::replace(&mut iter, new_iter);
683 stack.push((child_node, old_iter));
683 stack.push((child_node, old_iter));
684 }
684 }
685 // Found the end of a `children.iter()` iterator.
685 // Found the end of a `children.iter()` iterator.
686 if let Some((child_node, next_iter)) = stack.pop() {
686 if let Some((child_node, next_iter)) = stack.pop() {
687 // "Return" from pseudo-recursion by restoring state from the
687 // "Return" from pseudo-recursion by restoring state from the
688 // explicit stack
688 // explicit stack
689 iter = next_iter;
689 iter = next_iter;
690
690
691 Some(Ok(child_node))
691 Some(Ok(child_node))
692 } else {
692 } else {
693 // Reached the bottom of the stack, we’re done
693 // Reached the bottom of the stack, we’re done
694 None
694 None
695 }
695 }
696 })
696 })
697 }
697 }
698
698
699 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
699 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
700 if let Cow::Borrowed(path) = path {
700 if let Cow::Borrowed(path) = path {
701 *unreachable_bytes += path.len() as u32
701 *unreachable_bytes += path.len() as u32
702 }
702 }
703 }
703 }
704 }
704 }
705
705
706 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
706 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
707 ///
707 ///
708 /// The callback is only called for incoming `Ok` values. Errors are passed
708 /// The callback is only called for incoming `Ok` values. Errors are passed
709 /// through as-is. In order to let it use the `?` operator the callback is
709 /// through as-is. In order to let it use the `?` operator the callback is
710 /// expected to return a `Result` of `Option`, instead of an `Option` of
710 /// expected to return a `Result` of `Option`, instead of an `Option` of
711 /// `Result`.
711 /// `Result`.
712 fn filter_map_results<'a, I, F, A, B, E>(
712 fn filter_map_results<'a, I, F, A, B, E>(
713 iter: I,
713 iter: I,
714 f: F,
714 f: F,
715 ) -> impl Iterator<Item = Result<B, E>> + 'a
715 ) -> impl Iterator<Item = Result<B, E>> + 'a
716 where
716 where
717 I: Iterator<Item = Result<A, E>> + 'a,
717 I: Iterator<Item = Result<A, E>> + 'a,
718 F: Fn(A) -> Result<Option<B>, E> + 'a,
718 F: Fn(A) -> Result<Option<B>, E> + 'a,
719 {
719 {
720 iter.filter_map(move |result| match result {
720 iter.filter_map(move |result| match result {
721 Ok(node) => f(node).transpose(),
721 Ok(node) => f(node).transpose(),
722 Err(e) => Some(Err(e)),
722 Err(e) => Some(Err(e)),
723 })
723 })
724 }
724 }
725
725
726 impl OwningDirstateMap {
726 impl OwningDirstateMap {
727 pub fn clear(&mut self) {
727 pub fn clear(&mut self) {
728 let map = self.get_map_mut();
728 self.with_dmap_mut(|map| {
729 map.root = Default::default();
729 map.root = Default::default();
730 map.nodes_with_entry_count = 0;
730 map.nodes_with_entry_count = 0;
731 map.nodes_with_copy_source_count = 0;
731 map.nodes_with_copy_source_count = 0;
732 });
732 }
733 }
733
734
734 pub fn set_entry(
735 pub fn set_entry(
735 &mut self,
736 &mut self,
736 filename: &HgPath,
737 filename: &HgPath,
737 entry: DirstateEntry,
738 entry: DirstateEntry,
738 ) -> Result<(), DirstateV2ParseError> {
739 ) -> Result<(), DirstateV2ParseError> {
739 let map = self.get_map_mut();
740 self.with_dmap_mut(|map| {
740 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
741 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
741 Ok(())
742 Ok(())
743 })
742 }
744 }
743
745
744 pub fn add_file(
746 pub fn add_file(
745 &mut self,
747 &mut self,
746 filename: &HgPath,
748 filename: &HgPath,
747 entry: DirstateEntry,
749 entry: DirstateEntry,
748 ) -> Result<(), DirstateError> {
750 ) -> Result<(), DirstateError> {
749 let old_state = self.get(filename)?.map(|e| e.state());
751 let old_state = self.get(filename)?.map(|e| e.state());
750 let map = self.get_map_mut();
752 self.with_dmap_mut(|map| {
751 Ok(map.add_or_remove_file(filename, old_state, entry)?)
753 Ok(map.add_or_remove_file(filename, old_state, entry)?)
754 })
752 }
755 }
753
756
754 pub fn remove_file(
757 pub fn remove_file(
755 &mut self,
758 &mut self,
756 filename: &HgPath,
759 filename: &HgPath,
757 in_merge: bool,
760 in_merge: bool,
758 ) -> Result<(), DirstateError> {
761 ) -> Result<(), DirstateError> {
759 let old_entry_opt = self.get(filename)?;
762 let old_entry_opt = self.get(filename)?;
760 let old_state = old_entry_opt.map(|e| e.state());
763 let old_state = old_entry_opt.map(|e| e.state());
761 let mut size = 0;
764 let mut size = 0;
762 if in_merge {
765 if in_merge {
763 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
766 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
764 // during a merge. So I (marmoute) am not sure we need the
767 // during a merge. So I (marmoute) am not sure we need the
765 // conditionnal at all. Adding double checking this with assert
768 // conditionnal at all. Adding double checking this with assert
766 // would be nice.
769 // would be nice.
767 if let Some(old_entry) = old_entry_opt {
770 if let Some(old_entry) = old_entry_opt {
768 // backup the previous state
771 // backup the previous state
769 if old_entry.state() == EntryState::Merged {
772 if old_entry.state() == EntryState::Merged {
770 size = SIZE_NON_NORMAL;
773 size = SIZE_NON_NORMAL;
771 } else if old_entry.state() == EntryState::Normal
774 } else if old_entry.state() == EntryState::Normal
772 && old_entry.size() == SIZE_FROM_OTHER_PARENT
775 && old_entry.size() == SIZE_FROM_OTHER_PARENT
773 {
776 {
774 // other parent
777 // other parent
775 size = SIZE_FROM_OTHER_PARENT;
778 size = SIZE_FROM_OTHER_PARENT;
776 }
779 }
777 }
780 }
778 }
781 }
779 if size == 0 {
782 if size == 0 {
780 self.copy_map_remove(filename)?;
783 self.copy_map_remove(filename)?;
781 }
784 }
782 let map = self.get_map_mut();
785 self.with_dmap_mut(|map| {
783 let entry = DirstateEntry::new_removed(size);
786 let entry = DirstateEntry::new_removed(size);
784 Ok(map.add_or_remove_file(filename, old_state, entry)?)
787 Ok(map.add_or_remove_file(filename, old_state, entry)?)
788 })
785 }
789 }
786
790
787 pub fn drop_entry_and_copy_source(
791 pub fn drop_entry_and_copy_source(
788 &mut self,
792 &mut self,
789 filename: &HgPath,
793 filename: &HgPath,
790 ) -> Result<(), DirstateError> {
794 ) -> Result<(), DirstateError> {
791 let was_tracked = self
795 let was_tracked = self
792 .get(filename)?
796 .get(filename)?
793 .map_or(false, |e| e.state().is_tracked());
797 .map_or(false, |e| e.state().is_tracked());
794 let map = self.get_map_mut();
795 struct Dropped {
798 struct Dropped {
796 was_tracked: bool,
799 was_tracked: bool,
797 had_entry: bool,
800 had_entry: bool,
798 had_copy_source: bool,
801 had_copy_source: bool,
799 }
802 }
800
803
801 /// If this returns `Ok(Some((dropped, removed)))`, then
804 /// If this returns `Ok(Some((dropped, removed)))`, then
802 ///
805 ///
803 /// * `dropped` is about the leaf node that was at `filename`
806 /// * `dropped` is about the leaf node that was at `filename`
804 /// * `removed` is whether this particular level of recursion just
807 /// * `removed` is whether this particular level of recursion just
805 /// removed a node in `nodes`.
808 /// removed a node in `nodes`.
806 fn recur<'on_disk>(
809 fn recur<'on_disk>(
807 on_disk: &'on_disk [u8],
810 on_disk: &'on_disk [u8],
808 unreachable_bytes: &mut u32,
811 unreachable_bytes: &mut u32,
809 nodes: &mut ChildNodes<'on_disk>,
812 nodes: &mut ChildNodes<'on_disk>,
810 path: &HgPath,
813 path: &HgPath,
811 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
814 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
812 let (first_path_component, rest_of_path) =
815 let (first_path_component, rest_of_path) =
813 path.split_first_component();
816 path.split_first_component();
814 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
817 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
815 let node = if let Some(node) = nodes.get_mut(first_path_component)
818 let node = if let Some(node) = nodes.get_mut(first_path_component)
816 {
819 {
817 node
820 node
818 } else {
821 } else {
819 return Ok(None);
822 return Ok(None);
820 };
823 };
821 let dropped;
824 let dropped;
822 if let Some(rest) = rest_of_path {
825 if let Some(rest) = rest_of_path {
823 if let Some((d, removed)) = recur(
826 if let Some((d, removed)) = recur(
824 on_disk,
827 on_disk,
825 unreachable_bytes,
828 unreachable_bytes,
826 &mut node.children,
829 &mut node.children,
827 rest,
830 rest,
828 )? {
831 )? {
829 dropped = d;
832 dropped = d;
830 if dropped.had_entry {
833 if dropped.had_entry {
831 node.descendants_with_entry_count -= 1;
834 node.descendants_with_entry_count -= 1;
832 }
835 }
833 if dropped.was_tracked {
836 if dropped.was_tracked {
834 node.tracked_descendants_count -= 1;
837 node.tracked_descendants_count -= 1;
835 }
838 }
836
839
837 // Directory caches must be invalidated when removing a
840 // Directory caches must be invalidated when removing a
838 // child node
841 // child node
839 if removed {
842 if removed {
840 if let NodeData::CachedDirectory { .. } = &node.data {
843 if let NodeData::CachedDirectory { .. } = &node.data {
841 node.data = NodeData::None
844 node.data = NodeData::None
842 }
845 }
843 }
846 }
844 } else {
847 } else {
845 return Ok(None);
848 return Ok(None);
846 }
849 }
847 } else {
850 } else {
848 let had_entry = node.data.has_entry();
851 let had_entry = node.data.has_entry();
849 if had_entry {
852 if had_entry {
850 node.data = NodeData::None
853 node.data = NodeData::None
851 }
854 }
852 if let Some(source) = &node.copy_source {
855 if let Some(source) = &node.copy_source {
853 DirstateMap::count_dropped_path(unreachable_bytes, source);
856 DirstateMap::count_dropped_path(unreachable_bytes, source);
854 node.copy_source = None
857 node.copy_source = None
855 }
858 }
856 dropped = Dropped {
859 dropped = Dropped {
857 was_tracked: node
860 was_tracked: node
858 .data
861 .data
859 .as_entry()
862 .as_entry()
860 .map_or(false, |entry| entry.state().is_tracked()),
863 .map_or(false, |entry| entry.state().is_tracked()),
861 had_entry,
864 had_entry,
862 had_copy_source: node.copy_source.take().is_some(),
865 had_copy_source: node.copy_source.take().is_some(),
863 };
866 };
864 }
867 }
865 // After recursion, for both leaf (rest_of_path is None) nodes and
868 // After recursion, for both leaf (rest_of_path is None) nodes and
866 // parent nodes, remove a node if it just became empty.
869 // parent nodes, remove a node if it just became empty.
867 let remove = !node.data.has_entry()
870 let remove = !node.data.has_entry()
868 && node.copy_source.is_none()
871 && node.copy_source.is_none()
869 && node.children.is_empty();
872 && node.children.is_empty();
870 if remove {
873 if remove {
871 let (key, _) =
874 let (key, _) =
872 nodes.remove_entry(first_path_component).unwrap();
875 nodes.remove_entry(first_path_component).unwrap();
873 DirstateMap::count_dropped_path(
876 DirstateMap::count_dropped_path(
874 unreachable_bytes,
877 unreachable_bytes,
875 key.full_path(),
878 key.full_path(),
876 )
879 )
877 }
880 }
878 Ok(Some((dropped, remove)))
881 Ok(Some((dropped, remove)))
879 }
882 }
880
883
881 if let Some((dropped, _removed)) = recur(
884 self.with_dmap_mut(|map| {
882 map.on_disk,
885 if let Some((dropped, _removed)) = recur(
883 &mut map.unreachable_bytes,
886 map.on_disk,
884 &mut map.root,
887 &mut map.unreachable_bytes,
885 filename,
888 &mut map.root,
886 )? {
889 filename,
887 if dropped.had_entry {
890 )? {
888 map.nodes_with_entry_count -= 1
891 if dropped.had_entry {
892 map.nodes_with_entry_count -= 1
893 }
894 if dropped.had_copy_source {
895 map.nodes_with_copy_source_count -= 1
896 }
897 } else {
898 debug_assert!(!was_tracked);
889 }
899 }
890 if dropped.had_copy_source {
900 Ok(())
891 map.nodes_with_copy_source_count -= 1
901 })
892 }
893 } else {
894 debug_assert!(!was_tracked);
895 }
896 Ok(())
897 }
902 }
898
903
899 pub fn has_tracked_dir(
904 pub fn has_tracked_dir(
900 &mut self,
905 &mut self,
901 directory: &HgPath,
906 directory: &HgPath,
902 ) -> Result<bool, DirstateError> {
907 ) -> Result<bool, DirstateError> {
903 let map = self.get_map_mut();
908 self.with_dmap_mut(|map| {
904 if let Some(node) = map.get_node(directory)? {
909 if let Some(node) = map.get_node(directory)? {
905 // A node without a `DirstateEntry` was created to hold child
910 // A node without a `DirstateEntry` was created to hold child
906 // nodes, and is therefore a directory.
911 // nodes, and is therefore a directory.
907 let state = node.state()?;
912 let state = node.state()?;
908 Ok(state.is_none() && node.tracked_descendants_count() > 0)
913 Ok(state.is_none() && node.tracked_descendants_count() > 0)
909 } else {
914 } else {
910 Ok(false)
915 Ok(false)
911 }
916 }
917 })
912 }
918 }
913
919
914 pub fn has_dir(
920 pub fn has_dir(
915 &mut self,
921 &mut self,
916 directory: &HgPath,
922 directory: &HgPath,
917 ) -> Result<bool, DirstateError> {
923 ) -> Result<bool, DirstateError> {
918 let map = self.get_map_mut();
924 self.with_dmap_mut(|map| {
919 if let Some(node) = map.get_node(directory)? {
925 if let Some(node) = map.get_node(directory)? {
920 // A node without a `DirstateEntry` was created to hold child
926 // A node without a `DirstateEntry` was created to hold child
921 // nodes, and is therefore a directory.
927 // nodes, and is therefore a directory.
922 let state = node.state()?;
928 let state = node.state()?;
923 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
929 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
924 } else {
930 } else {
925 Ok(false)
931 Ok(false)
926 }
932 }
933 })
927 }
934 }
928
935
929 #[timed]
936 #[timed]
930 pub fn pack_v1(
937 pub fn pack_v1(
931 &self,
938 &self,
932 parents: DirstateParents,
939 parents: DirstateParents,
933 ) -> Result<Vec<u8>, DirstateError> {
940 ) -> Result<Vec<u8>, DirstateError> {
934 let map = self.get_map();
941 let map = self.get_map();
935 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
942 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
936 // reallocations
943 // reallocations
937 let mut size = parents.as_bytes().len();
944 let mut size = parents.as_bytes().len();
938 for node in map.iter_nodes() {
945 for node in map.iter_nodes() {
939 let node = node?;
946 let node = node?;
940 if node.entry()?.is_some() {
947 if node.entry()?.is_some() {
941 size += packed_entry_size(
948 size += packed_entry_size(
942 node.full_path(map.on_disk)?,
949 node.full_path(map.on_disk)?,
943 node.copy_source(map.on_disk)?,
950 node.copy_source(map.on_disk)?,
944 );
951 );
945 }
952 }
946 }
953 }
947
954
948 let mut packed = Vec::with_capacity(size);
955 let mut packed = Vec::with_capacity(size);
949 packed.extend(parents.as_bytes());
956 packed.extend(parents.as_bytes());
950
957
951 for node in map.iter_nodes() {
958 for node in map.iter_nodes() {
952 let node = node?;
959 let node = node?;
953 if let Some(entry) = node.entry()? {
960 if let Some(entry) = node.entry()? {
954 pack_entry(
961 pack_entry(
955 node.full_path(map.on_disk)?,
962 node.full_path(map.on_disk)?,
956 &entry,
963 &entry,
957 node.copy_source(map.on_disk)?,
964 node.copy_source(map.on_disk)?,
958 &mut packed,
965 &mut packed,
959 );
966 );
960 }
967 }
961 }
968 }
962 Ok(packed)
969 Ok(packed)
963 }
970 }
964
971
965 /// Returns new data and metadata together with whether that data should be
972 /// Returns new data and metadata together with whether that data should be
966 /// appended to the existing data file whose content is at
973 /// appended to the existing data file whose content is at
967 /// `map.on_disk` (true), instead of written to a new data file
974 /// `map.on_disk` (true), instead of written to a new data file
968 /// (false).
975 /// (false).
969 #[timed]
976 #[timed]
970 pub fn pack_v2(
977 pub fn pack_v2(
971 &self,
978 &self,
972 can_append: bool,
979 can_append: bool,
973 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
980 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
974 let map = self.get_map();
981 let map = self.get_map();
975 on_disk::write(map, can_append)
982 on_disk::write(map, can_append)
976 }
983 }
977
984
978 pub fn status<'a>(
985 /// `callback` allows the caller to process and do something with the
979 &'a mut self,
986 /// results of the status. This is needed to do so efficiently (i.e.
980 matcher: &'a (dyn Matcher + Sync),
987 /// without cloning the `DirstateStatus` object with its paths) because
988 /// we need to borrow from `Self`.
989 pub fn with_status<R>(
990 &mut self,
991 matcher: &(dyn Matcher + Sync),
981 root_dir: PathBuf,
992 root_dir: PathBuf,
982 ignore_files: Vec<PathBuf>,
993 ignore_files: Vec<PathBuf>,
983 options: StatusOptions,
994 options: StatusOptions,
984 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
995 callback: impl for<'r> FnOnce(
985 {
996 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
986 let map = self.get_map_mut();
997 ) -> R,
987 super::status::status(map, matcher, root_dir, ignore_files, options)
998 ) -> R {
999 self.with_dmap_mut(|map| {
1000 callback(super::status::status(
1001 map,
1002 matcher,
1003 root_dir,
1004 ignore_files,
1005 options,
1006 ))
1007 })
988 }
1008 }
989
1009
990 pub fn copy_map_len(&self) -> usize {
1010 pub fn copy_map_len(&self) -> usize {
991 let map = self.get_map();
1011 let map = self.get_map();
992 map.nodes_with_copy_source_count as usize
1012 map.nodes_with_copy_source_count as usize
993 }
1013 }
994
1014
995 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1015 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
996 let map = self.get_map();
1016 let map = self.get_map();
997 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1017 Box::new(filter_map_results(map.iter_nodes(), move |node| {
998 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1018 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
999 Some((node.full_path(map.on_disk)?, source))
1019 Some((node.full_path(map.on_disk)?, source))
1000 } else {
1020 } else {
1001 None
1021 None
1002 })
1022 })
1003 }))
1023 }))
1004 }
1024 }
1005
1025
1006 pub fn copy_map_contains_key(
1026 pub fn copy_map_contains_key(
1007 &self,
1027 &self,
1008 key: &HgPath,
1028 key: &HgPath,
1009 ) -> Result<bool, DirstateV2ParseError> {
1029 ) -> Result<bool, DirstateV2ParseError> {
1010 let map = self.get_map();
1030 let map = self.get_map();
1011 Ok(if let Some(node) = map.get_node(key)? {
1031 Ok(if let Some(node) = map.get_node(key)? {
1012 node.has_copy_source()
1032 node.has_copy_source()
1013 } else {
1033 } else {
1014 false
1034 false
1015 })
1035 })
1016 }
1036 }
1017
1037
1018 pub fn copy_map_get(
1038 pub fn copy_map_get(
1019 &self,
1039 &self,
1020 key: &HgPath,
1040 key: &HgPath,
1021 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1041 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1022 let map = self.get_map();
1042 let map = self.get_map();
1023 if let Some(node) = map.get_node(key)? {
1043 if let Some(node) = map.get_node(key)? {
1024 if let Some(source) = node.copy_source(map.on_disk)? {
1044 if let Some(source) = node.copy_source(map.on_disk)? {
1025 return Ok(Some(source));
1045 return Ok(Some(source));
1026 }
1046 }
1027 }
1047 }
1028 Ok(None)
1048 Ok(None)
1029 }
1049 }
1030
1050
1031 pub fn copy_map_remove(
1051 pub fn copy_map_remove(
1032 &mut self,
1052 &mut self,
1033 key: &HgPath,
1053 key: &HgPath,
1034 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1054 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1035 let map = self.get_map_mut();
1055 self.with_dmap_mut(|map| {
1036 let count = &mut map.nodes_with_copy_source_count;
1056 let count = &mut map.nodes_with_copy_source_count;
1037 let unreachable_bytes = &mut map.unreachable_bytes;
1057 let unreachable_bytes = &mut map.unreachable_bytes;
1038 Ok(DirstateMap::get_node_mut(
1058 Ok(DirstateMap::get_node_mut(
1039 map.on_disk,
1059 map.on_disk,
1040 unreachable_bytes,
1060 unreachable_bytes,
1041 &mut map.root,
1061 &mut map.root,
1042 key,
1062 key,
1043 )?
1063 )?
1044 .and_then(|node| {
1064 .and_then(|node| {
1045 if let Some(source) = &node.copy_source {
1065 if let Some(source) = &node.copy_source {
1046 *count -= 1;
1066 *count -= 1;
1047 DirstateMap::count_dropped_path(unreachable_bytes, source);
1067 DirstateMap::count_dropped_path(unreachable_bytes, source);
1048 }
1068 }
1049 node.copy_source.take().map(Cow::into_owned)
1069 node.copy_source.take().map(Cow::into_owned)
1050 }))
1070 }))
1071 })
1051 }
1072 }
1052
1073
1053 pub fn copy_map_insert(
1074 pub fn copy_map_insert(
1054 &mut self,
1075 &mut self,
1055 key: HgPathBuf,
1076 key: HgPathBuf,
1056 value: HgPathBuf,
1077 value: HgPathBuf,
1057 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1078 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1058 let map = self.get_map_mut();
1079 self.with_dmap_mut(|map| {
1059 let node = DirstateMap::get_or_insert_node(
1080 let node = DirstateMap::get_or_insert_node(
1060 map.on_disk,
1081 map.on_disk,
1061 &mut map.unreachable_bytes,
1082 &mut map.unreachable_bytes,
1062 &mut map.root,
1083 &mut map.root,
1063 &key,
1084 &key,
1064 WithBasename::to_cow_owned,
1085 WithBasename::to_cow_owned,
1065 |_ancestor| {},
1086 |_ancestor| {},
1066 )?;
1087 )?;
1067 if node.copy_source.is_none() {
1088 if node.copy_source.is_none() {
1068 map.nodes_with_copy_source_count += 1
1089 map.nodes_with_copy_source_count += 1
1069 }
1090 }
1070 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1091 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1092 })
1071 }
1093 }
1072
1094
1073 pub fn len(&self) -> usize {
1095 pub fn len(&self) -> usize {
1074 let map = self.get_map();
1096 let map = self.get_map();
1075 map.nodes_with_entry_count as usize
1097 map.nodes_with_entry_count as usize
1076 }
1098 }
1077
1099
1078 pub fn contains_key(
1100 pub fn contains_key(
1079 &self,
1101 &self,
1080 key: &HgPath,
1102 key: &HgPath,
1081 ) -> Result<bool, DirstateV2ParseError> {
1103 ) -> Result<bool, DirstateV2ParseError> {
1082 Ok(self.get(key)?.is_some())
1104 Ok(self.get(key)?.is_some())
1083 }
1105 }
1084
1106
1085 pub fn get(
1107 pub fn get(
1086 &self,
1108 &self,
1087 key: &HgPath,
1109 key: &HgPath,
1088 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1110 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1089 let map = self.get_map();
1111 let map = self.get_map();
1090 Ok(if let Some(node) = map.get_node(key)? {
1112 Ok(if let Some(node) = map.get_node(key)? {
1091 node.entry()?
1113 node.entry()?
1092 } else {
1114 } else {
1093 None
1115 None
1094 })
1116 })
1095 }
1117 }
1096
1118
1097 pub fn iter(&self) -> StateMapIter<'_> {
1119 pub fn iter(&self) -> StateMapIter<'_> {
1098 let map = self.get_map();
1120 let map = self.get_map();
1099 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1121 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1100 Ok(if let Some(entry) = node.entry()? {
1122 Ok(if let Some(entry) = node.entry()? {
1101 Some((node.full_path(map.on_disk)?, entry))
1123 Some((node.full_path(map.on_disk)?, entry))
1102 } else {
1124 } else {
1103 None
1125 None
1104 })
1126 })
1105 }))
1127 }))
1106 }
1128 }
1107
1129
1108 pub fn iter_tracked_dirs(
1130 pub fn iter_tracked_dirs(
1109 &mut self,
1131 &mut self,
1110 ) -> Result<
1132 ) -> Result<
1111 Box<
1133 Box<
1112 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1134 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1113 + Send
1135 + Send
1114 + '_,
1136 + '_,
1115 >,
1137 >,
1116 DirstateError,
1138 DirstateError,
1117 > {
1139 > {
1118 let map = self.get_map_mut();
1140 let map = self.get_map();
1119 let on_disk = map.on_disk;
1141 let on_disk = map.on_disk;
1120 Ok(Box::new(filter_map_results(
1142 Ok(Box::new(filter_map_results(
1121 map.iter_nodes(),
1143 map.iter_nodes(),
1122 move |node| {
1144 move |node| {
1123 Ok(if node.tracked_descendants_count() > 0 {
1145 Ok(if node.tracked_descendants_count() > 0 {
1124 Some(node.full_path(on_disk)?)
1146 Some(node.full_path(on_disk)?)
1125 } else {
1147 } else {
1126 None
1148 None
1127 })
1149 })
1128 },
1150 },
1129 )))
1151 )))
1130 }
1152 }
1131
1153
1132 pub fn debug_iter(
1154 pub fn debug_iter(
1133 &self,
1155 &self,
1134 all: bool,
1156 all: bool,
1135 ) -> Box<
1157 ) -> Box<
1136 dyn Iterator<
1158 dyn Iterator<
1137 Item = Result<
1159 Item = Result<
1138 (&HgPath, (u8, i32, i32, i32)),
1160 (&HgPath, (u8, i32, i32, i32)),
1139 DirstateV2ParseError,
1161 DirstateV2ParseError,
1140 >,
1162 >,
1141 > + Send
1163 > + Send
1142 + '_,
1164 + '_,
1143 > {
1165 > {
1144 let map = self.get_map();
1166 let map = self.get_map();
1145 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1167 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1146 let debug_tuple = if let Some(entry) = node.entry()? {
1168 let debug_tuple = if let Some(entry) = node.entry()? {
1147 entry.debug_tuple()
1169 entry.debug_tuple()
1148 } else if !all {
1170 } else if !all {
1149 return Ok(None);
1171 return Ok(None);
1150 } else if let Some(mtime) = node.cached_directory_mtime()? {
1172 } else if let Some(mtime) = node.cached_directory_mtime()? {
1151 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1173 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1152 } else {
1174 } else {
1153 (b' ', 0, -1, -1)
1175 (b' ', 0, -1, -1)
1154 };
1176 };
1155 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1177 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1156 }))
1178 }))
1157 }
1179 }
1158 }
1180 }
@@ -1,142 +1,89 b''
1 use crate::{DirstateError, DirstateParents};
2
1 use super::dirstate_map::DirstateMap;
3 use super::dirstate_map::DirstateMap;
2 use stable_deref_trait::StableDeref;
3 use std::ops::Deref;
4 use std::ops::Deref;
4
5
5 /*
6 use ouroboros::self_referencing;
6 // /!\ This is unsound and can cause use after free. It will be fixed in the
7 // next patch
8
9 // If we change `value` from its current use of `HgPathBuf` to `&HgPath`,
10 // nothing here tells that `value` will outlive `OwningDirstateMap`
11 pub fn copy_map_insert<'a,'owned>(
12 &'owned mut self,
13 key: &HgPath,
14 value: &'a HgPath,
15 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
16 // `'local` is smaller than `'a` here
17 let map: &'local mut DirstateMap<'local> = self.get_map_mut();
18 let node: &'local mut Node<'local> = DirstateMap::get_or_insert_node(
19 map.on_disk,
20 &mut map.unreachable_bytes,
21 &mut map.root,
22 &key,
23 WithBasename::to_cow_owned,
24 |_ancestor| {},
25 )?;
26 if node.copy_source.is_none() {
27 map.nodes_with_copy_source_count += 1
28 }
29 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
30 // and right here ----------^^^^^^^^^^^^
31 // we are storing `&'a HgPath` in `Node<'local>` which is possible
32 // because to the compiler, `'a` is longer than ``local`.
33 // It is wrong because nothing proves that `&'a HgPath` will outlive `self`.
34 }
35
36 // All of this is caused by the wrong cast of the DirstateMap pointer that
37 // fakes the lifetime of `DirstateMap` and ensures the compiler that it lives
38 // as long as `on_disk`, which is only true for its immutable data.
39 // This will be fixed in the next commit.
40 */
41
7
42 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
43 /// borrows.
9 /// borrows.
44 ///
10 #[self_referencing]
45 /// This is similar to [`OwningRef`] which is more limited because it
46 /// represents exactly one `&T` reference next to the value it borrows, as
47 /// opposed to a struct that may contain an arbitrary number of references in
48 /// arbitrarily-nested data structures.
49 ///
50 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
51 pub struct OwningDirstateMap {
11 pub struct OwningDirstateMap {
52 /// Owned handle to a bytes buffer with a stable address.
53 ///
54 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
55 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
56
13 #[borrows(on_disk)]
57 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
14 #[covariant]
58 /// language cannot represent a lifetime referencing a sibling field.
15 map: DirstateMap<'this>,
59 /// This is not quite a self-referencial struct (moving this struct is not
60 /// a problem as it doesn’t change the address of the bytes buffer owned
61 /// by `on_disk`) but touches similar borrow-checker limitations.
62 ptr: *mut (),
63 }
16 }
64
17
65 impl OwningDirstateMap {
18 impl OwningDirstateMap {
66 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
67 where
20 where
68 OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
21 OnDisk: Deref<Target = [u8]> + Send + 'static,
69 {
22 {
70 let on_disk = Box::new(on_disk);
23 let on_disk = Box::new(on_disk);
71 let bytes: &'_ [u8] = &on_disk;
72 let map = DirstateMap::empty(bytes);
73
24
74 // Like in `bytes` above, this `'_` lifetime parameter borrows from
25 OwningDirstateMapBuilder {
75 // the bytes buffer owned by `on_disk`.
26 on_disk,
76 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
27 map_builder: |bytes| DirstateMap::empty(&bytes),
77
28 }
78 // Erase the pointed type entirely in order to erase the lifetime.
29 .build()
79 let ptr: *mut () = ptr.cast();
80
81 Self { on_disk, ptr }
82 }
30 }
83
31
84 pub fn get_pair_mut<'a>(
32 pub fn new_v1<OnDisk>(
85 &'a mut self,
33 on_disk: OnDisk,
86 ) -> (&'a [u8], &'a mut DirstateMap<'a>) {
34 ) -> Result<(Self, DirstateParents), DirstateError>
87 // SAFETY: We cast the type-erased pointer back to the same type it had
35 where
88 // in `new`, except with a different lifetime parameter. This time we
36 OnDisk: Deref<Target = [u8]> + Send + 'static,
89 // connect the lifetime to that of `self`. This cast is valid because
37 {
90 // `self` owns the same `on_disk` whose buffer `DirstateMap`
38 let on_disk = Box::new(on_disk);
91 // references. That buffer has a stable memory address because our
39 let mut parents = DirstateParents::NULL;
92 // `Self::new_empty` counstructor requires `StableDeref`.
93 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
94 // SAFETY: we dereference that pointer, connecting the lifetime of the
95 // new `&mut` to that of `self`. This is valid because the
96 // raw pointer is to a boxed value, and `self` owns that box.
97 (&self.on_disk, unsafe { &mut *ptr })
98 }
99
40
100 pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
41 Ok((
101 self.get_pair_mut().1
42 OwningDirstateMapTryBuilder {
43 on_disk,
44 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 parents = p.unwrap_or(DirstateParents::NULL);
47 dmap
48 })
49 },
50 }
51 .try_build()?,
52 parents,
53 ))
102 }
54 }
103
55
104 pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> {
56 pub fn new_v2<OnDisk>(
105 // SAFETY: same reasoning as in `get_pair_mut` above.
57 on_disk: OnDisk,
106 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
58 data_size: usize,
107 unsafe { &*ptr }
59 metadata: &[u8],
60 ) -> Result<Self, DirstateError>
61 where
62 OnDisk: Deref<Target = [u8]> + Send + 'static,
63 {
64 let on_disk = Box::new(on_disk);
65
66 OwningDirstateMapTryBuilder {
67 on_disk,
68 map_builder: |bytes| {
69 DirstateMap::new_v2(&bytes, data_size, metadata)
70 },
71 }
72 .try_build()
108 }
73 }
109
74
110 pub fn on_disk<'a>(&'a self) -> &'a [u8] {
75 pub fn with_dmap_mut<R>(
111 &self.on_disk
76 &mut self,
77 f: impl FnOnce(&mut DirstateMap) -> R,
78 ) -> R {
79 self.with_map_mut(f)
80 }
81
82 pub fn get_map(&self) -> &DirstateMap {
83 self.borrow_map()
84 }
85
86 pub fn on_disk(&self) -> &[u8] {
87 self.borrow_on_disk()
112 }
88 }
113 }
89 }
114
115 impl Drop for OwningDirstateMap {
116 fn drop(&mut self) {
117 // Silence a "field is never read" warning, and demonstrate that this
118 // value is still alive.
119 let _: &Box<dyn Deref<Target = [u8]> + Send> = &self.on_disk;
120 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
121 // same reason. `self.on_disk` still exists at this point, drop glue
122 // will drop it implicitly after this `drop` method returns.
123 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
124 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
125 // This is fine because drop glue does nothing for `*mut ()` and we’re
126 // in `drop`, so `get` and `get_mut` cannot be called again.
127 unsafe { drop(Box::from_raw(ptr)) }
128 }
129 }
130
131 fn _static_assert_is_send<T: Send>() {}
132
133 fn _static_assert_fields_are_send() {
134 _static_assert_is_send::<Box<DirstateMap<'_>>>();
135 }
136
137 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
138 // thread-safety of raw pointers is unknown in the general case. However this
139 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
140 // own. Since that `Box` is `Send` as shown in above, it is sound to mark
141 // this struct as `Send` too.
142 unsafe impl Send for OwningDirstateMap {}
@@ -1,848 +1,849 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::NodeData;
7 use crate::dirstate_tree::dirstate_map::NodeData;
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_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::BadMatch;
15 use crate::BadMatch;
16 use crate::DirstateStatus;
16 use crate::DirstateStatus;
17 use crate::EntryState;
17 use crate::EntryState;
18 use crate::HgPathBuf;
18 use crate::HgPathBuf;
19 use crate::HgPathCow;
19 use crate::HgPathCow;
20 use crate::PatternFileWarning;
20 use crate::PatternFileWarning;
21 use crate::StatusError;
21 use crate::StatusError;
22 use crate::StatusOptions;
22 use crate::StatusOptions;
23 use micro_timer::timed;
23 use micro_timer::timed;
24 use rayon::prelude::*;
24 use rayon::prelude::*;
25 use sha1::{Digest, Sha1};
25 use sha1::{Digest, Sha1};
26 use std::borrow::Cow;
26 use std::borrow::Cow;
27 use std::io;
27 use std::io;
28 use std::path::Path;
28 use std::path::Path;
29 use std::path::PathBuf;
29 use std::path::PathBuf;
30 use std::sync::Mutex;
30 use std::sync::Mutex;
31 use std::time::SystemTime;
31 use std::time::SystemTime;
32
32
33 /// Returns the status of the working directory compared to its parent
33 /// Returns the status of the working directory compared to its parent
34 /// changeset.
34 /// changeset.
35 ///
35 ///
36 /// This algorithm is based on traversing the filesystem tree (`fs` in function
36 /// 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
37 /// 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
38 /// 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
39 /// 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
40 /// exists in one of the two trees, depending on information requested by
41 /// `options` we may need to traverse the remaining subtree.
41 /// `options` we may need to traverse the remaining subtree.
42 #[timed]
42 #[timed]
43 pub fn status<'tree, 'on_disk: 'tree>(
43 pub fn status<'dirstate>(
44 dmap: &'tree mut DirstateMap<'on_disk>,
44 dmap: &'dirstate mut DirstateMap,
45 matcher: &(dyn Matcher + Sync),
45 matcher: &(dyn Matcher + Sync),
46 root_dir: PathBuf,
46 root_dir: PathBuf,
47 ignore_files: Vec<PathBuf>,
47 ignore_files: Vec<PathBuf>,
48 options: StatusOptions,
48 options: StatusOptions,
49 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
49 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
50 {
50 // Force the global rayon threadpool to not exceed 16 concurrent threads.
51 // Force the global rayon threadpool to not exceed 16 concurrent threads.
51 // This is a stop-gap measure until we figure out why using more than 16
52 // This is a stop-gap measure until we figure out why using more than 16
52 // threads makes `status` slower for each additional thread.
53 // threads makes `status` slower for each additional thread.
53 // We use `ok()` in case the global threadpool has already been
54 // We use `ok()` in case the global threadpool has already been
54 // instantiated in `rhg` or some other caller.
55 // instantiated in `rhg` or some other caller.
55 // TODO find the underlying cause and fix it, then remove this.
56 // TODO find the underlying cause and fix it, then remove this.
56 rayon::ThreadPoolBuilder::new()
57 rayon::ThreadPoolBuilder::new()
57 .num_threads(16)
58 .num_threads(16)
58 .build_global()
59 .build_global()
59 .ok();
60 .ok();
60
61
61 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 if options.list_ignored || options.list_unknown {
63 if options.list_ignored || options.list_unknown {
63 let mut hasher = Sha1::new();
64 let mut hasher = Sha1::new();
64 let (ignore_fn, warnings) = get_ignore_function(
65 let (ignore_fn, warnings) = get_ignore_function(
65 ignore_files,
66 ignore_files,
66 &root_dir,
67 &root_dir,
67 &mut |pattern_bytes| hasher.update(pattern_bytes),
68 &mut |pattern_bytes| hasher.update(pattern_bytes),
68 )?;
69 )?;
69 let new_hash = *hasher.finalize().as_ref();
70 let new_hash = *hasher.finalize().as_ref();
70 let changed = new_hash != dmap.ignore_patterns_hash;
71 let changed = new_hash != dmap.ignore_patterns_hash;
71 dmap.ignore_patterns_hash = new_hash;
72 dmap.ignore_patterns_hash = new_hash;
72 (ignore_fn, warnings, Some(changed))
73 (ignore_fn, warnings, Some(changed))
73 } else {
74 } else {
74 (Box::new(|&_| true), vec![], None)
75 (Box::new(|&_| true), vec![], None)
75 };
76 };
76
77
77 let filesystem_time_at_status_start =
78 let filesystem_time_at_status_start =
78 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
79 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
79
80
80 // If the repository is under the current directory, prefer using a
81 // If the repository is under the current directory, prefer using a
81 // relative path, so the kernel needs to traverse fewer directory in every
82 // relative path, so the kernel needs to traverse fewer directory in every
82 // call to `read_dir` or `symlink_metadata`.
83 // call to `read_dir` or `symlink_metadata`.
83 // This is effective in the common case where the current directory is the
84 // This is effective in the common case where the current directory is the
84 // repository root.
85 // repository root.
85
86
86 // TODO: Better yet would be to use libc functions like `openat` and
87 // TODO: Better yet would be to use libc functions like `openat` and
87 // `fstatat` to remove such repeated traversals entirely, but the standard
88 // `fstatat` to remove such repeated traversals entirely, but the standard
88 // library does not provide APIs based on those.
89 // library does not provide APIs based on those.
89 // Maybe with a crate like https://crates.io/crates/openat instead?
90 // Maybe with a crate like https://crates.io/crates/openat instead?
90 let root_dir = if let Some(relative) = std::env::current_dir()
91 let root_dir = if let Some(relative) = std::env::current_dir()
91 .ok()
92 .ok()
92 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
93 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
93 {
94 {
94 relative
95 relative
95 } else {
96 } else {
96 &root_dir
97 &root_dir
97 };
98 };
98
99
99 let outcome = DirstateStatus {
100 let outcome = DirstateStatus {
100 filesystem_time_at_status_start,
101 filesystem_time_at_status_start,
101 ..Default::default()
102 ..Default::default()
102 };
103 };
103 let common = StatusCommon {
104 let common = StatusCommon {
104 dmap,
105 dmap,
105 options,
106 options,
106 matcher,
107 matcher,
107 ignore_fn,
108 ignore_fn,
108 outcome: Mutex::new(outcome),
109 outcome: Mutex::new(outcome),
109 ignore_patterns_have_changed: patterns_changed,
110 ignore_patterns_have_changed: patterns_changed,
110 new_cachable_directories: Default::default(),
111 new_cachable_directories: Default::default(),
111 outated_cached_directories: Default::default(),
112 outated_cached_directories: Default::default(),
112 filesystem_time_at_status_start,
113 filesystem_time_at_status_start,
113 };
114 };
114 let is_at_repo_root = true;
115 let is_at_repo_root = true;
115 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
116 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
116 let has_ignored_ancestor = false;
117 let has_ignored_ancestor = false;
117 let root_cached_mtime = None;
118 let root_cached_mtime = None;
118 let root_dir_metadata = None;
119 let root_dir_metadata = None;
119 // If the path we have for the repository root is a symlink, do follow it.
120 // If the path we have for the repository root is a symlink, do follow it.
120 // (As opposed to symlinks within the working directory which are not
121 // (As opposed to symlinks within the working directory which are not
121 // followed, using `std::fs::symlink_metadata`.)
122 // followed, using `std::fs::symlink_metadata`.)
122 common.traverse_fs_directory_and_dirstate(
123 common.traverse_fs_directory_and_dirstate(
123 has_ignored_ancestor,
124 has_ignored_ancestor,
124 dmap.root.as_ref(),
125 dmap.root.as_ref(),
125 hg_path,
126 hg_path,
126 &root_dir,
127 &root_dir,
127 root_dir_metadata,
128 root_dir_metadata,
128 root_cached_mtime,
129 root_cached_mtime,
129 is_at_repo_root,
130 is_at_repo_root,
130 )?;
131 )?;
131 let mut outcome = common.outcome.into_inner().unwrap();
132 let mut outcome = common.outcome.into_inner().unwrap();
132 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
133 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
133 let outdated = common.outated_cached_directories.into_inner().unwrap();
134 let outdated = common.outated_cached_directories.into_inner().unwrap();
134
135
135 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
136 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
136 || !outdated.is_empty()
137 || !outdated.is_empty()
137 || !new_cachable.is_empty();
138 || !new_cachable.is_empty();
138
139
139 // Remove outdated mtimes before adding new mtimes, in case a given
140 // Remove outdated mtimes before adding new mtimes, in case a given
140 // directory is both
141 // directory is both
141 for path in &outdated {
142 for path in &outdated {
142 let node = dmap.get_or_insert(path)?;
143 let node = dmap.get_or_insert(path)?;
143 if let NodeData::CachedDirectory { .. } = &node.data {
144 if let NodeData::CachedDirectory { .. } = &node.data {
144 node.data = NodeData::None
145 node.data = NodeData::None
145 }
146 }
146 }
147 }
147 for (path, mtime) in &new_cachable {
148 for (path, mtime) in &new_cachable {
148 let node = dmap.get_or_insert(path)?;
149 let node = dmap.get_or_insert(path)?;
149 match &node.data {
150 match &node.data {
150 NodeData::Entry(_) => {} // Don’t overwrite an entry
151 NodeData::Entry(_) => {} // Don’t overwrite an entry
151 NodeData::CachedDirectory { .. } | NodeData::None => {
152 NodeData::CachedDirectory { .. } | NodeData::None => {
152 node.data = NodeData::CachedDirectory { mtime: *mtime }
153 node.data = NodeData::CachedDirectory { mtime: *mtime }
153 }
154 }
154 }
155 }
155 }
156 }
156
157
157 Ok((outcome, warnings))
158 Ok((outcome, warnings))
158 }
159 }
159
160
160 /// Bag of random things needed by various parts of the algorithm. Reduces the
161 /// Bag of random things needed by various parts of the algorithm. Reduces the
161 /// number of parameters passed to functions.
162 /// number of parameters passed to functions.
162 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
163 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
163 dmap: &'tree DirstateMap<'on_disk>,
164 dmap: &'tree DirstateMap<'on_disk>,
164 options: StatusOptions,
165 options: StatusOptions,
165 matcher: &'a (dyn Matcher + Sync),
166 matcher: &'a (dyn Matcher + Sync),
166 ignore_fn: IgnoreFnType<'a>,
167 ignore_fn: IgnoreFnType<'a>,
167 outcome: Mutex<DirstateStatus<'on_disk>>,
168 outcome: Mutex<DirstateStatus<'on_disk>>,
168 new_cachable_directories:
169 new_cachable_directories:
169 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
170 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
170 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
171 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
171
172
172 /// Whether ignore files like `.hgignore` have changed since the previous
173 /// Whether ignore files like `.hgignore` have changed since the previous
173 /// time a `status()` call wrote their hash to the dirstate. `None` means
174 /// time a `status()` call wrote their hash to the dirstate. `None` means
174 /// we don’t know as this run doesn’t list either ignored or uknown files
175 /// we don’t know as this run doesn’t list either ignored or uknown files
175 /// and therefore isn’t reading `.hgignore`.
176 /// and therefore isn’t reading `.hgignore`.
176 ignore_patterns_have_changed: Option<bool>,
177 ignore_patterns_have_changed: Option<bool>,
177
178
178 /// The current time at the start of the `status()` algorithm, as measured
179 /// The current time at the start of the `status()` algorithm, as measured
179 /// and possibly truncated by the filesystem.
180 /// and possibly truncated by the filesystem.
180 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
181 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
181 }
182 }
182
183
183 enum Outcome {
184 enum Outcome {
184 Modified,
185 Modified,
185 Added,
186 Added,
186 Removed,
187 Removed,
187 Deleted,
188 Deleted,
188 Clean,
189 Clean,
189 Ignored,
190 Ignored,
190 Unknown,
191 Unknown,
191 Unsure,
192 Unsure,
192 }
193 }
193
194
194 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
195 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
195 fn push_outcome(
196 fn push_outcome(
196 &self,
197 &self,
197 which: Outcome,
198 which: Outcome,
198 dirstate_node: &NodeRef<'tree, 'on_disk>,
199 dirstate_node: &NodeRef<'tree, 'on_disk>,
199 ) -> Result<(), DirstateV2ParseError> {
200 ) -> Result<(), DirstateV2ParseError> {
200 let path = dirstate_node
201 let path = dirstate_node
201 .full_path_borrowed(self.dmap.on_disk)?
202 .full_path_borrowed(self.dmap.on_disk)?
202 .detach_from_tree();
203 .detach_from_tree();
203 let copy_source = if self.options.list_copies {
204 let copy_source = if self.options.list_copies {
204 dirstate_node
205 dirstate_node
205 .copy_source_borrowed(self.dmap.on_disk)?
206 .copy_source_borrowed(self.dmap.on_disk)?
206 .map(|source| source.detach_from_tree())
207 .map(|source| source.detach_from_tree())
207 } else {
208 } else {
208 None
209 None
209 };
210 };
210 self.push_outcome_common(which, path, copy_source);
211 self.push_outcome_common(which, path, copy_source);
211 Ok(())
212 Ok(())
212 }
213 }
213
214
214 fn push_outcome_without_copy_source(
215 fn push_outcome_without_copy_source(
215 &self,
216 &self,
216 which: Outcome,
217 which: Outcome,
217 path: &BorrowedPath<'_, 'on_disk>,
218 path: &BorrowedPath<'_, 'on_disk>,
218 ) {
219 ) {
219 self.push_outcome_common(which, path.detach_from_tree(), None)
220 self.push_outcome_common(which, path.detach_from_tree(), None)
220 }
221 }
221
222
222 fn push_outcome_common(
223 fn push_outcome_common(
223 &self,
224 &self,
224 which: Outcome,
225 which: Outcome,
225 path: HgPathCow<'on_disk>,
226 path: HgPathCow<'on_disk>,
226 copy_source: Option<HgPathCow<'on_disk>>,
227 copy_source: Option<HgPathCow<'on_disk>>,
227 ) {
228 ) {
228 let mut outcome = self.outcome.lock().unwrap();
229 let mut outcome = self.outcome.lock().unwrap();
229 let vec = match which {
230 let vec = match which {
230 Outcome::Modified => &mut outcome.modified,
231 Outcome::Modified => &mut outcome.modified,
231 Outcome::Added => &mut outcome.added,
232 Outcome::Added => &mut outcome.added,
232 Outcome::Removed => &mut outcome.removed,
233 Outcome::Removed => &mut outcome.removed,
233 Outcome::Deleted => &mut outcome.deleted,
234 Outcome::Deleted => &mut outcome.deleted,
234 Outcome::Clean => &mut outcome.clean,
235 Outcome::Clean => &mut outcome.clean,
235 Outcome::Ignored => &mut outcome.ignored,
236 Outcome::Ignored => &mut outcome.ignored,
236 Outcome::Unknown => &mut outcome.unknown,
237 Outcome::Unknown => &mut outcome.unknown,
237 Outcome::Unsure => &mut outcome.unsure,
238 Outcome::Unsure => &mut outcome.unsure,
238 };
239 };
239 vec.push(StatusPath { path, copy_source });
240 vec.push(StatusPath { path, copy_source });
240 }
241 }
241
242
242 fn read_dir(
243 fn read_dir(
243 &self,
244 &self,
244 hg_path: &HgPath,
245 hg_path: &HgPath,
245 fs_path: &Path,
246 fs_path: &Path,
246 is_at_repo_root: bool,
247 is_at_repo_root: bool,
247 ) -> Result<Vec<DirEntry>, ()> {
248 ) -> Result<Vec<DirEntry>, ()> {
248 DirEntry::read_dir(fs_path, is_at_repo_root)
249 DirEntry::read_dir(fs_path, is_at_repo_root)
249 .map_err(|error| self.io_error(error, hg_path))
250 .map_err(|error| self.io_error(error, hg_path))
250 }
251 }
251
252
252 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
253 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
253 let errno = error.raw_os_error().expect("expected real OS error");
254 let errno = error.raw_os_error().expect("expected real OS error");
254 self.outcome
255 self.outcome
255 .lock()
256 .lock()
256 .unwrap()
257 .unwrap()
257 .bad
258 .bad
258 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
259 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
259 }
260 }
260
261
261 fn check_for_outdated_directory_cache(
262 fn check_for_outdated_directory_cache(
262 &self,
263 &self,
263 dirstate_node: &NodeRef<'tree, 'on_disk>,
264 dirstate_node: &NodeRef<'tree, 'on_disk>,
264 ) -> Result<(), DirstateV2ParseError> {
265 ) -> Result<(), DirstateV2ParseError> {
265 if self.ignore_patterns_have_changed == Some(true)
266 if self.ignore_patterns_have_changed == Some(true)
266 && dirstate_node.cached_directory_mtime()?.is_some()
267 && dirstate_node.cached_directory_mtime()?.is_some()
267 {
268 {
268 self.outated_cached_directories.lock().unwrap().push(
269 self.outated_cached_directories.lock().unwrap().push(
269 dirstate_node
270 dirstate_node
270 .full_path_borrowed(self.dmap.on_disk)?
271 .full_path_borrowed(self.dmap.on_disk)?
271 .detach_from_tree(),
272 .detach_from_tree(),
272 )
273 )
273 }
274 }
274 Ok(())
275 Ok(())
275 }
276 }
276
277
277 /// If this returns true, we can get accurate results by only using
278 /// If this returns true, we can get accurate results by only using
278 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
279 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
279 /// need to call `read_dir`.
280 /// need to call `read_dir`.
280 fn can_skip_fs_readdir(
281 fn can_skip_fs_readdir(
281 &self,
282 &self,
282 directory_metadata: Option<&std::fs::Metadata>,
283 directory_metadata: Option<&std::fs::Metadata>,
283 cached_directory_mtime: Option<TruncatedTimestamp>,
284 cached_directory_mtime: Option<TruncatedTimestamp>,
284 ) -> bool {
285 ) -> bool {
285 if !self.options.list_unknown && !self.options.list_ignored {
286 if !self.options.list_unknown && !self.options.list_ignored {
286 // All states that we care about listing have corresponding
287 // All states that we care about listing have corresponding
287 // dirstate entries.
288 // dirstate entries.
288 // This happens for example with `hg status -mard`.
289 // This happens for example with `hg status -mard`.
289 return true;
290 return true;
290 }
291 }
291 if !self.options.list_ignored
292 if !self.options.list_ignored
292 && self.ignore_patterns_have_changed == Some(false)
293 && self.ignore_patterns_have_changed == Some(false)
293 {
294 {
294 if let Some(cached_mtime) = cached_directory_mtime {
295 if let Some(cached_mtime) = cached_directory_mtime {
295 // The dirstate contains a cached mtime for this directory, set
296 // The dirstate contains a cached mtime for this directory, set
296 // by a previous run of the `status` algorithm which found this
297 // by a previous run of the `status` algorithm which found this
297 // directory eligible for `read_dir` caching.
298 // directory eligible for `read_dir` caching.
298 if let Some(meta) = directory_metadata {
299 if let Some(meta) = directory_metadata {
299 if cached_mtime
300 if cached_mtime
300 .likely_equal_to_mtime_of(meta)
301 .likely_equal_to_mtime_of(meta)
301 .unwrap_or(false)
302 .unwrap_or(false)
302 {
303 {
303 // The mtime of that directory has not changed
304 // The mtime of that directory has not changed
304 // since then, which means that the results of
305 // since then, which means that the results of
305 // `read_dir` should also be unchanged.
306 // `read_dir` should also be unchanged.
306 return true;
307 return true;
307 }
308 }
308 }
309 }
309 }
310 }
310 }
311 }
311 false
312 false
312 }
313 }
313
314
314 /// Returns whether all child entries of the filesystem directory have a
315 /// Returns whether all child entries of the filesystem directory have a
315 /// corresponding dirstate node or are ignored.
316 /// corresponding dirstate node or are ignored.
316 fn traverse_fs_directory_and_dirstate(
317 fn traverse_fs_directory_and_dirstate(
317 &self,
318 &self,
318 has_ignored_ancestor: bool,
319 has_ignored_ancestor: bool,
319 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
320 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
320 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
321 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
321 directory_fs_path: &Path,
322 directory_fs_path: &Path,
322 directory_metadata: Option<&std::fs::Metadata>,
323 directory_metadata: Option<&std::fs::Metadata>,
323 cached_directory_mtime: Option<TruncatedTimestamp>,
324 cached_directory_mtime: Option<TruncatedTimestamp>,
324 is_at_repo_root: bool,
325 is_at_repo_root: bool,
325 ) -> Result<bool, DirstateV2ParseError> {
326 ) -> Result<bool, DirstateV2ParseError> {
326 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
327 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
327 {
328 {
328 dirstate_nodes
329 dirstate_nodes
329 .par_iter()
330 .par_iter()
330 .map(|dirstate_node| {
331 .map(|dirstate_node| {
331 let fs_path = directory_fs_path.join(get_path_from_bytes(
332 let fs_path = directory_fs_path.join(get_path_from_bytes(
332 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
333 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
333 ));
334 ));
334 match std::fs::symlink_metadata(&fs_path) {
335 match std::fs::symlink_metadata(&fs_path) {
335 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
336 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
336 &fs_path,
337 &fs_path,
337 &fs_metadata,
338 &fs_metadata,
338 dirstate_node,
339 dirstate_node,
339 has_ignored_ancestor,
340 has_ignored_ancestor,
340 ),
341 ),
341 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
342 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
342 self.traverse_dirstate_only(dirstate_node)
343 self.traverse_dirstate_only(dirstate_node)
343 }
344 }
344 Err(error) => {
345 Err(error) => {
345 let hg_path =
346 let hg_path =
346 dirstate_node.full_path(self.dmap.on_disk)?;
347 dirstate_node.full_path(self.dmap.on_disk)?;
347 Ok(self.io_error(error, hg_path))
348 Ok(self.io_error(error, hg_path))
348 }
349 }
349 }
350 }
350 })
351 })
351 .collect::<Result<_, _>>()?;
352 .collect::<Result<_, _>>()?;
352
353
353 // We don’t know, so conservatively say this isn’t the case
354 // We don’t know, so conservatively say this isn’t the case
354 let children_all_have_dirstate_node_or_are_ignored = false;
355 let children_all_have_dirstate_node_or_are_ignored = false;
355
356
356 return Ok(children_all_have_dirstate_node_or_are_ignored);
357 return Ok(children_all_have_dirstate_node_or_are_ignored);
357 }
358 }
358
359
359 let mut fs_entries = if let Ok(entries) = self.read_dir(
360 let mut fs_entries = if let Ok(entries) = self.read_dir(
360 directory_hg_path,
361 directory_hg_path,
361 directory_fs_path,
362 directory_fs_path,
362 is_at_repo_root,
363 is_at_repo_root,
363 ) {
364 ) {
364 entries
365 entries
365 } else {
366 } else {
366 // Treat an unreadable directory (typically because of insufficient
367 // Treat an unreadable directory (typically because of insufficient
367 // permissions) like an empty directory. `self.read_dir` has
368 // permissions) like an empty directory. `self.read_dir` has
368 // already called `self.io_error` so a warning will be emitted.
369 // already called `self.io_error` so a warning will be emitted.
369 Vec::new()
370 Vec::new()
370 };
371 };
371
372
372 // `merge_join_by` requires both its input iterators to be sorted:
373 // `merge_join_by` requires both its input iterators to be sorted:
373
374
374 let dirstate_nodes = dirstate_nodes.sorted();
375 let dirstate_nodes = dirstate_nodes.sorted();
375 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
376 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
376 // https://github.com/rust-lang/rust/issues/34162
377 // https://github.com/rust-lang/rust/issues/34162
377 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
378 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
378
379
379 // Propagate here any error that would happen inside the comparison
380 // Propagate here any error that would happen inside the comparison
380 // callback below
381 // callback below
381 for dirstate_node in &dirstate_nodes {
382 for dirstate_node in &dirstate_nodes {
382 dirstate_node.base_name(self.dmap.on_disk)?;
383 dirstate_node.base_name(self.dmap.on_disk)?;
383 }
384 }
384 itertools::merge_join_by(
385 itertools::merge_join_by(
385 dirstate_nodes,
386 dirstate_nodes,
386 &fs_entries,
387 &fs_entries,
387 |dirstate_node, fs_entry| {
388 |dirstate_node, fs_entry| {
388 // This `unwrap` never panics because we already propagated
389 // This `unwrap` never panics because we already propagated
389 // those errors above
390 // those errors above
390 dirstate_node
391 dirstate_node
391 .base_name(self.dmap.on_disk)
392 .base_name(self.dmap.on_disk)
392 .unwrap()
393 .unwrap()
393 .cmp(&fs_entry.base_name)
394 .cmp(&fs_entry.base_name)
394 },
395 },
395 )
396 )
396 .par_bridge()
397 .par_bridge()
397 .map(|pair| {
398 .map(|pair| {
398 use itertools::EitherOrBoth::*;
399 use itertools::EitherOrBoth::*;
399 let has_dirstate_node_or_is_ignored;
400 let has_dirstate_node_or_is_ignored;
400 match pair {
401 match pair {
401 Both(dirstate_node, fs_entry) => {
402 Both(dirstate_node, fs_entry) => {
402 self.traverse_fs_and_dirstate(
403 self.traverse_fs_and_dirstate(
403 &fs_entry.full_path,
404 &fs_entry.full_path,
404 &fs_entry.metadata,
405 &fs_entry.metadata,
405 dirstate_node,
406 dirstate_node,
406 has_ignored_ancestor,
407 has_ignored_ancestor,
407 )?;
408 )?;
408 has_dirstate_node_or_is_ignored = true
409 has_dirstate_node_or_is_ignored = true
409 }
410 }
410 Left(dirstate_node) => {
411 Left(dirstate_node) => {
411 self.traverse_dirstate_only(dirstate_node)?;
412 self.traverse_dirstate_only(dirstate_node)?;
412 has_dirstate_node_or_is_ignored = true;
413 has_dirstate_node_or_is_ignored = true;
413 }
414 }
414 Right(fs_entry) => {
415 Right(fs_entry) => {
415 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
416 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
416 has_ignored_ancestor,
417 has_ignored_ancestor,
417 directory_hg_path,
418 directory_hg_path,
418 fs_entry,
419 fs_entry,
419 )
420 )
420 }
421 }
421 }
422 }
422 Ok(has_dirstate_node_or_is_ignored)
423 Ok(has_dirstate_node_or_is_ignored)
423 })
424 })
424 .try_reduce(|| true, |a, b| Ok(a && b))
425 .try_reduce(|| true, |a, b| Ok(a && b))
425 }
426 }
426
427
427 fn traverse_fs_and_dirstate(
428 fn traverse_fs_and_dirstate(
428 &self,
429 &self,
429 fs_path: &Path,
430 fs_path: &Path,
430 fs_metadata: &std::fs::Metadata,
431 fs_metadata: &std::fs::Metadata,
431 dirstate_node: NodeRef<'tree, 'on_disk>,
432 dirstate_node: NodeRef<'tree, 'on_disk>,
432 has_ignored_ancestor: bool,
433 has_ignored_ancestor: bool,
433 ) -> Result<(), DirstateV2ParseError> {
434 ) -> Result<(), DirstateV2ParseError> {
434 self.check_for_outdated_directory_cache(&dirstate_node)?;
435 self.check_for_outdated_directory_cache(&dirstate_node)?;
435 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
436 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
436 let file_type = fs_metadata.file_type();
437 let file_type = fs_metadata.file_type();
437 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
438 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
438 if !file_or_symlink {
439 if !file_or_symlink {
439 // If we previously had a file here, it was removed (with
440 // If we previously had a file here, it was removed (with
440 // `hg rm` or similar) or deleted before it could be
441 // `hg rm` or similar) or deleted before it could be
441 // replaced by a directory or something else.
442 // replaced by a directory or something else.
442 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
443 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
443 }
444 }
444 if file_type.is_dir() {
445 if file_type.is_dir() {
445 if self.options.collect_traversed_dirs {
446 if self.options.collect_traversed_dirs {
446 self.outcome
447 self.outcome
447 .lock()
448 .lock()
448 .unwrap()
449 .unwrap()
449 .traversed
450 .traversed
450 .push(hg_path.detach_from_tree())
451 .push(hg_path.detach_from_tree())
451 }
452 }
452 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
453 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
453 let is_at_repo_root = false;
454 let is_at_repo_root = false;
454 let children_all_have_dirstate_node_or_are_ignored = self
455 let children_all_have_dirstate_node_or_are_ignored = self
455 .traverse_fs_directory_and_dirstate(
456 .traverse_fs_directory_and_dirstate(
456 is_ignored,
457 is_ignored,
457 dirstate_node.children(self.dmap.on_disk)?,
458 dirstate_node.children(self.dmap.on_disk)?,
458 hg_path,
459 hg_path,
459 fs_path,
460 fs_path,
460 Some(fs_metadata),
461 Some(fs_metadata),
461 dirstate_node.cached_directory_mtime()?,
462 dirstate_node.cached_directory_mtime()?,
462 is_at_repo_root,
463 is_at_repo_root,
463 )?;
464 )?;
464 self.maybe_save_directory_mtime(
465 self.maybe_save_directory_mtime(
465 children_all_have_dirstate_node_or_are_ignored,
466 children_all_have_dirstate_node_or_are_ignored,
466 fs_metadata,
467 fs_metadata,
467 dirstate_node,
468 dirstate_node,
468 )?
469 )?
469 } else {
470 } else {
470 if file_or_symlink && self.matcher.matches(hg_path) {
471 if file_or_symlink && self.matcher.matches(hg_path) {
471 if let Some(state) = dirstate_node.state()? {
472 if let Some(state) = dirstate_node.state()? {
472 match state {
473 match state {
473 EntryState::Added => {
474 EntryState::Added => {
474 self.push_outcome(Outcome::Added, &dirstate_node)?
475 self.push_outcome(Outcome::Added, &dirstate_node)?
475 }
476 }
476 EntryState::Removed => self
477 EntryState::Removed => self
477 .push_outcome(Outcome::Removed, &dirstate_node)?,
478 .push_outcome(Outcome::Removed, &dirstate_node)?,
478 EntryState::Merged => self
479 EntryState::Merged => self
479 .push_outcome(Outcome::Modified, &dirstate_node)?,
480 .push_outcome(Outcome::Modified, &dirstate_node)?,
480 EntryState::Normal => self
481 EntryState::Normal => self
481 .handle_normal_file(&dirstate_node, fs_metadata)?,
482 .handle_normal_file(&dirstate_node, fs_metadata)?,
482 }
483 }
483 } else {
484 } else {
484 // `node.entry.is_none()` indicates a "directory"
485 // `node.entry.is_none()` indicates a "directory"
485 // node, but the filesystem has a file
486 // node, but the filesystem has a file
486 self.mark_unknown_or_ignored(
487 self.mark_unknown_or_ignored(
487 has_ignored_ancestor,
488 has_ignored_ancestor,
488 hg_path,
489 hg_path,
489 );
490 );
490 }
491 }
491 }
492 }
492
493
493 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
494 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
494 {
495 {
495 self.traverse_dirstate_only(child_node)?
496 self.traverse_dirstate_only(child_node)?
496 }
497 }
497 }
498 }
498 Ok(())
499 Ok(())
499 }
500 }
500
501
501 fn maybe_save_directory_mtime(
502 fn maybe_save_directory_mtime(
502 &self,
503 &self,
503 children_all_have_dirstate_node_or_are_ignored: bool,
504 children_all_have_dirstate_node_or_are_ignored: bool,
504 directory_metadata: &std::fs::Metadata,
505 directory_metadata: &std::fs::Metadata,
505 dirstate_node: NodeRef<'tree, 'on_disk>,
506 dirstate_node: NodeRef<'tree, 'on_disk>,
506 ) -> Result<(), DirstateV2ParseError> {
507 ) -> Result<(), DirstateV2ParseError> {
507 if !children_all_have_dirstate_node_or_are_ignored {
508 if !children_all_have_dirstate_node_or_are_ignored {
508 return Ok(());
509 return Ok(());
509 }
510 }
510 // All filesystem directory entries from `read_dir` have a
511 // All filesystem directory entries from `read_dir` have a
511 // corresponding node in the dirstate, so we can reconstitute the
512 // corresponding node in the dirstate, so we can reconstitute the
512 // names of those entries without calling `read_dir` again.
513 // names of those entries without calling `read_dir` again.
513
514
514 // TODO: use let-else here and below when available:
515 // TODO: use let-else here and below when available:
515 // https://github.com/rust-lang/rust/issues/87335
516 // https://github.com/rust-lang/rust/issues/87335
516 let status_start = if let Some(status_start) =
517 let status_start = if let Some(status_start) =
517 &self.filesystem_time_at_status_start
518 &self.filesystem_time_at_status_start
518 {
519 {
519 status_start
520 status_start
520 } else {
521 } else {
521 return Ok(());
522 return Ok(());
522 };
523 };
523
524
524 // Although the Rust standard library’s `SystemTime` type
525 // Although the Rust standard library’s `SystemTime` type
525 // has nanosecond precision, the times reported for a
526 // has nanosecond precision, the times reported for a
526 // directory’s (or file’s) modified time may have lower
527 // directory’s (or file’s) modified time may have lower
527 // resolution based on the filesystem (for example ext3
528 // resolution based on the filesystem (for example ext3
528 // only stores integer seconds), kernel (see
529 // only stores integer seconds), kernel (see
529 // https://stackoverflow.com/a/14393315/1162888), etc.
530 // https://stackoverflow.com/a/14393315/1162888), etc.
530 let directory_mtime = if let Ok(option) =
531 let directory_mtime = if let Ok(option) =
531 TruncatedTimestamp::for_reliable_mtime_of(
532 TruncatedTimestamp::for_reliable_mtime_of(
532 directory_metadata,
533 directory_metadata,
533 status_start,
534 status_start,
534 ) {
535 ) {
535 if let Some(directory_mtime) = option {
536 if let Some(directory_mtime) = option {
536 directory_mtime
537 directory_mtime
537 } else {
538 } else {
538 // The directory was modified too recently,
539 // The directory was modified too recently,
539 // don’t cache its `read_dir` results.
540 // don’t cache its `read_dir` results.
540 //
541 //
541 // 1. A change to this directory (direct child was
542 // 1. A change to this directory (direct child was
542 // added or removed) cause its mtime to be set
543 // added or removed) cause its mtime to be set
543 // (possibly truncated) to `directory_mtime`
544 // (possibly truncated) to `directory_mtime`
544 // 2. This `status` algorithm calls `read_dir`
545 // 2. This `status` algorithm calls `read_dir`
545 // 3. An other change is made to the same directory is
546 // 3. An other change is made to the same directory is
546 // made so that calling `read_dir` agin would give
547 // made so that calling `read_dir` agin would give
547 // different results, but soon enough after 1. that
548 // different results, but soon enough after 1. that
548 // the mtime stays the same
549 // the mtime stays the same
549 //
550 //
550 // On a system where the time resolution poor, this
551 // On a system where the time resolution poor, this
551 // scenario is not unlikely if all three steps are caused
552 // scenario is not unlikely if all three steps are caused
552 // by the same script.
553 // by the same script.
553 return Ok(());
554 return Ok(());
554 }
555 }
555 } else {
556 } else {
556 // OS/libc does not support mtime?
557 // OS/libc does not support mtime?
557 return Ok(());
558 return Ok(());
558 };
559 };
559 // We’ve observed (through `status_start`) that time has
560 // We’ve observed (through `status_start`) that time has
560 // “progressed” since `directory_mtime`, so any further
561 // “progressed” since `directory_mtime`, so any further
561 // change to this directory is extremely likely to cause a
562 // change to this directory is extremely likely to cause a
562 // different mtime.
563 // different mtime.
563 //
564 //
564 // Having the same mtime again is not entirely impossible
565 // Having the same mtime again is not entirely impossible
565 // since the system clock is not monotonous. It could jump
566 // since the system clock is not monotonous. It could jump
566 // backward to some point before `directory_mtime`, then a
567 // backward to some point before `directory_mtime`, then a
567 // directory change could potentially happen during exactly
568 // directory change could potentially happen during exactly
568 // the wrong tick.
569 // the wrong tick.
569 //
570 //
570 // We deem this scenario (unlike the previous one) to be
571 // We deem this scenario (unlike the previous one) to be
571 // unlikely enough in practice.
572 // unlikely enough in practice.
572
573
573 let is_up_to_date =
574 let is_up_to_date =
574 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
575 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
575 cached.likely_equal(directory_mtime)
576 cached.likely_equal(directory_mtime)
576 } else {
577 } else {
577 false
578 false
578 };
579 };
579 if !is_up_to_date {
580 if !is_up_to_date {
580 let hg_path = dirstate_node
581 let hg_path = dirstate_node
581 .full_path_borrowed(self.dmap.on_disk)?
582 .full_path_borrowed(self.dmap.on_disk)?
582 .detach_from_tree();
583 .detach_from_tree();
583 self.new_cachable_directories
584 self.new_cachable_directories
584 .lock()
585 .lock()
585 .unwrap()
586 .unwrap()
586 .push((hg_path, directory_mtime))
587 .push((hg_path, directory_mtime))
587 }
588 }
588 Ok(())
589 Ok(())
589 }
590 }
590
591
591 /// A file with `EntryState::Normal` in the dirstate was found in the
592 /// A file with `EntryState::Normal` in the dirstate was found in the
592 /// filesystem
593 /// filesystem
593 fn handle_normal_file(
594 fn handle_normal_file(
594 &self,
595 &self,
595 dirstate_node: &NodeRef<'tree, 'on_disk>,
596 dirstate_node: &NodeRef<'tree, 'on_disk>,
596 fs_metadata: &std::fs::Metadata,
597 fs_metadata: &std::fs::Metadata,
597 ) -> Result<(), DirstateV2ParseError> {
598 ) -> Result<(), DirstateV2ParseError> {
598 // Keep the low 31 bits
599 // Keep the low 31 bits
599 fn truncate_u64(value: u64) -> i32 {
600 fn truncate_u64(value: u64) -> i32 {
600 (value & 0x7FFF_FFFF) as i32
601 (value & 0x7FFF_FFFF) as i32
601 }
602 }
602
603
603 let entry = dirstate_node
604 let entry = dirstate_node
604 .entry()?
605 .entry()?
605 .expect("handle_normal_file called with entry-less node");
606 .expect("handle_normal_file called with entry-less node");
606 let mode_changed =
607 let mode_changed =
607 || self.options.check_exec && entry.mode_changed(fs_metadata);
608 || self.options.check_exec && entry.mode_changed(fs_metadata);
608 let size = entry.size();
609 let size = entry.size();
609 let size_changed = size != truncate_u64(fs_metadata.len());
610 let size_changed = size != truncate_u64(fs_metadata.len());
610 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
611 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
611 // issue6456: Size returned may be longer due to encryption
612 // issue6456: Size returned may be longer due to encryption
612 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
613 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
613 self.push_outcome(Outcome::Unsure, dirstate_node)?
614 self.push_outcome(Outcome::Unsure, dirstate_node)?
614 } else if dirstate_node.has_copy_source()
615 } else if dirstate_node.has_copy_source()
615 || entry.is_from_other_parent()
616 || entry.is_from_other_parent()
616 || (size >= 0 && (size_changed || mode_changed()))
617 || (size >= 0 && (size_changed || mode_changed()))
617 {
618 {
618 self.push_outcome(Outcome::Modified, dirstate_node)?
619 self.push_outcome(Outcome::Modified, dirstate_node)?
619 } else {
620 } else {
620 let mtime_looks_clean;
621 let mtime_looks_clean;
621 if let Some(dirstate_mtime) = entry.truncated_mtime() {
622 if let Some(dirstate_mtime) = entry.truncated_mtime() {
622 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
623 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
623 .expect("OS/libc does not support mtime?");
624 .expect("OS/libc does not support mtime?");
624 // There might be a change in the future if for example the
625 // There might be a change in the future if for example the
625 // internal clock become off while process run, but this is a
626 // internal clock become off while process run, but this is a
626 // case where the issues the user would face
627 // case where the issues the user would face
627 // would be a lot worse and there is nothing we
628 // would be a lot worse and there is nothing we
628 // can really do.
629 // can really do.
629 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
630 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
630 } else {
631 } else {
631 // No mtime in the dirstate entry
632 // No mtime in the dirstate entry
632 mtime_looks_clean = false
633 mtime_looks_clean = false
633 };
634 };
634 if !mtime_looks_clean {
635 if !mtime_looks_clean {
635 self.push_outcome(Outcome::Unsure, dirstate_node)?
636 self.push_outcome(Outcome::Unsure, dirstate_node)?
636 } else if self.options.list_clean {
637 } else if self.options.list_clean {
637 self.push_outcome(Outcome::Clean, dirstate_node)?
638 self.push_outcome(Outcome::Clean, dirstate_node)?
638 }
639 }
639 }
640 }
640 Ok(())
641 Ok(())
641 }
642 }
642
643
643 /// A node in the dirstate tree has no corresponding filesystem entry
644 /// A node in the dirstate tree has no corresponding filesystem entry
644 fn traverse_dirstate_only(
645 fn traverse_dirstate_only(
645 &self,
646 &self,
646 dirstate_node: NodeRef<'tree, 'on_disk>,
647 dirstate_node: NodeRef<'tree, 'on_disk>,
647 ) -> Result<(), DirstateV2ParseError> {
648 ) -> Result<(), DirstateV2ParseError> {
648 self.check_for_outdated_directory_cache(&dirstate_node)?;
649 self.check_for_outdated_directory_cache(&dirstate_node)?;
649 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
650 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
650 dirstate_node
651 dirstate_node
651 .children(self.dmap.on_disk)?
652 .children(self.dmap.on_disk)?
652 .par_iter()
653 .par_iter()
653 .map(|child_node| self.traverse_dirstate_only(child_node))
654 .map(|child_node| self.traverse_dirstate_only(child_node))
654 .collect()
655 .collect()
655 }
656 }
656
657
657 /// A node in the dirstate tree has no corresponding *file* on the
658 /// A node in the dirstate tree has no corresponding *file* on the
658 /// filesystem
659 /// filesystem
659 ///
660 ///
660 /// Does nothing on a "directory" node
661 /// Does nothing on a "directory" node
661 fn mark_removed_or_deleted_if_file(
662 fn mark_removed_or_deleted_if_file(
662 &self,
663 &self,
663 dirstate_node: &NodeRef<'tree, 'on_disk>,
664 dirstate_node: &NodeRef<'tree, 'on_disk>,
664 ) -> Result<(), DirstateV2ParseError> {
665 ) -> Result<(), DirstateV2ParseError> {
665 if let Some(state) = dirstate_node.state()? {
666 if let Some(state) = dirstate_node.state()? {
666 let path = dirstate_node.full_path(self.dmap.on_disk)?;
667 let path = dirstate_node.full_path(self.dmap.on_disk)?;
667 if self.matcher.matches(path) {
668 if self.matcher.matches(path) {
668 if let EntryState::Removed = state {
669 if let EntryState::Removed = state {
669 self.push_outcome(Outcome::Removed, dirstate_node)?
670 self.push_outcome(Outcome::Removed, dirstate_node)?
670 } else {
671 } else {
671 self.push_outcome(Outcome::Deleted, &dirstate_node)?
672 self.push_outcome(Outcome::Deleted, &dirstate_node)?
672 }
673 }
673 }
674 }
674 }
675 }
675 Ok(())
676 Ok(())
676 }
677 }
677
678
678 /// Something in the filesystem has no corresponding dirstate node
679 /// Something in the filesystem has no corresponding dirstate node
679 ///
680 ///
680 /// Returns whether that path is ignored
681 /// Returns whether that path is ignored
681 fn traverse_fs_only(
682 fn traverse_fs_only(
682 &self,
683 &self,
683 has_ignored_ancestor: bool,
684 has_ignored_ancestor: bool,
684 directory_hg_path: &HgPath,
685 directory_hg_path: &HgPath,
685 fs_entry: &DirEntry,
686 fs_entry: &DirEntry,
686 ) -> bool {
687 ) -> bool {
687 let hg_path = directory_hg_path.join(&fs_entry.base_name);
688 let hg_path = directory_hg_path.join(&fs_entry.base_name);
688 let file_type = fs_entry.metadata.file_type();
689 let file_type = fs_entry.metadata.file_type();
689 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
690 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
690 if file_type.is_dir() {
691 if file_type.is_dir() {
691 let is_ignored =
692 let is_ignored =
692 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
693 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
693 let traverse_children = if is_ignored {
694 let traverse_children = if is_ignored {
694 // Descendants of an ignored directory are all ignored
695 // Descendants of an ignored directory are all ignored
695 self.options.list_ignored
696 self.options.list_ignored
696 } else {
697 } else {
697 // Descendants of an unknown directory may be either unknown or
698 // Descendants of an unknown directory may be either unknown or
698 // ignored
699 // ignored
699 self.options.list_unknown || self.options.list_ignored
700 self.options.list_unknown || self.options.list_ignored
700 };
701 };
701 if traverse_children {
702 if traverse_children {
702 let is_at_repo_root = false;
703 let is_at_repo_root = false;
703 if let Ok(children_fs_entries) = self.read_dir(
704 if let Ok(children_fs_entries) = self.read_dir(
704 &hg_path,
705 &hg_path,
705 &fs_entry.full_path,
706 &fs_entry.full_path,
706 is_at_repo_root,
707 is_at_repo_root,
707 ) {
708 ) {
708 children_fs_entries.par_iter().for_each(|child_fs_entry| {
709 children_fs_entries.par_iter().for_each(|child_fs_entry| {
709 self.traverse_fs_only(
710 self.traverse_fs_only(
710 is_ignored,
711 is_ignored,
711 &hg_path,
712 &hg_path,
712 child_fs_entry,
713 child_fs_entry,
713 );
714 );
714 })
715 })
715 }
716 }
716 }
717 }
717 if self.options.collect_traversed_dirs {
718 if self.options.collect_traversed_dirs {
718 self.outcome.lock().unwrap().traversed.push(hg_path.into())
719 self.outcome.lock().unwrap().traversed.push(hg_path.into())
719 }
720 }
720 is_ignored
721 is_ignored
721 } else {
722 } else {
722 if file_or_symlink {
723 if file_or_symlink {
723 if self.matcher.matches(&hg_path) {
724 if self.matcher.matches(&hg_path) {
724 self.mark_unknown_or_ignored(
725 self.mark_unknown_or_ignored(
725 has_ignored_ancestor,
726 has_ignored_ancestor,
726 &BorrowedPath::InMemory(&hg_path),
727 &BorrowedPath::InMemory(&hg_path),
727 )
728 )
728 } else {
729 } else {
729 // We haven’t computed whether this path is ignored. It
730 // We haven’t computed whether this path is ignored. It
730 // might not be, and a future run of status might have a
731 // might not be, and a future run of status might have a
731 // different matcher that matches it. So treat it as not
732 // different matcher that matches it. So treat it as not
732 // ignored. That is, inhibit readdir caching of the parent
733 // ignored. That is, inhibit readdir caching of the parent
733 // directory.
734 // directory.
734 false
735 false
735 }
736 }
736 } else {
737 } else {
737 // This is neither a directory, a plain file, or a symlink.
738 // This is neither a directory, a plain file, or a symlink.
738 // Treat it like an ignored file.
739 // Treat it like an ignored file.
739 true
740 true
740 }
741 }
741 }
742 }
742 }
743 }
743
744
744 /// Returns whether that path is ignored
745 /// Returns whether that path is ignored
745 fn mark_unknown_or_ignored(
746 fn mark_unknown_or_ignored(
746 &self,
747 &self,
747 has_ignored_ancestor: bool,
748 has_ignored_ancestor: bool,
748 hg_path: &BorrowedPath<'_, 'on_disk>,
749 hg_path: &BorrowedPath<'_, 'on_disk>,
749 ) -> bool {
750 ) -> bool {
750 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
751 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
751 if is_ignored {
752 if is_ignored {
752 if self.options.list_ignored {
753 if self.options.list_ignored {
753 self.push_outcome_without_copy_source(
754 self.push_outcome_without_copy_source(
754 Outcome::Ignored,
755 Outcome::Ignored,
755 hg_path,
756 hg_path,
756 )
757 )
757 }
758 }
758 } else {
759 } else {
759 if self.options.list_unknown {
760 if self.options.list_unknown {
760 self.push_outcome_without_copy_source(
761 self.push_outcome_without_copy_source(
761 Outcome::Unknown,
762 Outcome::Unknown,
762 hg_path,
763 hg_path,
763 )
764 )
764 }
765 }
765 }
766 }
766 is_ignored
767 is_ignored
767 }
768 }
768 }
769 }
769
770
770 struct DirEntry {
771 struct DirEntry {
771 base_name: HgPathBuf,
772 base_name: HgPathBuf,
772 full_path: PathBuf,
773 full_path: PathBuf,
773 metadata: std::fs::Metadata,
774 metadata: std::fs::Metadata,
774 }
775 }
775
776
776 impl DirEntry {
777 impl DirEntry {
777 /// Returns **unsorted** entries in the given directory, with name and
778 /// Returns **unsorted** entries in the given directory, with name and
778 /// metadata.
779 /// metadata.
779 ///
780 ///
780 /// If a `.hg` sub-directory is encountered:
781 /// If a `.hg` sub-directory is encountered:
781 ///
782 ///
782 /// * At the repository root, ignore that sub-directory
783 /// * At the repository root, ignore that sub-directory
783 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
784 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
784 /// list instead.
785 /// list instead.
785 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
786 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
786 // `read_dir` returns a "not found" error for the empty path
787 // `read_dir` returns a "not found" error for the empty path
787 let at_cwd = path == Path::new("");
788 let at_cwd = path == Path::new("");
788 let read_dir_path = if at_cwd { Path::new(".") } else { path };
789 let read_dir_path = if at_cwd { Path::new(".") } else { path };
789 let mut results = Vec::new();
790 let mut results = Vec::new();
790 for entry in read_dir_path.read_dir()? {
791 for entry in read_dir_path.read_dir()? {
791 let entry = entry?;
792 let entry = entry?;
792 let metadata = match entry.metadata() {
793 let metadata = match entry.metadata() {
793 Ok(v) => v,
794 Ok(v) => v,
794 Err(e) => {
795 Err(e) => {
795 // race with file deletion?
796 // race with file deletion?
796 if e.kind() == std::io::ErrorKind::NotFound {
797 if e.kind() == std::io::ErrorKind::NotFound {
797 continue;
798 continue;
798 } else {
799 } else {
799 return Err(e);
800 return Err(e);
800 }
801 }
801 }
802 }
802 };
803 };
803 let file_name = entry.file_name();
804 let file_name = entry.file_name();
804 // FIXME don't do this when cached
805 // FIXME don't do this when cached
805 if file_name == ".hg" {
806 if file_name == ".hg" {
806 if is_at_repo_root {
807 if is_at_repo_root {
807 // Skip the repo’s own .hg (might be a symlink)
808 // Skip the repo’s own .hg (might be a symlink)
808 continue;
809 continue;
809 } else if metadata.is_dir() {
810 } else if metadata.is_dir() {
810 // A .hg sub-directory at another location means a subrepo,
811 // A .hg sub-directory at another location means a subrepo,
811 // skip it entirely.
812 // skip it entirely.
812 return Ok(Vec::new());
813 return Ok(Vec::new());
813 }
814 }
814 }
815 }
815 let full_path = if at_cwd {
816 let full_path = if at_cwd {
816 file_name.clone().into()
817 file_name.clone().into()
817 } else {
818 } else {
818 entry.path()
819 entry.path()
819 };
820 };
820 let base_name = get_bytes_from_os_string(file_name).into();
821 let base_name = get_bytes_from_os_string(file_name).into();
821 results.push(DirEntry {
822 results.push(DirEntry {
822 base_name,
823 base_name,
823 full_path,
824 full_path,
824 metadata,
825 metadata,
825 })
826 })
826 }
827 }
827 Ok(results)
828 Ok(results)
828 }
829 }
829 }
830 }
830
831
831 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
832 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
832 /// of the give repository.
833 /// of the give repository.
833 ///
834 ///
834 /// This is similar to `SystemTime::now()`, with the result truncated to the
835 /// This is similar to `SystemTime::now()`, with the result truncated to the
835 /// same time resolution as other files’ modification times. Using `.hg`
836 /// same time resolution as other files’ modification times. Using `.hg`
836 /// instead of the system’s default temporary directory (such as `/tmp`) makes
837 /// instead of the system’s default temporary directory (such as `/tmp`) makes
837 /// it more likely the temporary file is in the same disk partition as contents
838 /// it more likely the temporary file is in the same disk partition as contents
838 /// of the working directory, which can matter since different filesystems may
839 /// of the working directory, which can matter since different filesystems may
839 /// store timestamps with different resolutions.
840 /// store timestamps with different resolutions.
840 ///
841 ///
841 /// This may fail, typically if we lack write permissions. In that case we
842 /// This may fail, typically if we lack write permissions. In that case we
842 /// should continue the `status()` algoritm anyway and consider the current
843 /// should continue the `status()` algoritm anyway and consider the current
843 /// date/time to be unknown.
844 /// date/time to be unknown.
844 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
845 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
845 tempfile::tempfile_in(repo_root.join(".hg"))?
846 tempfile::tempfile_in(repo_root.join(".hg"))?
846 .metadata()?
847 .metadata()?
847 .modified()
848 .modified()
848 }
849 }
@@ -1,540 +1,533 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
7 use crate::errors::HgResultExt;
6 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
7 use crate::errors::{HgError, IoResultExt};
9 use crate::exit_codes;
8 use crate::exit_codes;
10 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::lock::{try_with_lock_no_wait, LockError};
11 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
12 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
13 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
14 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
16 use crate::utils::SliceExt;
15 use crate::utils::SliceExt;
17 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::vfs::{is_dir, is_file, Vfs};
18 use crate::{requirements, NodePrefix};
17 use crate::{requirements, NodePrefix};
19 use crate::{DirstateError, Revision};
18 use crate::{DirstateError, Revision};
20 use std::cell::{Ref, RefCell, RefMut};
19 use std::cell::{Ref, RefCell, RefMut};
21 use std::collections::HashSet;
20 use std::collections::HashSet;
22 use std::io::Seek;
21 use std::io::Seek;
23 use std::io::SeekFrom;
22 use std::io::SeekFrom;
24 use std::io::Write as IoWrite;
23 use std::io::Write as IoWrite;
25 use std::path::{Path, PathBuf};
24 use std::path::{Path, PathBuf};
26
25
27 /// A repository on disk
26 /// A repository on disk
28 pub struct Repo {
27 pub struct Repo {
29 working_directory: PathBuf,
28 working_directory: PathBuf,
30 dot_hg: PathBuf,
29 dot_hg: PathBuf,
31 store: PathBuf,
30 store: PathBuf,
32 requirements: HashSet<String>,
31 requirements: HashSet<String>,
33 config: Config,
32 config: Config,
34 dirstate_parents: LazyCell<DirstateParents, HgError>,
33 dirstate_parents: LazyCell<DirstateParents, HgError>,
35 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
36 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
35 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
37 changelog: LazyCell<Changelog, HgError>,
36 changelog: LazyCell<Changelog, HgError>,
38 manifestlog: LazyCell<Manifestlog, HgError>,
37 manifestlog: LazyCell<Manifestlog, HgError>,
39 }
38 }
40
39
41 #[derive(Debug, derive_more::From)]
40 #[derive(Debug, derive_more::From)]
42 pub enum RepoError {
41 pub enum RepoError {
43 NotFound {
42 NotFound {
44 at: PathBuf,
43 at: PathBuf,
45 },
44 },
46 #[from]
45 #[from]
47 ConfigParseError(ConfigParseError),
46 ConfigParseError(ConfigParseError),
48 #[from]
47 #[from]
49 Other(HgError),
48 Other(HgError),
50 }
49 }
51
50
52 impl From<ConfigError> for RepoError {
51 impl From<ConfigError> for RepoError {
53 fn from(error: ConfigError) -> Self {
52 fn from(error: ConfigError) -> Self {
54 match error {
53 match error {
55 ConfigError::Parse(error) => error.into(),
54 ConfigError::Parse(error) => error.into(),
56 ConfigError::Other(error) => error.into(),
55 ConfigError::Other(error) => error.into(),
57 }
56 }
58 }
57 }
59 }
58 }
60
59
61 impl Repo {
60 impl Repo {
62 /// tries to find nearest repository root in current working directory or
61 /// tries to find nearest repository root in current working directory or
63 /// its ancestors
62 /// its ancestors
64 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
65 let current_directory = crate::utils::current_dir()?;
64 let current_directory = crate::utils::current_dir()?;
66 // ancestors() is inclusive: it first yields `current_directory`
65 // ancestors() is inclusive: it first yields `current_directory`
67 // as-is.
66 // as-is.
68 for ancestor in current_directory.ancestors() {
67 for ancestor in current_directory.ancestors() {
69 if is_dir(ancestor.join(".hg"))? {
68 if is_dir(ancestor.join(".hg"))? {
70 return Ok(ancestor.to_path_buf());
69 return Ok(ancestor.to_path_buf());
71 }
70 }
72 }
71 }
73 return Err(RepoError::NotFound {
72 return Err(RepoError::NotFound {
74 at: current_directory,
73 at: current_directory,
75 });
74 });
76 }
75 }
77
76
78 /// Find a repository, either at the given path (which must contain a `.hg`
77 /// Find a repository, either at the given path (which must contain a `.hg`
79 /// sub-directory) or by searching the current directory and its
78 /// sub-directory) or by searching the current directory and its
80 /// ancestors.
79 /// ancestors.
81 ///
80 ///
82 /// A method with two very different "modes" like this usually a code smell
81 /// A method with two very different "modes" like this usually a code smell
83 /// to make two methods instead, but in this case an `Option` is what rhg
82 /// to make two methods instead, but in this case an `Option` is what rhg
84 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
85 /// Having two methods would just move that `if` to almost all callers.
84 /// Having two methods would just move that `if` to almost all callers.
86 pub fn find(
85 pub fn find(
87 config: &Config,
86 config: &Config,
88 explicit_path: Option<PathBuf>,
87 explicit_path: Option<PathBuf>,
89 ) -> Result<Self, RepoError> {
88 ) -> Result<Self, RepoError> {
90 if let Some(root) = explicit_path {
89 if let Some(root) = explicit_path {
91 if is_dir(root.join(".hg"))? {
90 if is_dir(root.join(".hg"))? {
92 Self::new_at_path(root.to_owned(), config)
91 Self::new_at_path(root.to_owned(), config)
93 } else if is_file(&root)? {
92 } else if is_file(&root)? {
94 Err(HgError::unsupported("bundle repository").into())
93 Err(HgError::unsupported("bundle repository").into())
95 } else {
94 } else {
96 Err(RepoError::NotFound {
95 Err(RepoError::NotFound {
97 at: root.to_owned(),
96 at: root.to_owned(),
98 })
97 })
99 }
98 }
100 } else {
99 } else {
101 let root = Self::find_repo_root()?;
100 let root = Self::find_repo_root()?;
102 Self::new_at_path(root, config)
101 Self::new_at_path(root, config)
103 }
102 }
104 }
103 }
105
104
106 /// To be called after checking that `.hg` is a sub-directory
105 /// To be called after checking that `.hg` is a sub-directory
107 fn new_at_path(
106 fn new_at_path(
108 working_directory: PathBuf,
107 working_directory: PathBuf,
109 config: &Config,
108 config: &Config,
110 ) -> Result<Self, RepoError> {
109 ) -> Result<Self, RepoError> {
111 let dot_hg = working_directory.join(".hg");
110 let dot_hg = working_directory.join(".hg");
112
111
113 let mut repo_config_files = Vec::new();
112 let mut repo_config_files = Vec::new();
114 repo_config_files.push(dot_hg.join("hgrc"));
113 repo_config_files.push(dot_hg.join("hgrc"));
115 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
116
115
117 let hg_vfs = Vfs { base: &dot_hg };
116 let hg_vfs = Vfs { base: &dot_hg };
118 let mut reqs = requirements::load_if_exists(hg_vfs)?;
117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 let relative =
118 let relative =
120 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 let shared =
120 let shared =
122 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123
122
124 // From `mercurial/localrepo.py`:
123 // From `mercurial/localrepo.py`:
125 //
124 //
126 // if .hg/requires contains the sharesafe requirement, it means
125 // if .hg/requires contains the sharesafe requirement, it means
127 // there exists a `.hg/store/requires` too and we should read it
126 // there exists a `.hg/store/requires` too and we should read it
128 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 // is not present, refer checkrequirementscompat() for that
129 // is not present, refer checkrequirementscompat() for that
131 //
130 //
132 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 // repository was shared the old way. We check the share source
132 // repository was shared the old way. We check the share source
134 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 // current repository needs to be reshared
134 // current repository needs to be reshared
136 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137
136
138 let store_path;
137 let store_path;
139 if !shared {
138 if !shared {
140 store_path = dot_hg.join("store");
139 store_path = dot_hg.join("store");
141 } else {
140 } else {
142 let bytes = hg_vfs.read("sharedpath")?;
141 let bytes = hg_vfs.read("sharedpath")?;
143 let mut shared_path =
142 let mut shared_path =
144 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 .to_owned();
144 .to_owned();
146 if relative {
145 if relative {
147 shared_path = dot_hg.join(shared_path)
146 shared_path = dot_hg.join(shared_path)
148 }
147 }
149 if !is_dir(&shared_path)? {
148 if !is_dir(&shared_path)? {
150 return Err(HgError::corrupted(format!(
149 return Err(HgError::corrupted(format!(
151 ".hg/sharedpath points to nonexistent directory {}",
150 ".hg/sharedpath points to nonexistent directory {}",
152 shared_path.display()
151 shared_path.display()
153 ))
152 ))
154 .into());
153 .into());
155 }
154 }
156
155
157 store_path = shared_path.join("store");
156 store_path = shared_path.join("store");
158
157
159 let source_is_share_safe =
158 let source_is_share_safe =
160 requirements::load(Vfs { base: &shared_path })?
159 requirements::load(Vfs { base: &shared_path })?
161 .contains(requirements::SHARESAFE_REQUIREMENT);
160 .contains(requirements::SHARESAFE_REQUIREMENT);
162
161
163 if share_safe && !source_is_share_safe {
162 if share_safe && !source_is_share_safe {
164 return Err(match config
163 return Err(match config
165 .get(b"share", b"safe-mismatch.source-not-safe")
164 .get(b"share", b"safe-mismatch.source-not-safe")
166 {
165 {
167 Some(b"abort") | None => HgError::abort(
166 Some(b"abort") | None => HgError::abort(
168 "abort: share source does not support share-safe requirement\n\
167 "abort: share source does not support share-safe requirement\n\
169 (see `hg help config.format.use-share-safe` for more information)",
168 (see `hg help config.format.use-share-safe` for more information)",
170 exit_codes::ABORT,
169 exit_codes::ABORT,
171 ),
170 ),
172 _ => HgError::unsupported("share-safe downgrade"),
171 _ => HgError::unsupported("share-safe downgrade"),
173 }
172 }
174 .into());
173 .into());
175 } else if source_is_share_safe && !share_safe {
174 } else if source_is_share_safe && !share_safe {
176 return Err(
175 return Err(
177 match config.get(b"share", b"safe-mismatch.source-safe") {
176 match config.get(b"share", b"safe-mismatch.source-safe") {
178 Some(b"abort") | None => HgError::abort(
177 Some(b"abort") | None => HgError::abort(
179 "abort: version mismatch: source uses share-safe \
178 "abort: version mismatch: source uses share-safe \
180 functionality while the current share does not\n\
179 functionality while the current share does not\n\
181 (see `hg help config.format.use-share-safe` for more information)",
180 (see `hg help config.format.use-share-safe` for more information)",
182 exit_codes::ABORT,
181 exit_codes::ABORT,
183 ),
182 ),
184 _ => HgError::unsupported("share-safe upgrade"),
183 _ => HgError::unsupported("share-safe upgrade"),
185 }
184 }
186 .into(),
185 .into(),
187 );
186 );
188 }
187 }
189
188
190 if share_safe {
189 if share_safe {
191 repo_config_files.insert(0, shared_path.join("hgrc"))
190 repo_config_files.insert(0, shared_path.join("hgrc"))
192 }
191 }
193 }
192 }
194 if share_safe {
193 if share_safe {
195 reqs.extend(requirements::load(Vfs { base: &store_path })?);
194 reqs.extend(requirements::load(Vfs { base: &store_path })?);
196 }
195 }
197
196
198 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
197 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
199 config.combine_with_repo(&repo_config_files)?
198 config.combine_with_repo(&repo_config_files)?
200 } else {
199 } else {
201 config.clone()
200 config.clone()
202 };
201 };
203
202
204 let repo = Self {
203 let repo = Self {
205 requirements: reqs,
204 requirements: reqs,
206 working_directory,
205 working_directory,
207 store: store_path,
206 store: store_path,
208 dot_hg,
207 dot_hg,
209 config: repo_config,
208 config: repo_config,
210 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
209 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
211 dirstate_data_file_uuid: LazyCell::new(
210 dirstate_data_file_uuid: LazyCell::new(
212 Self::read_dirstate_data_file_uuid,
211 Self::read_dirstate_data_file_uuid,
213 ),
212 ),
214 dirstate_map: LazyCell::new(Self::new_dirstate_map),
213 dirstate_map: LazyCell::new(Self::new_dirstate_map),
215 changelog: LazyCell::new(Changelog::open),
214 changelog: LazyCell::new(Changelog::open),
216 manifestlog: LazyCell::new(Manifestlog::open),
215 manifestlog: LazyCell::new(Manifestlog::open),
217 };
216 };
218
217
219 requirements::check(&repo)?;
218 requirements::check(&repo)?;
220
219
221 Ok(repo)
220 Ok(repo)
222 }
221 }
223
222
224 pub fn working_directory_path(&self) -> &Path {
223 pub fn working_directory_path(&self) -> &Path {
225 &self.working_directory
224 &self.working_directory
226 }
225 }
227
226
228 pub fn requirements(&self) -> &HashSet<String> {
227 pub fn requirements(&self) -> &HashSet<String> {
229 &self.requirements
228 &self.requirements
230 }
229 }
231
230
232 pub fn config(&self) -> &Config {
231 pub fn config(&self) -> &Config {
233 &self.config
232 &self.config
234 }
233 }
235
234
236 /// For accessing repository files (in `.hg`), except for the store
235 /// For accessing repository files (in `.hg`), except for the store
237 /// (`.hg/store`).
236 /// (`.hg/store`).
238 pub fn hg_vfs(&self) -> Vfs<'_> {
237 pub fn hg_vfs(&self) -> Vfs<'_> {
239 Vfs { base: &self.dot_hg }
238 Vfs { base: &self.dot_hg }
240 }
239 }
241
240
242 /// For accessing repository store files (in `.hg/store`)
241 /// For accessing repository store files (in `.hg/store`)
243 pub fn store_vfs(&self) -> Vfs<'_> {
242 pub fn store_vfs(&self) -> Vfs<'_> {
244 Vfs { base: &self.store }
243 Vfs { base: &self.store }
245 }
244 }
246
245
247 /// For accessing the working copy
246 /// For accessing the working copy
248 pub fn working_directory_vfs(&self) -> Vfs<'_> {
247 pub fn working_directory_vfs(&self) -> Vfs<'_> {
249 Vfs {
248 Vfs {
250 base: &self.working_directory,
249 base: &self.working_directory,
251 }
250 }
252 }
251 }
253
252
254 pub fn try_with_wlock_no_wait<R>(
253 pub fn try_with_wlock_no_wait<R>(
255 &self,
254 &self,
256 f: impl FnOnce() -> R,
255 f: impl FnOnce() -> R,
257 ) -> Result<R, LockError> {
256 ) -> Result<R, LockError> {
258 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
257 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
259 }
258 }
260
259
261 pub fn has_dirstate_v2(&self) -> bool {
260 pub fn has_dirstate_v2(&self) -> bool {
262 self.requirements
261 self.requirements
263 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
262 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
264 }
263 }
265
264
266 pub fn has_sparse(&self) -> bool {
265 pub fn has_sparse(&self) -> bool {
267 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
266 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
268 }
267 }
269
268
270 pub fn has_narrow(&self) -> bool {
269 pub fn has_narrow(&self) -> bool {
271 self.requirements.contains(requirements::NARROW_REQUIREMENT)
270 self.requirements.contains(requirements::NARROW_REQUIREMENT)
272 }
271 }
273
272
274 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
273 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
275 Ok(self
274 Ok(self
276 .hg_vfs()
275 .hg_vfs()
277 .read("dirstate")
276 .read("dirstate")
278 .io_not_found_as_none()?
277 .io_not_found_as_none()?
279 .unwrap_or(Vec::new()))
278 .unwrap_or(Vec::new()))
280 }
279 }
281
280
282 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
281 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
283 Ok(*self.dirstate_parents.get_or_init(self)?)
282 Ok(*self.dirstate_parents.get_or_init(self)?)
284 }
283 }
285
284
286 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
285 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
287 let dirstate = self.dirstate_file_contents()?;
286 let dirstate = self.dirstate_file_contents()?;
288 let parents = if dirstate.is_empty() {
287 let parents = if dirstate.is_empty() {
289 if self.has_dirstate_v2() {
288 if self.has_dirstate_v2() {
290 self.dirstate_data_file_uuid.set(None);
289 self.dirstate_data_file_uuid.set(None);
291 }
290 }
292 DirstateParents::NULL
291 DirstateParents::NULL
293 } else if self.has_dirstate_v2() {
292 } else if self.has_dirstate_v2() {
294 let docket =
293 let docket =
295 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
294 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
296 self.dirstate_data_file_uuid
295 self.dirstate_data_file_uuid
297 .set(Some(docket.uuid.to_owned()));
296 .set(Some(docket.uuid.to_owned()));
298 docket.parents()
297 docket.parents()
299 } else {
298 } else {
300 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
299 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
301 .clone()
300 .clone()
302 };
301 };
303 self.dirstate_parents.set(parents);
302 self.dirstate_parents.set(parents);
304 Ok(parents)
303 Ok(parents)
305 }
304 }
306
305
307 fn read_dirstate_data_file_uuid(
306 fn read_dirstate_data_file_uuid(
308 &self,
307 &self,
309 ) -> Result<Option<Vec<u8>>, HgError> {
308 ) -> Result<Option<Vec<u8>>, HgError> {
310 assert!(
309 assert!(
311 self.has_dirstate_v2(),
310 self.has_dirstate_v2(),
312 "accessing dirstate data file ID without dirstate-v2"
311 "accessing dirstate data file ID without dirstate-v2"
313 );
312 );
314 let dirstate = self.dirstate_file_contents()?;
313 let dirstate = self.dirstate_file_contents()?;
315 if dirstate.is_empty() {
314 if dirstate.is_empty() {
316 self.dirstate_parents.set(DirstateParents::NULL);
315 self.dirstate_parents.set(DirstateParents::NULL);
317 Ok(None)
316 Ok(None)
318 } else {
317 } else {
319 let docket =
318 let docket =
320 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
319 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
321 self.dirstate_parents.set(docket.parents());
320 self.dirstate_parents.set(docket.parents());
322 Ok(Some(docket.uuid.to_owned()))
321 Ok(Some(docket.uuid.to_owned()))
323 }
322 }
324 }
323 }
325
324
326 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
325 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
327 let dirstate_file_contents = self.dirstate_file_contents()?;
326 let dirstate_file_contents = self.dirstate_file_contents()?;
328 if dirstate_file_contents.is_empty() {
327 if dirstate_file_contents.is_empty() {
329 self.dirstate_parents.set(DirstateParents::NULL);
328 self.dirstate_parents.set(DirstateParents::NULL);
330 if self.has_dirstate_v2() {
329 if self.has_dirstate_v2() {
331 self.dirstate_data_file_uuid.set(None);
330 self.dirstate_data_file_uuid.set(None);
332 }
331 }
333 Ok(OwningDirstateMap::new_empty(Vec::new()))
332 Ok(OwningDirstateMap::new_empty(Vec::new()))
334 } else if self.has_dirstate_v2() {
333 } else if self.has_dirstate_v2() {
335 let docket = crate::dirstate_tree::on_disk::read_docket(
334 let docket = crate::dirstate_tree::on_disk::read_docket(
336 &dirstate_file_contents,
335 &dirstate_file_contents,
337 )?;
336 )?;
338 self.dirstate_parents.set(docket.parents());
337 self.dirstate_parents.set(docket.parents());
339 self.dirstate_data_file_uuid
338 self.dirstate_data_file_uuid
340 .set(Some(docket.uuid.to_owned()));
339 .set(Some(docket.uuid.to_owned()));
341 let data_size = docket.data_size();
340 let data_size = docket.data_size();
342 let metadata = docket.tree_metadata();
341 let metadata = docket.tree_metadata();
343 let mut map = if let Some(data_mmap) = self
342 if let Some(data_mmap) = self
344 .hg_vfs()
343 .hg_vfs()
345 .mmap_open(docket.data_filename())
344 .mmap_open(docket.data_filename())
346 .io_not_found_as_none()?
345 .io_not_found_as_none()?
347 {
346 {
348 OwningDirstateMap::new_empty(data_mmap)
347 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
349 } else {
348 } else {
350 OwningDirstateMap::new_empty(Vec::new())
349 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
351 };
350 }
352 let (on_disk, placeholder) = map.get_pair_mut();
353 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
354 Ok(map)
355 } else {
351 } else {
356 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
352 let (map, parents) =
357 let (on_disk, placeholder) = map.get_pair_mut();
353 OwningDirstateMap::new_v1(dirstate_file_contents)?;
358 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
354 self.dirstate_parents.set(parents);
359 self.dirstate_parents
360 .set(parents.unwrap_or(DirstateParents::NULL));
361 *placeholder = inner;
362 Ok(map)
355 Ok(map)
363 }
356 }
364 }
357 }
365
358
366 pub fn dirstate_map(
359 pub fn dirstate_map(
367 &self,
360 &self,
368 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
361 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
369 self.dirstate_map.get_or_init(self)
362 self.dirstate_map.get_or_init(self)
370 }
363 }
371
364
372 pub fn dirstate_map_mut(
365 pub fn dirstate_map_mut(
373 &self,
366 &self,
374 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
367 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
375 self.dirstate_map.get_mut_or_init(self)
368 self.dirstate_map.get_mut_or_init(self)
376 }
369 }
377
370
378 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
371 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
379 self.changelog.get_or_init(self)
372 self.changelog.get_or_init(self)
380 }
373 }
381
374
382 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
375 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
383 self.changelog.get_mut_or_init(self)
376 self.changelog.get_mut_or_init(self)
384 }
377 }
385
378
386 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
379 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
387 self.manifestlog.get_or_init(self)
380 self.manifestlog.get_or_init(self)
388 }
381 }
389
382
390 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
383 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
391 self.manifestlog.get_mut_or_init(self)
384 self.manifestlog.get_mut_or_init(self)
392 }
385 }
393
386
394 /// Returns the manifest of the *changeset* with the given node ID
387 /// Returns the manifest of the *changeset* with the given node ID
395 pub fn manifest_for_node(
388 pub fn manifest_for_node(
396 &self,
389 &self,
397 node: impl Into<NodePrefix>,
390 node: impl Into<NodePrefix>,
398 ) -> Result<Manifest, RevlogError> {
391 ) -> Result<Manifest, RevlogError> {
399 self.manifestlog()?.data_for_node(
392 self.manifestlog()?.data_for_node(
400 self.changelog()?
393 self.changelog()?
401 .data_for_node(node.into())?
394 .data_for_node(node.into())?
402 .manifest_node()?
395 .manifest_node()?
403 .into(),
396 .into(),
404 )
397 )
405 }
398 }
406
399
407 /// Returns the manifest of the *changeset* with the given revision number
400 /// Returns the manifest of the *changeset* with the given revision number
408 pub fn manifest_for_rev(
401 pub fn manifest_for_rev(
409 &self,
402 &self,
410 revision: Revision,
403 revision: Revision,
411 ) -> Result<Manifest, RevlogError> {
404 ) -> Result<Manifest, RevlogError> {
412 self.manifestlog()?.data_for_node(
405 self.manifestlog()?.data_for_node(
413 self.changelog()?
406 self.changelog()?
414 .data_for_rev(revision)?
407 .data_for_rev(revision)?
415 .manifest_node()?
408 .manifest_node()?
416 .into(),
409 .into(),
417 )
410 )
418 }
411 }
419
412
420 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
413 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
421 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
414 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
422 Ok(entry.state().is_tracked())
415 Ok(entry.state().is_tracked())
423 } else {
416 } else {
424 Ok(false)
417 Ok(false)
425 }
418 }
426 }
419 }
427
420
428 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
421 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
429 Filelog::open(self, path)
422 Filelog::open(self, path)
430 }
423 }
431
424
432 /// Write to disk any updates that were made through `dirstate_map_mut`.
425 /// Write to disk any updates that were made through `dirstate_map_mut`.
433 ///
426 ///
434 /// The "wlock" must be held while calling this.
427 /// The "wlock" must be held while calling this.
435 /// See for example `try_with_wlock_no_wait`.
428 /// See for example `try_with_wlock_no_wait`.
436 ///
429 ///
437 /// TODO: have a `WritableRepo` type only accessible while holding the
430 /// TODO: have a `WritableRepo` type only accessible while holding the
438 /// lock?
431 /// lock?
439 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
432 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
440 let map = self.dirstate_map()?;
433 let map = self.dirstate_map()?;
441 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
434 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
442 // it’s unset
435 // it’s unset
443 let parents = self.dirstate_parents()?;
436 let parents = self.dirstate_parents()?;
444 let packed_dirstate = if self.has_dirstate_v2() {
437 let packed_dirstate = if self.has_dirstate_v2() {
445 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
438 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
446 let mut uuid = uuid.as_ref();
439 let mut uuid = uuid.as_ref();
447 let can_append = uuid.is_some();
440 let can_append = uuid.is_some();
448 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
441 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
449 if !append {
442 if !append {
450 uuid = None
443 uuid = None
451 }
444 }
452 let uuid = if let Some(uuid) = uuid {
445 let uuid = if let Some(uuid) = uuid {
453 std::str::from_utf8(uuid)
446 std::str::from_utf8(uuid)
454 .map_err(|_| {
447 .map_err(|_| {
455 HgError::corrupted("non-UTF-8 dirstate data file ID")
448 HgError::corrupted("non-UTF-8 dirstate data file ID")
456 })?
449 })?
457 .to_owned()
450 .to_owned()
458 } else {
451 } else {
459 DirstateDocket::new_uid()
452 DirstateDocket::new_uid()
460 };
453 };
461 let data_filename = format!("dirstate.{}", uuid);
454 let data_filename = format!("dirstate.{}", uuid);
462 let data_filename = self.hg_vfs().join(data_filename);
455 let data_filename = self.hg_vfs().join(data_filename);
463 let mut options = std::fs::OpenOptions::new();
456 let mut options = std::fs::OpenOptions::new();
464 if append {
457 if append {
465 options.append(true);
458 options.append(true);
466 } else {
459 } else {
467 options.write(true).create_new(true);
460 options.write(true).create_new(true);
468 }
461 }
469 let data_size = (|| {
462 let data_size = (|| {
470 // TODO: loop and try another random ID if !append and this
463 // TODO: loop and try another random ID if !append and this
471 // returns `ErrorKind::AlreadyExists`? Collision chance of two
464 // returns `ErrorKind::AlreadyExists`? Collision chance of two
472 // random IDs is one in 2**32
465 // random IDs is one in 2**32
473 let mut file = options.open(&data_filename)?;
466 let mut file = options.open(&data_filename)?;
474 file.write_all(&data)?;
467 file.write_all(&data)?;
475 file.flush()?;
468 file.flush()?;
476 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
469 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
477 file.seek(SeekFrom::Current(0))
470 file.seek(SeekFrom::Current(0))
478 })()
471 })()
479 .when_writing_file(&data_filename)?;
472 .when_writing_file(&data_filename)?;
480 DirstateDocket::serialize(
473 DirstateDocket::serialize(
481 parents,
474 parents,
482 tree_metadata,
475 tree_metadata,
483 data_size,
476 data_size,
484 uuid.as_bytes(),
477 uuid.as_bytes(),
485 )
478 )
486 .map_err(|_: std::num::TryFromIntError| {
479 .map_err(|_: std::num::TryFromIntError| {
487 HgError::corrupted("overflow in dirstate docket serialization")
480 HgError::corrupted("overflow in dirstate docket serialization")
488 })?
481 })?
489 } else {
482 } else {
490 map.pack_v1(parents)?
483 map.pack_v1(parents)?
491 };
484 };
492 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
485 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
493 Ok(())
486 Ok(())
494 }
487 }
495 }
488 }
496
489
497 /// Lazily-initialized component of `Repo` with interior mutability
490 /// Lazily-initialized component of `Repo` with interior mutability
498 ///
491 ///
499 /// This differs from `OnceCell` in that the value can still be "deinitialized"
492 /// This differs from `OnceCell` in that the value can still be "deinitialized"
500 /// later by setting its inner `Option` to `None`.
493 /// later by setting its inner `Option` to `None`.
501 struct LazyCell<T, E> {
494 struct LazyCell<T, E> {
502 value: RefCell<Option<T>>,
495 value: RefCell<Option<T>>,
503 // `Fn`s that don’t capture environment are zero-size, so this box does
496 // `Fn`s that don’t capture environment are zero-size, so this box does
504 // not allocate:
497 // not allocate:
505 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
498 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
506 }
499 }
507
500
508 impl<T, E> LazyCell<T, E> {
501 impl<T, E> LazyCell<T, E> {
509 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
502 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
510 Self {
503 Self {
511 value: RefCell::new(None),
504 value: RefCell::new(None),
512 init: Box::new(init),
505 init: Box::new(init),
513 }
506 }
514 }
507 }
515
508
516 fn set(&self, value: T) {
509 fn set(&self, value: T) {
517 *self.value.borrow_mut() = Some(value)
510 *self.value.borrow_mut() = Some(value)
518 }
511 }
519
512
520 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
513 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
521 let mut borrowed = self.value.borrow();
514 let mut borrowed = self.value.borrow();
522 if borrowed.is_none() {
515 if borrowed.is_none() {
523 drop(borrowed);
516 drop(borrowed);
524 // Only use `borrow_mut` if it is really needed to avoid panic in
517 // Only use `borrow_mut` if it is really needed to avoid panic in
525 // case there is another outstanding borrow but mutation is not
518 // case there is another outstanding borrow but mutation is not
526 // needed.
519 // needed.
527 *self.value.borrow_mut() = Some((self.init)(repo)?);
520 *self.value.borrow_mut() = Some((self.init)(repo)?);
528 borrowed = self.value.borrow()
521 borrowed = self.value.borrow()
529 }
522 }
530 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
523 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
531 }
524 }
532
525
533 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
526 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
534 let mut borrowed = self.value.borrow_mut();
527 let mut borrowed = self.value.borrow_mut();
535 if borrowed.is_none() {
528 if borrowed.is_none() {
536 *borrowed = Some((self.init)(repo)?);
529 *borrowed = Some((self.init)(repo)?);
537 }
530 }
538 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
531 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
539 }
532 }
540 }
533 }
@@ -1,499 +1,490 b''
1 // dirstate_map.rs
1 // dirstate_map.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 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 };
17 };
18
18
19 use crate::{
19 use crate::{
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::item::DirstateItem,
21 dirstate::item::DirstateItem,
22 pybytes_deref::PyBytesDeref,
22 pybytes_deref::PyBytesDeref,
23 };
23 };
24 use hg::{
24 use hg::{
25 dirstate::StateMapIter,
25 dirstate::StateMapIter,
26 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
27 dirstate_tree::on_disk::DirstateV2ParseError,
26 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::owning::OwningDirstateMap,
27 dirstate_tree::owning::OwningDirstateMap,
29 revlog::Node,
28 revlog::Node,
30 utils::files::normalize_case,
29 utils::files::normalize_case,
31 utils::hg_path::{HgPath, HgPathBuf},
30 utils::hg_path::{HgPath, HgPathBuf},
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
31 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 };
32 };
34
33
35 // TODO
34 // TODO
36 // This object needs to share references to multiple members of its Rust
35 // This object needs to share references to multiple members of its Rust
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
37 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
38 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 // every method in `CopyMap` (copymapcopy, etc.).
39 // every method in `CopyMap` (copymapcopy, etc.).
41 // This is ugly and hard to maintain.
40 // This is ugly and hard to maintain.
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 // `py_class!` is already implemented and does not mention
42 // `py_class!` is already implemented and does not mention
44 // `RustDirstateMap`, rightfully so.
43 // `RustDirstateMap`, rightfully so.
45 // All attributes also have to have a separate refcount data attribute for
44 // All attributes also have to have a separate refcount data attribute for
46 // leaks, with all methods that go along for reference sharing.
45 // leaks, with all methods that go along for reference sharing.
47 py_class!(pub class DirstateMap |py| {
46 py_class!(pub class DirstateMap |py| {
48 @shared data inner: OwningDirstateMap;
47 @shared data inner: OwningDirstateMap;
49
48
50 /// Returns a `(dirstate_map, parents)` tuple
49 /// Returns a `(dirstate_map, parents)` tuple
51 @staticmethod
50 @staticmethod
52 def new_v1(
51 def new_v1(
53 on_disk: PyBytes,
52 on_disk: PyBytes,
54 ) -> PyResult<PyObject> {
53 ) -> PyResult<PyObject> {
55 let on_disk = PyBytesDeref::new(py, on_disk);
54 let on_disk = PyBytesDeref::new(py, on_disk);
56 let mut map = OwningDirstateMap::new_empty(on_disk);
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
57 let (on_disk, map_placeholder) = map.get_pair_mut();
58
59 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
60 .map_err(|e| dirstate_error(py, e))?;
56 .map_err(|e| dirstate_error(py, e))?;
61 *map_placeholder = actual_map;
62 let map = Self::create_instance(py, map)?;
57 let map = Self::create_instance(py, map)?;
63 let parents = parents.map(|p| {
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
64 let p1 = PyBytes::new(py, p.p1.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
65 let p2 = PyBytes::new(py, p.p2.as_bytes());
60 let parents = (p1, p2);
66 (p1, p2)
67 });
68 Ok((map, parents).to_py_object(py).into_object())
61 Ok((map, parents).to_py_object(py).into_object())
69 }
62 }
70
63
71 /// Returns a DirstateMap
64 /// Returns a DirstateMap
72 @staticmethod
65 @staticmethod
73 def new_v2(
66 def new_v2(
74 on_disk: PyBytes,
67 on_disk: PyBytes,
75 data_size: usize,
68 data_size: usize,
76 tree_metadata: PyBytes,
69 tree_metadata: PyBytes,
77 ) -> PyResult<PyObject> {
70 ) -> PyResult<PyObject> {
78 let dirstate_error = |e: DirstateError| {
71 let dirstate_error = |e: DirstateError| {
79 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
80 };
73 };
81 let on_disk = PyBytesDeref::new(py, on_disk);
74 let on_disk = PyBytesDeref::new(py, on_disk);
82 let mut map = OwningDirstateMap::new_empty(on_disk);
75 let map = OwningDirstateMap::new_v2(
83 let (on_disk, map_placeholder) = map.get_pair_mut();
84 *map_placeholder = TreeDirstateMap::new_v2(
85 on_disk, data_size, tree_metadata.data(py),
76 on_disk, data_size, tree_metadata.data(py),
86 ).map_err(dirstate_error)?;
77 ).map_err(dirstate_error)?;
87 let map = Self::create_instance(py, map)?;
78 let map = Self::create_instance(py, map)?;
88 Ok(map.into_object())
79 Ok(map.into_object())
89 }
80 }
90
81
91 def clear(&self) -> PyResult<PyObject> {
82 def clear(&self) -> PyResult<PyObject> {
92 self.inner(py).borrow_mut().clear();
83 self.inner(py).borrow_mut().clear();
93 Ok(py.None())
84 Ok(py.None())
94 }
85 }
95
86
96 def get(
87 def get(
97 &self,
88 &self,
98 key: PyObject,
89 key: PyObject,
99 default: Option<PyObject> = None
90 default: Option<PyObject> = None
100 ) -> PyResult<Option<PyObject>> {
91 ) -> PyResult<Option<PyObject>> {
101 let key = key.extract::<PyBytes>(py)?;
92 let key = key.extract::<PyBytes>(py)?;
102 match self
93 match self
103 .inner(py)
94 .inner(py)
104 .borrow()
95 .borrow()
105 .get(HgPath::new(key.data(py)))
96 .get(HgPath::new(key.data(py)))
106 .map_err(|e| v2_error(py, e))?
97 .map_err(|e| v2_error(py, e))?
107 {
98 {
108 Some(entry) => {
99 Some(entry) => {
109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 },
101 },
111 None => Ok(default)
102 None => Ok(default)
112 }
103 }
113 }
104 }
114
105
115 def set_dirstate_item(
106 def set_dirstate_item(
116 &self,
107 &self,
117 path: PyObject,
108 path: PyObject,
118 item: DirstateItem
109 item: DirstateItem
119 ) -> PyResult<PyObject> {
110 ) -> PyResult<PyObject> {
120 let f = path.extract::<PyBytes>(py)?;
111 let f = path.extract::<PyBytes>(py)?;
121 let filename = HgPath::new(f.data(py));
112 let filename = HgPath::new(f.data(py));
122 self.inner(py)
113 self.inner(py)
123 .borrow_mut()
114 .borrow_mut()
124 .set_entry(filename, item.get_entry(py))
115 .set_entry(filename, item.get_entry(py))
125 .map_err(|e| v2_error(py, e))?;
116 .map_err(|e| v2_error(py, e))?;
126 Ok(py.None())
117 Ok(py.None())
127 }
118 }
128
119
129 def addfile(
120 def addfile(
130 &self,
121 &self,
131 f: PyBytes,
122 f: PyBytes,
132 item: DirstateItem,
123 item: DirstateItem,
133 ) -> PyResult<PyNone> {
124 ) -> PyResult<PyNone> {
134 let filename = HgPath::new(f.data(py));
125 let filename = HgPath::new(f.data(py));
135 let entry = item.get_entry(py);
126 let entry = item.get_entry(py);
136 self.inner(py)
127 self.inner(py)
137 .borrow_mut()
128 .borrow_mut()
138 .add_file(filename, entry)
129 .add_file(filename, entry)
139 .map_err(|e |dirstate_error(py, e))?;
130 .map_err(|e |dirstate_error(py, e))?;
140 Ok(PyNone)
131 Ok(PyNone)
141 }
132 }
142
133
143 def removefile(
134 def removefile(
144 &self,
135 &self,
145 f: PyObject,
136 f: PyObject,
146 in_merge: PyObject
137 in_merge: PyObject
147 ) -> PyResult<PyObject> {
138 ) -> PyResult<PyObject> {
148 self.inner(py).borrow_mut()
139 self.inner(py).borrow_mut()
149 .remove_file(
140 .remove_file(
150 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
141 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 in_merge.extract::<PyBool>(py)?.is_true(),
142 in_merge.extract::<PyBool>(py)?.is_true(),
152 )
143 )
153 .or_else(|_| {
144 .or_else(|_| {
154 Err(PyErr::new::<exc::OSError, _>(
145 Err(PyErr::new::<exc::OSError, _>(
155 py,
146 py,
156 "Dirstate error".to_string(),
147 "Dirstate error".to_string(),
157 ))
148 ))
158 })?;
149 })?;
159 Ok(py.None())
150 Ok(py.None())
160 }
151 }
161
152
162 def drop_item_and_copy_source(
153 def drop_item_and_copy_source(
163 &self,
154 &self,
164 f: PyBytes,
155 f: PyBytes,
165 ) -> PyResult<PyNone> {
156 ) -> PyResult<PyNone> {
166 self.inner(py)
157 self.inner(py)
167 .borrow_mut()
158 .borrow_mut()
168 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
159 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 .map_err(|e |dirstate_error(py, e))?;
160 .map_err(|e |dirstate_error(py, e))?;
170 Ok(PyNone)
161 Ok(PyNone)
171 }
162 }
172
163
173 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
164 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 let d = d.extract::<PyBytes>(py)?;
165 let d = d.extract::<PyBytes>(py)?;
175 Ok(self.inner(py).borrow_mut()
166 Ok(self.inner(py).borrow_mut()
176 .has_tracked_dir(HgPath::new(d.data(py)))
167 .has_tracked_dir(HgPath::new(d.data(py)))
177 .map_err(|e| {
168 .map_err(|e| {
178 PyErr::new::<exc::ValueError, _>(py, e.to_string())
169 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 })?
170 })?
180 .to_py_object(py))
171 .to_py_object(py))
181 }
172 }
182
173
183 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
174 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 let d = d.extract::<PyBytes>(py)?;
175 let d = d.extract::<PyBytes>(py)?;
185 Ok(self.inner(py).borrow_mut()
176 Ok(self.inner(py).borrow_mut()
186 .has_dir(HgPath::new(d.data(py)))
177 .has_dir(HgPath::new(d.data(py)))
187 .map_err(|e| {
178 .map_err(|e| {
188 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 })?
180 })?
190 .to_py_object(py))
181 .to_py_object(py))
191 }
182 }
192
183
193 def write_v1(
184 def write_v1(
194 &self,
185 &self,
195 p1: PyObject,
186 p1: PyObject,
196 p2: PyObject,
187 p2: PyObject,
197 ) -> PyResult<PyBytes> {
188 ) -> PyResult<PyBytes> {
198 let inner = self.inner(py).borrow();
189 let inner = self.inner(py).borrow();
199 let parents = DirstateParents {
190 let parents = DirstateParents {
200 p1: extract_node_id(py, &p1)?,
191 p1: extract_node_id(py, &p1)?,
201 p2: extract_node_id(py, &p2)?,
192 p2: extract_node_id(py, &p2)?,
202 };
193 };
203 let result = inner.pack_v1(parents);
194 let result = inner.pack_v1(parents);
204 match result {
195 match result {
205 Ok(packed) => Ok(PyBytes::new(py, &packed)),
196 Ok(packed) => Ok(PyBytes::new(py, &packed)),
206 Err(_) => Err(PyErr::new::<exc::OSError, _>(
197 Err(_) => Err(PyErr::new::<exc::OSError, _>(
207 py,
198 py,
208 "Dirstate error".to_string(),
199 "Dirstate error".to_string(),
209 )),
200 )),
210 }
201 }
211 }
202 }
212
203
213 /// Returns new data together with whether that data should be appended to
204 /// Returns new data together with whether that data should be appended to
214 /// the existing data file whose content is at `self.on_disk` (True),
205 /// the existing data file whose content is at `self.on_disk` (True),
215 /// instead of written to a new data file (False).
206 /// instead of written to a new data file (False).
216 def write_v2(
207 def write_v2(
217 &self,
208 &self,
218 can_append: bool,
209 can_append: bool,
219 ) -> PyResult<PyObject> {
210 ) -> PyResult<PyObject> {
220 let inner = self.inner(py).borrow();
211 let inner = self.inner(py).borrow();
221 let result = inner.pack_v2(can_append);
212 let result = inner.pack_v2(can_append);
222 match result {
213 match result {
223 Ok((packed, tree_metadata, append)) => {
214 Ok((packed, tree_metadata, append)) => {
224 let packed = PyBytes::new(py, &packed);
215 let packed = PyBytes::new(py, &packed);
225 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
216 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
226 let tuple = (packed, tree_metadata, append);
217 let tuple = (packed, tree_metadata, append);
227 Ok(tuple.to_py_object(py).into_object())
218 Ok(tuple.to_py_object(py).into_object())
228 },
219 },
229 Err(_) => Err(PyErr::new::<exc::OSError, _>(
220 Err(_) => Err(PyErr::new::<exc::OSError, _>(
230 py,
221 py,
231 "Dirstate error".to_string(),
222 "Dirstate error".to_string(),
232 )),
223 )),
233 }
224 }
234 }
225 }
235
226
236 def filefoldmapasdict(&self) -> PyResult<PyDict> {
227 def filefoldmapasdict(&self) -> PyResult<PyDict> {
237 let dict = PyDict::new(py);
228 let dict = PyDict::new(py);
238 for item in self.inner(py).borrow_mut().iter() {
229 for item in self.inner(py).borrow_mut().iter() {
239 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
230 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
240 if entry.state() != EntryState::Removed {
231 if entry.state() != EntryState::Removed {
241 let key = normalize_case(path);
232 let key = normalize_case(path);
242 let value = path;
233 let value = path;
243 dict.set_item(
234 dict.set_item(
244 py,
235 py,
245 PyBytes::new(py, key.as_bytes()).into_object(),
236 PyBytes::new(py, key.as_bytes()).into_object(),
246 PyBytes::new(py, value.as_bytes()).into_object(),
237 PyBytes::new(py, value.as_bytes()).into_object(),
247 )?;
238 )?;
248 }
239 }
249 }
240 }
250 Ok(dict)
241 Ok(dict)
251 }
242 }
252
243
253 def __len__(&self) -> PyResult<usize> {
244 def __len__(&self) -> PyResult<usize> {
254 Ok(self.inner(py).borrow().len())
245 Ok(self.inner(py).borrow().len())
255 }
246 }
256
247
257 def __contains__(&self, key: PyObject) -> PyResult<bool> {
248 def __contains__(&self, key: PyObject) -> PyResult<bool> {
258 let key = key.extract::<PyBytes>(py)?;
249 let key = key.extract::<PyBytes>(py)?;
259 self.inner(py)
250 self.inner(py)
260 .borrow()
251 .borrow()
261 .contains_key(HgPath::new(key.data(py)))
252 .contains_key(HgPath::new(key.data(py)))
262 .map_err(|e| v2_error(py, e))
253 .map_err(|e| v2_error(py, e))
263 }
254 }
264
255
265 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
256 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
266 let key = key.extract::<PyBytes>(py)?;
257 let key = key.extract::<PyBytes>(py)?;
267 let key = HgPath::new(key.data(py));
258 let key = HgPath::new(key.data(py));
268 match self
259 match self
269 .inner(py)
260 .inner(py)
270 .borrow()
261 .borrow()
271 .get(key)
262 .get(key)
272 .map_err(|e| v2_error(py, e))?
263 .map_err(|e| v2_error(py, e))?
273 {
264 {
274 Some(entry) => {
265 Some(entry) => {
275 Ok(DirstateItem::new_as_pyobject(py, entry)?)
266 Ok(DirstateItem::new_as_pyobject(py, entry)?)
276 },
267 },
277 None => Err(PyErr::new::<exc::KeyError, _>(
268 None => Err(PyErr::new::<exc::KeyError, _>(
278 py,
269 py,
279 String::from_utf8_lossy(key.as_bytes()),
270 String::from_utf8_lossy(key.as_bytes()),
280 )),
271 )),
281 }
272 }
282 }
273 }
283
274
284 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
275 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
285 let leaked_ref = self.inner(py).leak_immutable();
276 let leaked_ref = self.inner(py).leak_immutable();
286 DirstateMapKeysIterator::from_inner(
277 DirstateMapKeysIterator::from_inner(
287 py,
278 py,
288 unsafe { leaked_ref.map(py, |o| o.iter()) },
279 unsafe { leaked_ref.map(py, |o| o.iter()) },
289 )
280 )
290 }
281 }
291
282
292 def items(&self) -> PyResult<DirstateMapItemsIterator> {
283 def items(&self) -> PyResult<DirstateMapItemsIterator> {
293 let leaked_ref = self.inner(py).leak_immutable();
284 let leaked_ref = self.inner(py).leak_immutable();
294 DirstateMapItemsIterator::from_inner(
285 DirstateMapItemsIterator::from_inner(
295 py,
286 py,
296 unsafe { leaked_ref.map(py, |o| o.iter()) },
287 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 )
288 )
298 }
289 }
299
290
300 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
291 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
301 let leaked_ref = self.inner(py).leak_immutable();
292 let leaked_ref = self.inner(py).leak_immutable();
302 DirstateMapKeysIterator::from_inner(
293 DirstateMapKeysIterator::from_inner(
303 py,
294 py,
304 unsafe { leaked_ref.map(py, |o| o.iter()) },
295 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 )
296 )
306 }
297 }
307
298
308 // TODO all copymap* methods, see docstring above
299 // TODO all copymap* methods, see docstring above
309 def copymapcopy(&self) -> PyResult<PyDict> {
300 def copymapcopy(&self) -> PyResult<PyDict> {
310 let dict = PyDict::new(py);
301 let dict = PyDict::new(py);
311 for item in self.inner(py).borrow().copy_map_iter() {
302 for item in self.inner(py).borrow().copy_map_iter() {
312 let (key, value) = item.map_err(|e| v2_error(py, e))?;
303 let (key, value) = item.map_err(|e| v2_error(py, e))?;
313 dict.set_item(
304 dict.set_item(
314 py,
305 py,
315 PyBytes::new(py, key.as_bytes()),
306 PyBytes::new(py, key.as_bytes()),
316 PyBytes::new(py, value.as_bytes()),
307 PyBytes::new(py, value.as_bytes()),
317 )?;
308 )?;
318 }
309 }
319 Ok(dict)
310 Ok(dict)
320 }
311 }
321
312
322 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
313 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
323 let key = key.extract::<PyBytes>(py)?;
314 let key = key.extract::<PyBytes>(py)?;
324 match self
315 match self
325 .inner(py)
316 .inner(py)
326 .borrow()
317 .borrow()
327 .copy_map_get(HgPath::new(key.data(py)))
318 .copy_map_get(HgPath::new(key.data(py)))
328 .map_err(|e| v2_error(py, e))?
319 .map_err(|e| v2_error(py, e))?
329 {
320 {
330 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
321 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
331 None => Err(PyErr::new::<exc::KeyError, _>(
322 None => Err(PyErr::new::<exc::KeyError, _>(
332 py,
323 py,
333 String::from_utf8_lossy(key.data(py)),
324 String::from_utf8_lossy(key.data(py)),
334 )),
325 )),
335 }
326 }
336 }
327 }
337 def copymap(&self) -> PyResult<CopyMap> {
328 def copymap(&self) -> PyResult<CopyMap> {
338 CopyMap::from_inner(py, self.clone_ref(py))
329 CopyMap::from_inner(py, self.clone_ref(py))
339 }
330 }
340
331
341 def copymaplen(&self) -> PyResult<usize> {
332 def copymaplen(&self) -> PyResult<usize> {
342 Ok(self.inner(py).borrow().copy_map_len())
333 Ok(self.inner(py).borrow().copy_map_len())
343 }
334 }
344 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
335 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
345 let key = key.extract::<PyBytes>(py)?;
336 let key = key.extract::<PyBytes>(py)?;
346 self.inner(py)
337 self.inner(py)
347 .borrow()
338 .borrow()
348 .copy_map_contains_key(HgPath::new(key.data(py)))
339 .copy_map_contains_key(HgPath::new(key.data(py)))
349 .map_err(|e| v2_error(py, e))
340 .map_err(|e| v2_error(py, e))
350 }
341 }
351 def copymapget(
342 def copymapget(
352 &self,
343 &self,
353 key: PyObject,
344 key: PyObject,
354 default: Option<PyObject>
345 default: Option<PyObject>
355 ) -> PyResult<Option<PyObject>> {
346 ) -> PyResult<Option<PyObject>> {
356 let key = key.extract::<PyBytes>(py)?;
347 let key = key.extract::<PyBytes>(py)?;
357 match self
348 match self
358 .inner(py)
349 .inner(py)
359 .borrow()
350 .borrow()
360 .copy_map_get(HgPath::new(key.data(py)))
351 .copy_map_get(HgPath::new(key.data(py)))
361 .map_err(|e| v2_error(py, e))?
352 .map_err(|e| v2_error(py, e))?
362 {
353 {
363 Some(copy) => Ok(Some(
354 Some(copy) => Ok(Some(
364 PyBytes::new(py, copy.as_bytes()).into_object(),
355 PyBytes::new(py, copy.as_bytes()).into_object(),
365 )),
356 )),
366 None => Ok(default),
357 None => Ok(default),
367 }
358 }
368 }
359 }
369 def copymapsetitem(
360 def copymapsetitem(
370 &self,
361 &self,
371 key: PyObject,
362 key: PyObject,
372 value: PyObject
363 value: PyObject
373 ) -> PyResult<PyObject> {
364 ) -> PyResult<PyObject> {
374 let key = key.extract::<PyBytes>(py)?;
365 let key = key.extract::<PyBytes>(py)?;
375 let value = value.extract::<PyBytes>(py)?;
366 let value = value.extract::<PyBytes>(py)?;
376 self.inner(py)
367 self.inner(py)
377 .borrow_mut()
368 .borrow_mut()
378 .copy_map_insert(
369 .copy_map_insert(
379 HgPathBuf::from_bytes(key.data(py)),
370 HgPathBuf::from_bytes(key.data(py)),
380 HgPathBuf::from_bytes(value.data(py)),
371 HgPathBuf::from_bytes(value.data(py)),
381 )
372 )
382 .map_err(|e| v2_error(py, e))?;
373 .map_err(|e| v2_error(py, e))?;
383 Ok(py.None())
374 Ok(py.None())
384 }
375 }
385 def copymappop(
376 def copymappop(
386 &self,
377 &self,
387 key: PyObject,
378 key: PyObject,
388 default: Option<PyObject>
379 default: Option<PyObject>
389 ) -> PyResult<Option<PyObject>> {
380 ) -> PyResult<Option<PyObject>> {
390 let key = key.extract::<PyBytes>(py)?;
381 let key = key.extract::<PyBytes>(py)?;
391 match self
382 match self
392 .inner(py)
383 .inner(py)
393 .borrow_mut()
384 .borrow_mut()
394 .copy_map_remove(HgPath::new(key.data(py)))
385 .copy_map_remove(HgPath::new(key.data(py)))
395 .map_err(|e| v2_error(py, e))?
386 .map_err(|e| v2_error(py, e))?
396 {
387 {
397 Some(copy) => Ok(Some(
388 Some(copy) => Ok(Some(
398 PyBytes::new(py, copy.as_bytes()).into_object(),
389 PyBytes::new(py, copy.as_bytes()).into_object(),
399 )),
390 )),
400 None => Ok(default),
391 None => Ok(default),
401 }
392 }
402 }
393 }
403
394
404 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
395 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
405 let leaked_ref = self.inner(py).leak_immutable();
396 let leaked_ref = self.inner(py).leak_immutable();
406 CopyMapKeysIterator::from_inner(
397 CopyMapKeysIterator::from_inner(
407 py,
398 py,
408 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
399 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
409 )
400 )
410 }
401 }
411
402
412 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
403 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
413 let leaked_ref = self.inner(py).leak_immutable();
404 let leaked_ref = self.inner(py).leak_immutable();
414 CopyMapItemsIterator::from_inner(
405 CopyMapItemsIterator::from_inner(
415 py,
406 py,
416 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
407 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
417 )
408 )
418 }
409 }
419
410
420 def tracked_dirs(&self) -> PyResult<PyList> {
411 def tracked_dirs(&self) -> PyResult<PyList> {
421 let dirs = PyList::new(py, &[]);
412 let dirs = PyList::new(py, &[]);
422 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
413 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
423 .map_err(|e |dirstate_error(py, e))?
414 .map_err(|e |dirstate_error(py, e))?
424 {
415 {
425 let path = path.map_err(|e| v2_error(py, e))?;
416 let path = path.map_err(|e| v2_error(py, e))?;
426 let path = PyBytes::new(py, path.as_bytes());
417 let path = PyBytes::new(py, path.as_bytes());
427 dirs.append(py, path.into_object())
418 dirs.append(py, path.into_object())
428 }
419 }
429 Ok(dirs)
420 Ok(dirs)
430 }
421 }
431
422
432 def debug_iter(&self, all: bool) -> PyResult<PyList> {
423 def debug_iter(&self, all: bool) -> PyResult<PyList> {
433 let dirs = PyList::new(py, &[]);
424 let dirs = PyList::new(py, &[]);
434 for item in self.inner(py).borrow().debug_iter(all) {
425 for item in self.inner(py).borrow().debug_iter(all) {
435 let (path, (state, mode, size, mtime)) =
426 let (path, (state, mode, size, mtime)) =
436 item.map_err(|e| v2_error(py, e))?;
427 item.map_err(|e| v2_error(py, e))?;
437 let path = PyBytes::new(py, path.as_bytes());
428 let path = PyBytes::new(py, path.as_bytes());
438 let item = (path, state, mode, size, mtime);
429 let item = (path, state, mode, size, mtime);
439 dirs.append(py, item.to_py_object(py).into_object())
430 dirs.append(py, item.to_py_object(py).into_object())
440 }
431 }
441 Ok(dirs)
432 Ok(dirs)
442 }
433 }
443 });
434 });
444
435
445 impl DirstateMap {
436 impl DirstateMap {
446 pub fn get_inner_mut<'a>(
437 pub fn get_inner_mut<'a>(
447 &'a self,
438 &'a self,
448 py: Python<'a>,
439 py: Python<'a>,
449 ) -> RefMut<'a, OwningDirstateMap> {
440 ) -> RefMut<'a, OwningDirstateMap> {
450 self.inner(py).borrow_mut()
441 self.inner(py).borrow_mut()
451 }
442 }
452 fn translate_key(
443 fn translate_key(
453 py: Python,
444 py: Python,
454 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
445 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
455 ) -> PyResult<Option<PyBytes>> {
446 ) -> PyResult<Option<PyBytes>> {
456 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
447 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
457 Ok(Some(PyBytes::new(py, f.as_bytes())))
448 Ok(Some(PyBytes::new(py, f.as_bytes())))
458 }
449 }
459 fn translate_key_value(
450 fn translate_key_value(
460 py: Python,
451 py: Python,
461 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
452 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
462 ) -> PyResult<Option<(PyBytes, PyObject)>> {
453 ) -> PyResult<Option<(PyBytes, PyObject)>> {
463 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
454 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
464 Ok(Some((
455 Ok(Some((
465 PyBytes::new(py, f.as_bytes()),
456 PyBytes::new(py, f.as_bytes()),
466 DirstateItem::new_as_pyobject(py, entry)?,
457 DirstateItem::new_as_pyobject(py, entry)?,
467 )))
458 )))
468 }
459 }
469 }
460 }
470
461
471 py_shared_iterator!(
462 py_shared_iterator!(
472 DirstateMapKeysIterator,
463 DirstateMapKeysIterator,
473 UnsafePyLeaked<StateMapIter<'static>>,
464 UnsafePyLeaked<StateMapIter<'static>>,
474 DirstateMap::translate_key,
465 DirstateMap::translate_key,
475 Option<PyBytes>
466 Option<PyBytes>
476 );
467 );
477
468
478 py_shared_iterator!(
469 py_shared_iterator!(
479 DirstateMapItemsIterator,
470 DirstateMapItemsIterator,
480 UnsafePyLeaked<StateMapIter<'static>>,
471 UnsafePyLeaked<StateMapIter<'static>>,
481 DirstateMap::translate_key_value,
472 DirstateMap::translate_key_value,
482 Option<(PyBytes, PyObject)>
473 Option<(PyBytes, PyObject)>
483 );
474 );
484
475
485 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
476 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
486 let bytes = obj.extract::<PyBytes>(py)?;
477 let bytes = obj.extract::<PyBytes>(py)?;
487 match bytes.data(py).try_into() {
478 match bytes.data(py).try_into() {
488 Ok(s) => Ok(s),
479 Ok(s) => Ok(s),
489 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
480 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
490 }
481 }
491 }
482 }
492
483
493 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
484 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
494 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
485 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
495 }
486 }
496
487
497 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
488 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
498 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
489 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
499 }
490 }
@@ -1,303 +1,302 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::status` module provided by the
8 //! Bindings for the `hg::status` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as
9 //! `hg-core` crate. From Python, this will be seen as
10 //! `rustext.dirstate.status`.
10 //! `rustext.dirstate.status`.
11
11
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 use cpython::{
13 use cpython::{
14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 };
16 };
17 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::status::StatusPath;
18 use hg::{
18 use hg::{
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
20 parse_pattern_syntax,
20 parse_pattern_syntax,
21 utils::{
21 utils::{
22 files::{get_bytes_from_path, get_path_from_bytes},
22 files::{get_bytes_from_path, get_path_from_bytes},
23 hg_path::{HgPath, HgPathBuf},
23 hg_path::{HgPath, HgPathBuf},
24 },
24 },
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
26 StatusOptions,
26 StatusOptions,
27 };
27 };
28 use std::borrow::Borrow;
28 use std::borrow::Borrow;
29
29
30 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
30 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
31 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
31 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
32 }
32 }
33
33
34 /// This will be useless once trait impls for collection are added to `PyBytes`
34 /// This will be useless once trait impls for collection are added to `PyBytes`
35 /// upstream.
35 /// upstream.
36 fn collect_pybytes_list(
36 fn collect_pybytes_list(
37 py: Python,
37 py: Python,
38 iter: impl Iterator<Item = impl AsRef<HgPath>>,
38 iter: impl Iterator<Item = impl AsRef<HgPath>>,
39 ) -> PyList {
39 ) -> PyList {
40 let list = PyList::new(py, &[]);
40 let list = PyList::new(py, &[]);
41
41
42 for path in iter {
42 for path in iter {
43 list.append(
43 list.append(
44 py,
44 py,
45 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
45 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
46 )
46 )
47 }
47 }
48
48
49 list
49 list
50 }
50 }
51
51
52 fn collect_bad_matches(
52 fn collect_bad_matches(
53 py: Python,
53 py: Python,
54 collection: &[(impl AsRef<HgPath>, BadMatch)],
54 collection: &[(impl AsRef<HgPath>, BadMatch)],
55 ) -> PyResult<PyList> {
55 ) -> PyResult<PyList> {
56 let list = PyList::new(py, &[]);
56 let list = PyList::new(py, &[]);
57
57
58 let os = py.import("os")?;
58 let os = py.import("os")?;
59 let get_error_message = |code: i32| -> PyResult<_> {
59 let get_error_message = |code: i32| -> PyResult<_> {
60 os.call(
60 os.call(
61 py,
61 py,
62 "strerror",
62 "strerror",
63 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
63 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
64 None,
64 None,
65 )
65 )
66 };
66 };
67
67
68 for (path, bad_match) in collection.iter() {
68 for (path, bad_match) in collection.iter() {
69 let message = match bad_match {
69 let message = match bad_match {
70 BadMatch::OsError(code) => get_error_message(*code)?,
70 BadMatch::OsError(code) => get_error_message(*code)?,
71 BadMatch::BadType(bad_type) => format!(
71 BadMatch::BadType(bad_type) => format!(
72 "unsupported file type (type is {})",
72 "unsupported file type (type is {})",
73 bad_type.to_string()
73 bad_type.to_string()
74 )
74 )
75 .to_py_object(py)
75 .to_py_object(py)
76 .into_object(),
76 .into_object(),
77 };
77 };
78 list.append(
78 list.append(
79 py,
79 py,
80 (PyBytes::new(py, path.as_ref().as_bytes()), message)
80 (PyBytes::new(py, path.as_ref().as_bytes()), message)
81 .to_py_object(py)
81 .to_py_object(py)
82 .into_object(),
82 .into_object(),
83 )
83 )
84 }
84 }
85
85
86 Ok(list)
86 Ok(list)
87 }
87 }
88
88
89 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
89 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
90 match err {
90 match err {
91 StatusError::Pattern(e) => {
91 StatusError::Pattern(e) => {
92 let as_string = e.to_string();
92 let as_string = e.to_string();
93 log::trace!("Rust status fallback: `{}`", &as_string);
93 log::trace!("Rust status fallback: `{}`", &as_string);
94
94
95 PyErr::new::<FallbackError, _>(py, &as_string)
95 PyErr::new::<FallbackError, _>(py, &as_string)
96 }
96 }
97 e => PyErr::new::<ValueError, _>(py, e.to_string()),
97 e => PyErr::new::<ValueError, _>(py, e.to_string()),
98 }
98 }
99 }
99 }
100
100
101 pub fn status_wrapper(
101 pub fn status_wrapper(
102 py: Python,
102 py: Python,
103 dmap: DirstateMap,
103 dmap: DirstateMap,
104 matcher: PyObject,
104 matcher: PyObject,
105 root_dir: PyObject,
105 root_dir: PyObject,
106 ignore_files: PyList,
106 ignore_files: PyList,
107 check_exec: bool,
107 check_exec: bool,
108 list_clean: bool,
108 list_clean: bool,
109 list_ignored: bool,
109 list_ignored: bool,
110 list_unknown: bool,
110 list_unknown: bool,
111 collect_traversed_dirs: bool,
111 collect_traversed_dirs: bool,
112 ) -> PyResult<PyTuple> {
112 ) -> PyResult<PyTuple> {
113 let bytes = root_dir.extract::<PyBytes>(py)?;
113 let bytes = root_dir.extract::<PyBytes>(py)?;
114 let root_dir = get_path_from_bytes(bytes.data(py));
114 let root_dir = get_path_from_bytes(bytes.data(py));
115
115
116 let dmap: DirstateMap = dmap.to_py_object(py);
116 let dmap: DirstateMap = dmap.to_py_object(py);
117 let mut dmap = dmap.get_inner_mut(py);
117 let mut dmap = dmap.get_inner_mut(py);
118
118
119 let ignore_files: PyResult<Vec<_>> = ignore_files
119 let ignore_files: PyResult<Vec<_>> = ignore_files
120 .iter(py)
120 .iter(py)
121 .map(|b| {
121 .map(|b| {
122 let file = b.extract::<PyBytes>(py)?;
122 let file = b.extract::<PyBytes>(py)?;
123 Ok(get_path_from_bytes(file.data(py)).to_owned())
123 Ok(get_path_from_bytes(file.data(py)).to_owned())
124 })
124 })
125 .collect();
125 .collect();
126 let ignore_files = ignore_files?;
126 let ignore_files = ignore_files?;
127 // The caller may call `copymap.items()` separately
127 // The caller may call `copymap.items()` separately
128 let list_copies = false;
128 let list_copies = false;
129
129
130 let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| {
131 let (status_res, warnings) =
132 res.map_err(|e| handle_fallback(py, e))?;
133 build_response(py, status_res, warnings)
134 };
135
130 match matcher.get_type(py).name(py).borrow() {
136 match matcher.get_type(py).name(py).borrow() {
131 "alwaysmatcher" => {
137 "alwaysmatcher" => {
132 let matcher = AlwaysMatcher;
138 let matcher = AlwaysMatcher;
133 let (status_res, warnings) = dmap
139 dmap.with_status(
134 .status(
140 &matcher,
135 &matcher,
141 root_dir.to_path_buf(),
136 root_dir.to_path_buf(),
142 ignore_files,
137 ignore_files,
143 StatusOptions {
138 StatusOptions {
144 check_exec,
139 check_exec,
145 list_clean,
140 list_clean,
146 list_ignored,
141 list_ignored,
147 list_unknown,
142 list_unknown,
148 list_copies,
143 list_copies,
149 collect_traversed_dirs,
144 collect_traversed_dirs,
150 },
145 },
151 after_status,
146 )
152 )
147 .map_err(|e| handle_fallback(py, e))?;
148 build_response(py, status_res, warnings)
149 }
153 }
150 "exactmatcher" => {
154 "exactmatcher" => {
151 let files = matcher.call_method(
155 let files = matcher.call_method(
152 py,
156 py,
153 "files",
157 "files",
154 PyTuple::new(py, &[]),
158 PyTuple::new(py, &[]),
155 None,
159 None,
156 )?;
160 )?;
157 let files: PyList = files.cast_into(py)?;
161 let files: PyList = files.cast_into(py)?;
158 let files: PyResult<Vec<HgPathBuf>> = files
162 let files: PyResult<Vec<HgPathBuf>> = files
159 .iter(py)
163 .iter(py)
160 .map(|f| {
164 .map(|f| {
161 Ok(HgPathBuf::from_bytes(
165 Ok(HgPathBuf::from_bytes(
162 f.extract::<PyBytes>(py)?.data(py),
166 f.extract::<PyBytes>(py)?.data(py),
163 ))
167 ))
164 })
168 })
165 .collect();
169 .collect();
166
170
167 let files = files?;
171 let files = files?;
168 let matcher = FileMatcher::new(files.as_ref())
172 let matcher = FileMatcher::new(files.as_ref())
169 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
173 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
170 let (status_res, warnings) = dmap
174 dmap.with_status(
171 .status(
175 &matcher,
172 &matcher,
176 root_dir.to_path_buf(),
173 root_dir.to_path_buf(),
177 ignore_files,
174 ignore_files,
178 StatusOptions {
175 StatusOptions {
179 check_exec,
176 check_exec,
180 list_clean,
177 list_clean,
181 list_ignored,
178 list_ignored,
182 list_unknown,
179 list_unknown,
183 list_copies,
180 list_copies,
184 collect_traversed_dirs,
181 collect_traversed_dirs,
185 },
182 },
186 after_status,
183 )
187 )
184 .map_err(|e| handle_fallback(py, e))?;
185 build_response(py, status_res, warnings)
186 }
188 }
187 "includematcher" => {
189 "includematcher" => {
188 // Get the patterns from Python even though most of them are
190 // Get the patterns from Python even though most of them are
189 // redundant with those we will parse later on, as they include
191 // redundant with those we will parse later on, as they include
190 // those passed from the command line.
192 // those passed from the command line.
191 let ignore_patterns: PyResult<Vec<_>> = matcher
193 let ignore_patterns: PyResult<Vec<_>> = matcher
192 .getattr(py, "_kindpats")?
194 .getattr(py, "_kindpats")?
193 .iter(py)?
195 .iter(py)?
194 .map(|k| {
196 .map(|k| {
195 let k = k?;
197 let k = k?;
196 let syntax = parse_pattern_syntax(
198 let syntax = parse_pattern_syntax(
197 &[
199 &[
198 k.get_item(py, 0)?
200 k.get_item(py, 0)?
199 .extract::<PyBytes>(py)?
201 .extract::<PyBytes>(py)?
200 .data(py),
202 .data(py),
201 &b":"[..],
203 &b":"[..],
202 ]
204 ]
203 .concat(),
205 .concat(),
204 )
206 )
205 .map_err(|e| {
207 .map_err(|e| {
206 handle_fallback(py, StatusError::Pattern(e))
208 handle_fallback(py, StatusError::Pattern(e))
207 })?;
209 })?;
208 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
210 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
209 let pattern = pattern.data(py);
211 let pattern = pattern.data(py);
210 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
212 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
211 let source = get_path_from_bytes(source.data(py));
213 let source = get_path_from_bytes(source.data(py));
212 let new = IgnorePattern::new(syntax, pattern, source);
214 let new = IgnorePattern::new(syntax, pattern, source);
213 Ok(new)
215 Ok(new)
214 })
216 })
215 .collect();
217 .collect();
216
218
217 let ignore_patterns = ignore_patterns?;
219 let ignore_patterns = ignore_patterns?;
218
220
219 let matcher = IncludeMatcher::new(ignore_patterns)
221 let matcher = IncludeMatcher::new(ignore_patterns)
220 .map_err(|e| handle_fallback(py, e.into()))?;
222 .map_err(|e| handle_fallback(py, e.into()))?;
221
223
222 let (status_res, warnings) = dmap
224 dmap.with_status(
223 .status(
225 &matcher,
224 &matcher,
226 root_dir.to_path_buf(),
225 root_dir.to_path_buf(),
227 ignore_files,
226 ignore_files,
228 StatusOptions {
227 StatusOptions {
229 check_exec,
228 check_exec,
230 list_clean,
229 list_clean,
231 list_ignored,
230 list_ignored,
232 list_unknown,
231 list_unknown,
233 list_copies,
232 list_copies,
234 collect_traversed_dirs,
233 collect_traversed_dirs,
235 },
234 },
236 after_status,
235 )
237 )
236 .map_err(|e| handle_fallback(py, e))?;
237
238 build_response(py, status_res, warnings)
239 }
238 }
240 e => Err(PyErr::new::<ValueError, _>(
239 e => Err(PyErr::new::<ValueError, _>(
241 py,
240 py,
242 format!("Unsupported matcher {}", e),
241 format!("Unsupported matcher {}", e),
243 )),
242 )),
244 }
243 }
245 }
244 }
246
245
247 fn build_response(
246 fn build_response(
248 py: Python,
247 py: Python,
249 status_res: DirstateStatus,
248 status_res: DirstateStatus,
250 warnings: Vec<PatternFileWarning>,
249 warnings: Vec<PatternFileWarning>,
251 ) -> PyResult<PyTuple> {
250 ) -> PyResult<PyTuple> {
252 let modified = collect_status_path_list(py, &status_res.modified);
251 let modified = collect_status_path_list(py, &status_res.modified);
253 let added = collect_status_path_list(py, &status_res.added);
252 let added = collect_status_path_list(py, &status_res.added);
254 let removed = collect_status_path_list(py, &status_res.removed);
253 let removed = collect_status_path_list(py, &status_res.removed);
255 let deleted = collect_status_path_list(py, &status_res.deleted);
254 let deleted = collect_status_path_list(py, &status_res.deleted);
256 let clean = collect_status_path_list(py, &status_res.clean);
255 let clean = collect_status_path_list(py, &status_res.clean);
257 let ignored = collect_status_path_list(py, &status_res.ignored);
256 let ignored = collect_status_path_list(py, &status_res.ignored);
258 let unknown = collect_status_path_list(py, &status_res.unknown);
257 let unknown = collect_status_path_list(py, &status_res.unknown);
259 let unsure = collect_status_path_list(py, &status_res.unsure);
258 let unsure = collect_status_path_list(py, &status_res.unsure);
260 let bad = collect_bad_matches(py, &status_res.bad)?;
259 let bad = collect_bad_matches(py, &status_res.bad)?;
261 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
260 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
262 let dirty = status_res.dirty.to_py_object(py);
261 let dirty = status_res.dirty.to_py_object(py);
263 let py_warnings = PyList::new(py, &[]);
262 let py_warnings = PyList::new(py, &[]);
264 for warning in warnings.iter() {
263 for warning in warnings.iter() {
265 // We use duck-typing on the Python side for dispatch, good enough for
264 // We use duck-typing on the Python side for dispatch, good enough for
266 // now.
265 // now.
267 match warning {
266 match warning {
268 PatternFileWarning::InvalidSyntax(file, syn) => {
267 PatternFileWarning::InvalidSyntax(file, syn) => {
269 py_warnings.append(
268 py_warnings.append(
270 py,
269 py,
271 (
270 (
272 PyBytes::new(py, &get_bytes_from_path(&file)),
271 PyBytes::new(py, &get_bytes_from_path(&file)),
273 PyBytes::new(py, syn),
272 PyBytes::new(py, syn),
274 )
273 )
275 .to_py_object(py)
274 .to_py_object(py)
276 .into_object(),
275 .into_object(),
277 );
276 );
278 }
277 }
279 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
278 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
280 py,
279 py,
281 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
280 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
282 ),
281 ),
283 }
282 }
284 }
283 }
285
284
286 Ok(PyTuple::new(
285 Ok(PyTuple::new(
287 py,
286 py,
288 &[
287 &[
289 unsure.into_object(),
288 unsure.into_object(),
290 modified.into_object(),
289 modified.into_object(),
291 added.into_object(),
290 added.into_object(),
292 removed.into_object(),
291 removed.into_object(),
293 deleted.into_object(),
292 deleted.into_object(),
294 clean.into_object(),
293 clean.into_object(),
295 ignored.into_object(),
294 ignored.into_object(),
296 unknown.into_object(),
295 unknown.into_object(),
297 py_warnings.into_object(),
296 py_warnings.into_object(),
298 bad.into_object(),
297 bad.into_object(),
299 traversed.into_object(),
298 traversed.into_object(),
300 dirty.into_object(),
299 dirty.into_object(),
301 ][..],
300 ][..],
302 ))
301 ))
303 }
302 }
@@ -1,531 +1,549 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::dirstate::RANGE_MASK_31BIT;
18 use hg::dirstate::RANGE_MASK_31BIT;
19 use hg::errors::{HgError, IoResultExt};
19 use hg::errors::{HgError, IoResultExt};
20 use hg::lock::LockError;
20 use hg::lock::LockError;
21 use hg::manifest::Manifest;
21 use hg::manifest::Manifest;
22 use hg::matchers::AlwaysMatcher;
22 use hg::matchers::AlwaysMatcher;
23 use hg::repo::Repo;
23 use hg::repo::Repo;
24 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_os_string;
25 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_bytes_from_path;
26 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::files::get_path_from_bytes;
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 use hg::DirstateStatus;
29 use hg::PatternFileWarning;
30 use hg::StatusError;
28 use hg::StatusOptions;
31 use hg::StatusOptions;
29 use log::info;
32 use log::info;
30 use std::io;
33 use std::io;
31 use std::path::PathBuf;
34 use std::path::PathBuf;
32
35
33 pub const HELP_TEXT: &str = "
36 pub const HELP_TEXT: &str = "
34 Show changed files in the working directory
37 Show changed files in the working directory
35
38
36 This is a pure Rust version of `hg status`.
39 This is a pure Rust version of `hg status`.
37
40
38 Some options might be missing, check the list below.
41 Some options might be missing, check the list below.
39 ";
42 ";
40
43
41 pub fn args() -> clap::App<'static, 'static> {
44 pub fn args() -> clap::App<'static, 'static> {
42 SubCommand::with_name("status")
45 SubCommand::with_name("status")
43 .alias("st")
46 .alias("st")
44 .about(HELP_TEXT)
47 .about(HELP_TEXT)
45 .arg(
48 .arg(
46 Arg::with_name("all")
49 Arg::with_name("all")
47 .help("show status of all files")
50 .help("show status of all files")
48 .short("-A")
51 .short("-A")
49 .long("--all"),
52 .long("--all"),
50 )
53 )
51 .arg(
54 .arg(
52 Arg::with_name("modified")
55 Arg::with_name("modified")
53 .help("show only modified files")
56 .help("show only modified files")
54 .short("-m")
57 .short("-m")
55 .long("--modified"),
58 .long("--modified"),
56 )
59 )
57 .arg(
60 .arg(
58 Arg::with_name("added")
61 Arg::with_name("added")
59 .help("show only added files")
62 .help("show only added files")
60 .short("-a")
63 .short("-a")
61 .long("--added"),
64 .long("--added"),
62 )
65 )
63 .arg(
66 .arg(
64 Arg::with_name("removed")
67 Arg::with_name("removed")
65 .help("show only removed files")
68 .help("show only removed files")
66 .short("-r")
69 .short("-r")
67 .long("--removed"),
70 .long("--removed"),
68 )
71 )
69 .arg(
72 .arg(
70 Arg::with_name("clean")
73 Arg::with_name("clean")
71 .help("show only clean files")
74 .help("show only clean files")
72 .short("-c")
75 .short("-c")
73 .long("--clean"),
76 .long("--clean"),
74 )
77 )
75 .arg(
78 .arg(
76 Arg::with_name("deleted")
79 Arg::with_name("deleted")
77 .help("show only deleted files")
80 .help("show only deleted files")
78 .short("-d")
81 .short("-d")
79 .long("--deleted"),
82 .long("--deleted"),
80 )
83 )
81 .arg(
84 .arg(
82 Arg::with_name("unknown")
85 Arg::with_name("unknown")
83 .help("show only unknown (not tracked) files")
86 .help("show only unknown (not tracked) files")
84 .short("-u")
87 .short("-u")
85 .long("--unknown"),
88 .long("--unknown"),
86 )
89 )
87 .arg(
90 .arg(
88 Arg::with_name("ignored")
91 Arg::with_name("ignored")
89 .help("show only ignored files")
92 .help("show only ignored files")
90 .short("-i")
93 .short("-i")
91 .long("--ignored"),
94 .long("--ignored"),
92 )
95 )
93 .arg(
96 .arg(
94 Arg::with_name("copies")
97 Arg::with_name("copies")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
98 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 .short("-C")
99 .short("-C")
97 .long("--copies"),
100 .long("--copies"),
98 )
101 )
99 .arg(
102 .arg(
100 Arg::with_name("no-status")
103 Arg::with_name("no-status")
101 .help("hide status prefix")
104 .help("hide status prefix")
102 .short("-n")
105 .short("-n")
103 .long("--no-status"),
106 .long("--no-status"),
104 )
107 )
105 }
108 }
106
109
107 /// Pure data type allowing the caller to specify file states to display
110 /// Pure data type allowing the caller to specify file states to display
108 #[derive(Copy, Clone, Debug)]
111 #[derive(Copy, Clone, Debug)]
109 pub struct DisplayStates {
112 pub struct DisplayStates {
110 pub modified: bool,
113 pub modified: bool,
111 pub added: bool,
114 pub added: bool,
112 pub removed: bool,
115 pub removed: bool,
113 pub clean: bool,
116 pub clean: bool,
114 pub deleted: bool,
117 pub deleted: bool,
115 pub unknown: bool,
118 pub unknown: bool,
116 pub ignored: bool,
119 pub ignored: bool,
117 }
120 }
118
121
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
122 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 modified: true,
123 modified: true,
121 added: true,
124 added: true,
122 removed: true,
125 removed: true,
123 clean: false,
126 clean: false,
124 deleted: true,
127 deleted: true,
125 unknown: true,
128 unknown: true,
126 ignored: false,
129 ignored: false,
127 };
130 };
128
131
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
132 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 modified: true,
133 modified: true,
131 added: true,
134 added: true,
132 removed: true,
135 removed: true,
133 clean: true,
136 clean: true,
134 deleted: true,
137 deleted: true,
135 unknown: true,
138 unknown: true,
136 ignored: true,
139 ignored: true,
137 };
140 };
138
141
139 impl DisplayStates {
142 impl DisplayStates {
140 pub fn is_empty(&self) -> bool {
143 pub fn is_empty(&self) -> bool {
141 !(self.modified
144 !(self.modified
142 || self.added
145 || self.added
143 || self.removed
146 || self.removed
144 || self.clean
147 || self.clean
145 || self.deleted
148 || self.deleted
146 || self.unknown
149 || self.unknown
147 || self.ignored)
150 || self.ignored)
148 }
151 }
149 }
152 }
150
153
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
154 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 // TODO: lift these limitations
155 // TODO: lift these limitations
153 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
156 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
154 return Err(CommandError::unsupported(
157 return Err(CommandError::unsupported(
155 "ui.tweakdefaults is not yet supported with rhg status",
158 "ui.tweakdefaults is not yet supported with rhg status",
156 ));
159 ));
157 }
160 }
158 if invocation.config.get_bool(b"ui", b"statuscopies")? {
161 if invocation.config.get_bool(b"ui", b"statuscopies")? {
159 return Err(CommandError::unsupported(
162 return Err(CommandError::unsupported(
160 "ui.statuscopies is not yet supported with rhg status",
163 "ui.statuscopies is not yet supported with rhg status",
161 ));
164 ));
162 }
165 }
163 if invocation
166 if invocation
164 .config
167 .config
165 .get(b"commands", b"status.terse")
168 .get(b"commands", b"status.terse")
166 .is_some()
169 .is_some()
167 {
170 {
168 return Err(CommandError::unsupported(
171 return Err(CommandError::unsupported(
169 "status.terse is not yet supported with rhg status",
172 "status.terse is not yet supported with rhg status",
170 ));
173 ));
171 }
174 }
172
175
173 let ui = invocation.ui;
176 let ui = invocation.ui;
174 let config = invocation.config;
177 let config = invocation.config;
175 let args = invocation.subcommand_args;
178 let args = invocation.subcommand_args;
176
179
177 let verbose = !ui.plain(None)
180 let verbose = !ui.plain(None)
178 && !args.is_present("print0")
181 && !args.is_present("print0")
179 && (config.get_bool(b"ui", b"verbose")?
182 && (config.get_bool(b"ui", b"verbose")?
180 || config.get_bool(b"commands", b"status.verbose")?);
183 || config.get_bool(b"commands", b"status.verbose")?);
181 if verbose {
184 if verbose {
182 return Err(CommandError::unsupported(
185 return Err(CommandError::unsupported(
183 "verbose status is not supported yet",
186 "verbose status is not supported yet",
184 ));
187 ));
185 }
188 }
186
189
187 let all = args.is_present("all");
190 let all = args.is_present("all");
188 let display_states = if all {
191 let display_states = if all {
189 // TODO when implementing `--quiet`: it excludes clean files
192 // TODO when implementing `--quiet`: it excludes clean files
190 // from `--all`
193 // from `--all`
191 ALL_DISPLAY_STATES
194 ALL_DISPLAY_STATES
192 } else {
195 } else {
193 let requested = DisplayStates {
196 let requested = DisplayStates {
194 modified: args.is_present("modified"),
197 modified: args.is_present("modified"),
195 added: args.is_present("added"),
198 added: args.is_present("added"),
196 removed: args.is_present("removed"),
199 removed: args.is_present("removed"),
197 clean: args.is_present("clean"),
200 clean: args.is_present("clean"),
198 deleted: args.is_present("deleted"),
201 deleted: args.is_present("deleted"),
199 unknown: args.is_present("unknown"),
202 unknown: args.is_present("unknown"),
200 ignored: args.is_present("ignored"),
203 ignored: args.is_present("ignored"),
201 };
204 };
202 if requested.is_empty() {
205 if requested.is_empty() {
203 DEFAULT_DISPLAY_STATES
206 DEFAULT_DISPLAY_STATES
204 } else {
207 } else {
205 requested
208 requested
206 }
209 }
207 };
210 };
208 let no_status = args.is_present("no-status");
211 let no_status = args.is_present("no-status");
209 let list_copies = all
212 let list_copies = all
210 || args.is_present("copies")
213 || args.is_present("copies")
211 || config.get_bool(b"ui", b"statuscopies")?;
214 || config.get_bool(b"ui", b"statuscopies")?;
212
215
213 let repo = invocation.repo?;
216 let repo = invocation.repo?;
214
217
215 if repo.has_sparse() || repo.has_narrow() {
218 if repo.has_sparse() || repo.has_narrow() {
216 return Err(CommandError::unsupported(
219 return Err(CommandError::unsupported(
217 "rhg status is not supported for sparse checkouts or narrow clones yet"
220 "rhg status is not supported for sparse checkouts or narrow clones yet"
218 ));
221 ));
219 }
222 }
220
223
221 let mut dmap = repo.dirstate_map_mut()?;
224 let mut dmap = repo.dirstate_map_mut()?;
222
225
223 let options = StatusOptions {
226 let options = StatusOptions {
224 // we're currently supporting file systems with exec flags only
227 // we're currently supporting file systems with exec flags only
225 // anyway
228 // anyway
226 check_exec: true,
229 check_exec: true,
227 list_clean: display_states.clean,
230 list_clean: display_states.clean,
228 list_unknown: display_states.unknown,
231 list_unknown: display_states.unknown,
229 list_ignored: display_states.ignored,
232 list_ignored: display_states.ignored,
230 list_copies,
233 list_copies,
231 collect_traversed_dirs: false,
234 collect_traversed_dirs: false,
232 };
235 };
233 let (mut ds_status, pattern_warnings) = dmap.status(
236
234 &AlwaysMatcher,
237 type StatusResult<'a> =
235 repo.working_directory_path().to_owned(),
238 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
236 ignore_files(repo, config),
239
237 options,
240 let after_status = |res: StatusResult| -> Result<_, CommandError> {
238 )?;
241 let (mut ds_status, pattern_warnings) = res?;
239 for warning in pattern_warnings {
242 for warning in pattern_warnings {
240 match warning {
243 match warning {
241 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
244 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
242 .write_stderr(&format_bytes!(
245 .write_stderr(&format_bytes!(
243 b"{}: ignoring invalid syntax '{}'\n",
246 b"{}: ignoring invalid syntax '{}'\n",
244 get_bytes_from_path(path),
247 get_bytes_from_path(path),
245 &*syntax
248 &*syntax
246 ))?,
249 ))?,
247 hg::PatternFileWarning::NoSuchFile(path) => {
250 hg::PatternFileWarning::NoSuchFile(path) => {
248 let path = if let Ok(relative) =
251 let path = if let Ok(relative) =
249 path.strip_prefix(repo.working_directory_path())
252 path.strip_prefix(repo.working_directory_path())
250 {
253 {
251 relative
254 relative
252 } else {
255 } else {
253 &*path
256 &*path
254 };
257 };
255 ui.write_stderr(&format_bytes!(
258 ui.write_stderr(&format_bytes!(
256 b"skipping unreadable pattern file '{}': \
259 b"skipping unreadable pattern file '{}': \
257 No such file or directory\n",
260 No such file or directory\n",
258 get_bytes_from_path(path),
261 get_bytes_from_path(path),
259 ))?
262 ))?
263 }
260 }
264 }
261 }
265 }
262 }
263
266
264 for (path, error) in ds_status.bad {
267 for (path, error) in ds_status.bad {
265 let error = match error {
268 let error = match error {
266 hg::BadMatch::OsError(code) => {
269 hg::BadMatch::OsError(code) => {
267 std::io::Error::from_raw_os_error(code).to_string()
270 std::io::Error::from_raw_os_error(code).to_string()
268 }
271 }
269 hg::BadMatch::BadType(ty) => {
272 hg::BadMatch::BadType(ty) => {
270 format!("unsupported file type (type is {})", ty)
273 format!("unsupported file type (type is {})", ty)
271 }
272 };
273 ui.write_stderr(&format_bytes!(
274 b"{}: {}\n",
275 path.as_bytes(),
276 error.as_bytes()
277 ))?
278 }
279 if !ds_status.unsure.is_empty() {
280 info!(
281 "Files to be rechecked by retrieval from filelog: {:?}",
282 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
283 );
284 }
285 let mut fixup = Vec::new();
286 if !ds_status.unsure.is_empty()
287 && (display_states.modified || display_states.clean)
288 {
289 let p1 = repo.dirstate_parents()?.p1;
290 let manifest = repo.manifest_for_node(p1).map_err(|e| {
291 CommandError::from((e, &*format!("{:x}", p1.short())))
292 })?;
293 for to_check in ds_status.unsure {
294 if unsure_is_modified(repo, &manifest, &to_check.path)? {
295 if display_states.modified {
296 ds_status.modified.push(to_check);
297 }
274 }
298 } else {
275 };
299 if display_states.clean {
276 ui.write_stderr(&format_bytes!(
300 ds_status.clean.push(to_check.clone());
277 b"{}: {}\n",
278 path.as_bytes(),
279 error.as_bytes()
280 ))?
281 }
282 if !ds_status.unsure.is_empty() {
283 info!(
284 "Files to be rechecked by retrieval from filelog: {:?}",
285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
286 );
287 }
288 let mut fixup = Vec::new();
289 if !ds_status.unsure.is_empty()
290 && (display_states.modified || display_states.clean)
291 {
292 let p1 = repo.dirstate_parents()?.p1;
293 let manifest = repo.manifest_for_node(p1).map_err(|e| {
294 CommandError::from((e, &*format!("{:x}", p1.short())))
295 })?;
296 for to_check in ds_status.unsure {
297 if unsure_is_modified(repo, &manifest, &to_check.path)? {
298 if display_states.modified {
299 ds_status.modified.push(to_check);
300 }
301 } else {
302 if display_states.clean {
303 ds_status.clean.push(to_check.clone());
304 }
305 fixup.push(to_check.path.into_owned())
301 }
306 }
302 fixup.push(to_check.path.into_owned())
303 }
307 }
304 }
308 }
305 }
309 let relative_paths = (!ui.plain(None))
306 let relative_paths = (!ui.plain(None))
310 && config
307 && config
311 .get_option(b"commands", b"status.relative")?
308 .get_option(b"commands", b"status.relative")?
312 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
309 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
313 let output = DisplayStatusPaths {
310 let output = DisplayStatusPaths {
314 ui,
311 ui,
315 no_status,
312 no_status,
316 relativize: if relative_paths {
313 relativize: if relative_paths {
317 Some(RelativizePaths::new(repo)?)
314 Some(RelativizePaths::new(repo)?)
318 } else {
315 } else {
319 None
316 None
320 },
317 },
321 };
322 if display_states.modified {
323 output.display(b"M ", "status.modified", ds_status.modified)?;
324 }
325 if display_states.added {
326 output.display(b"A ", "status.added", ds_status.added)?;
327 }
328 if display_states.removed {
329 output.display(b"R ", "status.removed", ds_status.removed)?;
330 }
331 if display_states.deleted {
332 output.display(b"! ", "status.deleted", ds_status.deleted)?;
333 }
334 if display_states.unknown {
335 output.display(b"? ", "status.unknown", ds_status.unknown)?;
336 }
337 if display_states.ignored {
338 output.display(b"I ", "status.ignored", ds_status.ignored)?;
339 }
340 if display_states.clean {
341 output.display(b"C ", "status.clean", ds_status.clean)?;
342 }
343
344 let dirstate_write_needed = ds_status.dirty;
345 let filesystem_time_at_status_start =
346 ds_status.filesystem_time_at_status_start;
347
348 Ok((
349 fixup,
350 dirstate_write_needed,
351 filesystem_time_at_status_start,
352 ))
318 };
353 };
319 if display_states.modified {
354 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
320 output.display(b"M ", "status.modified", ds_status.modified)?;
355 dmap.with_status(
321 }
356 &AlwaysMatcher,
322 if display_states.added {
357 repo.working_directory_path().to_owned(),
323 output.display(b"A ", "status.added", ds_status.added)?;
358 ignore_files(repo, config),
324 }
359 options,
325 if display_states.removed {
360 after_status,
326 output.display(b"R ", "status.removed", ds_status.removed)?;
361 )?;
327 }
328 if display_states.deleted {
329 output.display(b"! ", "status.deleted", ds_status.deleted)?;
330 }
331 if display_states.unknown {
332 output.display(b"? ", "status.unknown", ds_status.unknown)?;
333 }
334 if display_states.ignored {
335 output.display(b"I ", "status.ignored", ds_status.ignored)?;
336 }
337 if display_states.clean {
338 output.display(b"C ", "status.clean", ds_status.clean)?;
339 }
340
341 let mut dirstate_write_needed = ds_status.dirty;
342 let filesystem_time_at_status_start =
343 ds_status.filesystem_time_at_status_start;
344
362
345 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
363 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
346 && !dirstate_write_needed
364 && !dirstate_write_needed
347 {
365 {
348 // Nothing to update
366 // Nothing to update
349 return Ok(());
367 return Ok(());
350 }
368 }
351
369
352 // Update the dirstate on disk if we can
370 // Update the dirstate on disk if we can
353 let with_lock_result =
371 let with_lock_result =
354 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
372 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
355 if let Some(mtime_boundary) = filesystem_time_at_status_start {
373 if let Some(mtime_boundary) = filesystem_time_at_status_start {
356 for hg_path in fixup {
374 for hg_path in fixup {
357 use std::os::unix::fs::MetadataExt;
375 use std::os::unix::fs::MetadataExt;
358 let fs_path = hg_path_to_path_buf(&hg_path)
376 let fs_path = hg_path_to_path_buf(&hg_path)
359 .expect("HgPath conversion");
377 .expect("HgPath conversion");
360 // Specifically do not reuse `fs_metadata` from
378 // Specifically do not reuse `fs_metadata` from
361 // `unsure_is_clean` which was needed before reading
379 // `unsure_is_clean` which was needed before reading
362 // contents. Here we access metadata again after reading
380 // contents. Here we access metadata again after reading
363 // content, in case it changed in the meantime.
381 // content, in case it changed in the meantime.
364 let fs_metadata = repo
382 let fs_metadata = repo
365 .working_directory_vfs()
383 .working_directory_vfs()
366 .symlink_metadata(&fs_path)?;
384 .symlink_metadata(&fs_path)?;
367 if let Some(mtime) =
385 if let Some(mtime) =
368 TruncatedTimestamp::for_reliable_mtime_of(
386 TruncatedTimestamp::for_reliable_mtime_of(
369 &fs_metadata,
387 &fs_metadata,
370 &mtime_boundary,
388 &mtime_boundary,
371 )
389 )
372 .when_reading_file(&fs_path)?
390 .when_reading_file(&fs_path)?
373 {
391 {
374 let mode = fs_metadata.mode();
392 let mode = fs_metadata.mode();
375 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
393 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
376 let mut entry = dmap
394 let mut entry = dmap
377 .get(&hg_path)?
395 .get(&hg_path)?
378 .expect("ambiguous file not in dirstate");
396 .expect("ambiguous file not in dirstate");
379 entry.set_clean(mode, size, mtime);
397 entry.set_clean(mode, size, mtime);
380 dmap.add_file(&hg_path, entry)?;
398 dmap.add_file(&hg_path, entry)?;
381 dirstate_write_needed = true
399 dirstate_write_needed = true
382 }
400 }
383 }
401 }
384 }
402 }
385 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
403 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
386 if dirstate_write_needed {
404 if dirstate_write_needed {
387 repo.write_dirstate()?
405 repo.write_dirstate()?
388 }
406 }
389 Ok(())
407 Ok(())
390 });
408 });
391 match with_lock_result {
409 match with_lock_result {
392 Ok(closure_result) => closure_result?,
410 Ok(closure_result) => closure_result?,
393 Err(LockError::AlreadyHeld) => {
411 Err(LockError::AlreadyHeld) => {
394 // Not updating the dirstate is not ideal but not critical:
412 // Not updating the dirstate is not ideal but not critical:
395 // don’t keep our caller waiting until some other Mercurial
413 // don’t keep our caller waiting until some other Mercurial
396 // process releases the lock.
414 // process releases the lock.
397 }
415 }
398 Err(LockError::Other(HgError::IoError { error, .. }))
416 Err(LockError::Other(HgError::IoError { error, .. }))
399 if error.kind() == io::ErrorKind::PermissionDenied =>
417 if error.kind() == io::ErrorKind::PermissionDenied =>
400 {
418 {
401 // `hg status` on a read-only repository is fine
419 // `hg status` on a read-only repository is fine
402 }
420 }
403 Err(LockError::Other(error)) => {
421 Err(LockError::Other(error)) => {
404 // Report other I/O errors
422 // Report other I/O errors
405 Err(error)?
423 Err(error)?
406 }
424 }
407 }
425 }
408 Ok(())
426 Ok(())
409 }
427 }
410
428
411 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
429 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
412 let mut ignore_files = Vec::new();
430 let mut ignore_files = Vec::new();
413 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
431 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
414 if repo_ignore.exists() {
432 if repo_ignore.exists() {
415 ignore_files.push(repo_ignore)
433 ignore_files.push(repo_ignore)
416 }
434 }
417 for (key, value) in config.iter_section(b"ui") {
435 for (key, value) in config.iter_section(b"ui") {
418 if key == b"ignore" || key.starts_with(b"ignore.") {
436 if key == b"ignore" || key.starts_with(b"ignore.") {
419 let path = get_path_from_bytes(value);
437 let path = get_path_from_bytes(value);
420 // TODO: expand "~/" and environment variable here, like Python
438 // TODO: expand "~/" and environment variable here, like Python
421 // does with `os.path.expanduser` and `os.path.expandvars`
439 // does with `os.path.expanduser` and `os.path.expandvars`
422
440
423 let joined = repo.working_directory_path().join(path);
441 let joined = repo.working_directory_path().join(path);
424 ignore_files.push(joined);
442 ignore_files.push(joined);
425 }
443 }
426 }
444 }
427 ignore_files
445 ignore_files
428 }
446 }
429
447
430 struct DisplayStatusPaths<'a> {
448 struct DisplayStatusPaths<'a> {
431 ui: &'a Ui,
449 ui: &'a Ui,
432 no_status: bool,
450 no_status: bool,
433 relativize: Option<RelativizePaths>,
451 relativize: Option<RelativizePaths>,
434 }
452 }
435
453
436 impl DisplayStatusPaths<'_> {
454 impl DisplayStatusPaths<'_> {
437 // Probably more elegant to use a Deref or Borrow trait rather than
455 // Probably more elegant to use a Deref or Borrow trait rather than
438 // harcode HgPathBuf, but probably not really useful at this point
456 // harcode HgPathBuf, but probably not really useful at this point
439 fn display(
457 fn display(
440 &self,
458 &self,
441 status_prefix: &[u8],
459 status_prefix: &[u8],
442 label: &'static str,
460 label: &'static str,
443 mut paths: Vec<StatusPath<'_>>,
461 mut paths: Vec<StatusPath<'_>>,
444 ) -> Result<(), CommandError> {
462 ) -> Result<(), CommandError> {
445 paths.sort_unstable();
463 paths.sort_unstable();
446 // TODO: get the stdout lock once for the whole loop
464 // TODO: get the stdout lock once for the whole loop
447 // instead of in each write
465 // instead of in each write
448 for StatusPath { path, copy_source } in paths {
466 for StatusPath { path, copy_source } in paths {
449 let relative;
467 let relative;
450 let path = if let Some(relativize) = &self.relativize {
468 let path = if let Some(relativize) = &self.relativize {
451 relative = relativize.relativize(&path);
469 relative = relativize.relativize(&path);
452 &*relative
470 &*relative
453 } else {
471 } else {
454 path.as_bytes()
472 path.as_bytes()
455 };
473 };
456 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
474 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
457 // in order to stream to stdout instead of allocating an
475 // in order to stream to stdout instead of allocating an
458 // itermediate `Vec<u8>`.
476 // itermediate `Vec<u8>`.
459 if !self.no_status {
477 if !self.no_status {
460 self.ui.write_stdout_labelled(status_prefix, label)?
478 self.ui.write_stdout_labelled(status_prefix, label)?
461 }
479 }
462 self.ui
480 self.ui
463 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
481 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
464 if let Some(source) = copy_source {
482 if let Some(source) = copy_source {
465 let label = "status.copied";
483 let label = "status.copied";
466 self.ui.write_stdout_labelled(
484 self.ui.write_stdout_labelled(
467 &format_bytes!(b" {}\n", source.as_bytes()),
485 &format_bytes!(b" {}\n", source.as_bytes()),
468 label,
486 label,
469 )?
487 )?
470 }
488 }
471 }
489 }
472 Ok(())
490 Ok(())
473 }
491 }
474 }
492 }
475
493
476 /// Check if a file is modified by comparing actual repo store and file system.
494 /// Check if a file is modified by comparing actual repo store and file system.
477 ///
495 ///
478 /// This meant to be used for those that the dirstate cannot resolve, due
496 /// This meant to be used for those that the dirstate cannot resolve, due
479 /// to time resolution limits.
497 /// to time resolution limits.
480 fn unsure_is_modified(
498 fn unsure_is_modified(
481 repo: &Repo,
499 repo: &Repo,
482 manifest: &Manifest,
500 manifest: &Manifest,
483 hg_path: &HgPath,
501 hg_path: &HgPath,
484 ) -> Result<bool, HgError> {
502 ) -> Result<bool, HgError> {
485 let vfs = repo.working_directory_vfs();
503 let vfs = repo.working_directory_vfs();
486 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
504 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
487 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
505 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
488 let is_symlink = fs_metadata.file_type().is_symlink();
506 let is_symlink = fs_metadata.file_type().is_symlink();
489 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
507 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
490 // dirstate
508 // dirstate
491 let fs_flags = if is_symlink {
509 let fs_flags = if is_symlink {
492 Some(b'l')
510 Some(b'l')
493 } else if has_exec_bit(&fs_metadata) {
511 } else if has_exec_bit(&fs_metadata) {
494 Some(b'x')
512 Some(b'x')
495 } else {
513 } else {
496 None
514 None
497 };
515 };
498
516
499 let entry = manifest
517 let entry = manifest
500 .find_by_path(hg_path)?
518 .find_by_path(hg_path)?
501 .expect("ambgious file not in p1");
519 .expect("ambgious file not in p1");
502 if entry.flags != fs_flags {
520 if entry.flags != fs_flags {
503 return Ok(true);
521 return Ok(true);
504 }
522 }
505 let filelog = repo.filelog(hg_path)?;
523 let filelog = repo.filelog(hg_path)?;
506 let fs_len = fs_metadata.len();
524 let fs_len = fs_metadata.len();
507 let filelog_entry =
525 let filelog_entry =
508 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
526 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
509 HgError::corrupted("filelog missing node from manifest")
527 HgError::corrupted("filelog missing node from manifest")
510 })?;
528 })?;
511 if filelog_entry.file_data_len_not_equal_to(fs_len) {
529 if filelog_entry.file_data_len_not_equal_to(fs_len) {
512 // No need to read file contents:
530 // No need to read file contents:
513 // it cannot be equal if it has a different length.
531 // it cannot be equal if it has a different length.
514 return Ok(true);
532 return Ok(true);
515 }
533 }
516
534
517 let p1_filelog_data = filelog_entry.data()?;
535 let p1_filelog_data = filelog_entry.data()?;
518 let p1_contents = p1_filelog_data.file_data()?;
536 let p1_contents = p1_filelog_data.file_data()?;
519 if p1_contents.len() as u64 != fs_len {
537 if p1_contents.len() as u64 != fs_len {
520 // No need to read file contents:
538 // No need to read file contents:
521 // it cannot be equal if it has a different length.
539 // it cannot be equal if it has a different length.
522 return Ok(true);
540 return Ok(true);
523 }
541 }
524
542
525 let fs_contents = if is_symlink {
543 let fs_contents = if is_symlink {
526 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
544 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
527 } else {
545 } else {
528 vfs.read(fs_path)?
546 vfs.read(fs_path)?
529 };
547 };
530 Ok(p1_contents != &*fs_contents)
548 Ok(p1_contents != &*fs_contents)
531 }
549 }
General Comments 0
You need to be logged in to leave comments. Login now