Show More
@@ -640,6 +640,15 b' coreconfigitem(' | |||||
640 | b'deprec-warn', |
|
640 | b'deprec-warn', | |
641 | default=False, |
|
641 | default=False, | |
642 | ) |
|
642 | ) | |
|
643 | # possible values: | |||
|
644 | # - auto (the default) | |||
|
645 | # - force-append | |||
|
646 | # - force-new | |||
|
647 | coreconfigitem( | |||
|
648 | b'devel', | |||
|
649 | b'dirstate.v2.data_update_mode', | |||
|
650 | default="auto", | |||
|
651 | ) | |||
643 | coreconfigitem( |
|
652 | coreconfigitem( | |
644 | b'devel', |
|
653 | b'devel', | |
645 | b'disableloaddefaultcerts', |
|
654 | b'disableloaddefaultcerts', |
@@ -33,6 +33,7 b' rangemask = 0x7FFFFFFF' | |||||
33 |
|
33 | |||
34 | WRITE_MODE_AUTO = 0 |
|
34 | WRITE_MODE_AUTO = 0 | |
35 | WRITE_MODE_FORCE_NEW = 1 |
|
35 | WRITE_MODE_FORCE_NEW = 1 | |
|
36 | WRITE_MODE_FORCE_APPEND = 2 | |||
36 |
|
37 | |||
37 |
|
38 | |||
38 | class _dirstatemapcommon: |
|
39 | class _dirstatemapcommon: | |
@@ -57,6 +58,16 b' class _dirstatemapcommon:' | |||||
57 | self._parents = None |
|
58 | self._parents = None | |
58 | self._dirtyparents = False |
|
59 | self._dirtyparents = False | |
59 | self._docket = None |
|
60 | self._docket = None | |
|
61 | write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode") | |||
|
62 | if write_mode == b"auto": | |||
|
63 | self._write_mode = WRITE_MODE_AUTO | |||
|
64 | elif write_mode == b"force-append": | |||
|
65 | self._write_mode = WRITE_MODE_FORCE_APPEND | |||
|
66 | elif write_mode == b"force-new": | |||
|
67 | self._write_mode = WRITE_MODE_FORCE_NEW | |||
|
68 | else: | |||
|
69 | # unknown value, fallback to default | |||
|
70 | self._write_mode = WRITE_MODE_AUTO | |||
60 |
|
71 | |||
61 | # for consistent view between _pl() and _read() invocations |
|
72 | # for consistent view between _pl() and _read() invocations | |
62 | self._pendingmode = None |
|
73 | self._pendingmode = None | |
@@ -612,7 +623,7 b' if rustmod is not None:' | |||||
612 | return |
|
623 | return | |
613 |
|
624 | |||
614 | # We can only append to an existing data file if there is one |
|
625 | # We can only append to an existing data file if there is one | |
615 |
write_mode = |
|
626 | write_mode = self._write_mode | |
616 | if self.docket.uuid is None: |
|
627 | if self.docket.uuid is None: | |
617 | write_mode = WRITE_MODE_FORCE_NEW |
|
628 | write_mode = WRITE_MODE_FORCE_NEW | |
618 | packed, meta, append = self._map.write_v2(write_mode) |
|
629 | packed, meta, append = self._map.write_v2(write_mode) |
@@ -42,6 +42,7 b' pub enum DirstateVersion {' | |||||
42 | pub enum DirstateMapWriteMode { |
|
42 | pub enum DirstateMapWriteMode { | |
43 | Auto, |
|
43 | Auto, | |
44 | ForceNewDataFile, |
|
44 | ForceNewDataFile, | |
|
45 | ForceAppend, | |||
45 | } |
|
46 | } | |
46 |
|
47 | |||
47 | #[derive(Debug)] |
|
48 | #[derive(Debug)] | |
@@ -69,6 +70,9 b" pub struct DirstateMap<'on_disk> {" | |||||
69 | pub(super) old_data_size: usize, |
|
70 | pub(super) old_data_size: usize, | |
70 |
|
71 | |||
71 | pub(super) dirstate_version: DirstateVersion, |
|
72 | pub(super) dirstate_version: DirstateVersion, | |
|
73 | ||||
|
74 | /// Controlled by config option `devel.dirstate.v2.data_update_mode` | |||
|
75 | pub(super) write_mode: DirstateMapWriteMode, | |||
72 | } |
|
76 | } | |
73 |
|
77 | |||
74 | /// Using a plain `HgPathBuf` of the full path from the repository root as a |
|
78 | /// Using a plain `HgPathBuf` of the full path from the repository root as a | |
@@ -457,6 +461,7 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
457 | unreachable_bytes: 0, |
|
461 | unreachable_bytes: 0, | |
458 | old_data_size: 0, |
|
462 | old_data_size: 0, | |
459 | dirstate_version: DirstateVersion::V1, |
|
463 | dirstate_version: DirstateVersion::V1, | |
|
464 | write_mode: DirstateMapWriteMode::Auto, | |||
460 | } |
|
465 | } | |
461 | } |
|
466 | } | |
462 |
|
467 | |||
@@ -525,9 +530,16 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
525 | /// append to the existing data file that contains `self.on_disk` (true), |
|
530 | /// append to the existing data file that contains `self.on_disk` (true), | |
526 | /// or create a new data file from scratch (false). |
|
531 | /// or create a new data file from scratch (false). | |
527 | pub(super) fn write_should_append(&self) -> bool { |
|
532 | pub(super) fn write_should_append(&self) -> bool { | |
528 | let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32; |
|
533 | match self.write_mode { | |
|
534 | DirstateMapWriteMode::ForceAppend => true, | |||
|
535 | DirstateMapWriteMode::ForceNewDataFile => false, | |||
|
536 | DirstateMapWriteMode::Auto => { | |||
|
537 | let ratio = | |||
|
538 | self.unreachable_bytes as f32 / self.on_disk.len() as f32; | |||
529 | ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO |
|
539 | ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO | |
530 | } |
|
540 | } | |
|
541 | } | |||
|
542 | } | |||
531 |
|
543 | |||
532 | fn get_node<'tree>( |
|
544 | fn get_node<'tree>( | |
533 | &'tree self, |
|
545 | &'tree self, | |
@@ -923,6 +935,10 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
923 | *unreachable_bytes += path.len() as u32 |
|
935 | *unreachable_bytes += path.len() as u32 | |
924 | } |
|
936 | } | |
925 | } |
|
937 | } | |
|
938 | ||||
|
939 | pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) { | |||
|
940 | self.write_mode = write_mode; | |||
|
941 | } | |||
926 | } |
|
942 | } | |
927 |
|
943 | |||
928 | /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s. |
|
944 | /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s. |
@@ -313,6 +313,7 b" pub(super) fn read<'on_disk>(" | |||||
313 | unreachable_bytes: meta.unreachable_bytes.get(), |
|
313 | unreachable_bytes: meta.unreachable_bytes.get(), | |
314 | old_data_size: on_disk.len(), |
|
314 | old_data_size: on_disk.len(), | |
315 | dirstate_version: DirstateVersion::V2, |
|
315 | dirstate_version: DirstateVersion::V2, | |
|
316 | write_mode: DirstateMapWriteMode::Auto, | |||
316 | }; |
|
317 | }; | |
317 | Ok(dirstate_map) |
|
318 | Ok(dirstate_map) | |
318 | } |
|
319 | } | |
@@ -641,6 +642,7 b' pub(super) fn write(' | |||||
641 | let append = match write_mode { |
|
642 | let append = match write_mode { | |
642 | DirstateMapWriteMode::Auto => dirstate_map.write_should_append(), |
|
643 | DirstateMapWriteMode::Auto => dirstate_map.write_should_append(), | |
643 | DirstateMapWriteMode::ForceNewDataFile => false, |
|
644 | DirstateMapWriteMode::ForceNewDataFile => false, | |
|
645 | DirstateMapWriteMode::ForceAppend => true, | |||
644 | }; |
|
646 | }; | |
645 | if append { |
|
647 | if append { | |
646 | log::trace!("appending to the dirstate data file"); |
|
648 | log::trace!("appending to the dirstate data file"); |
@@ -321,6 +321,7 b' impl Repo {' | |||||
321 | .set(Some(docket.uuid.to_owned())); |
|
321 | .set(Some(docket.uuid.to_owned())); | |
322 | let data_size = docket.data_size(); |
|
322 | let data_size = docket.data_size(); | |
323 | let metadata = docket.tree_metadata(); |
|
323 | let metadata = docket.tree_metadata(); | |
|
324 | let mut map = | |||
324 | if crate::vfs::is_on_nfs_mount(docket.data_filename()) { |
|
325 | if crate::vfs::is_on_nfs_mount(docket.data_filename()) { | |
325 | // Don't mmap on NFS to prevent `SIGBUS` error on deletion |
|
326 | // Don't mmap on NFS to prevent `SIGBUS` error on deletion | |
326 | OwningDirstateMap::new_v2( |
|
327 | OwningDirstateMap::new_v2( | |
@@ -336,7 +337,23 b' impl Repo {' | |||||
336 | OwningDirstateMap::new_v2(data_mmap, data_size, metadata) |
|
337 | OwningDirstateMap::new_v2(data_mmap, data_size, metadata) | |
337 | } else { |
|
338 | } else { | |
338 | OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) |
|
339 | OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) | |
339 | } |
|
340 | }?; | |
|
341 | ||||
|
342 | let write_mode_config = self | |||
|
343 | .config() | |||
|
344 | .get_str(b"devel", b"dirstate.v2.data_update_mode") | |||
|
345 | .unwrap_or(Some("auto")) | |||
|
346 | .unwrap_or("auto"); // don't bother for devel options | |||
|
347 | let write_mode = match write_mode_config { | |||
|
348 | "auto" => DirstateMapWriteMode::Auto, | |||
|
349 | "force-new" => DirstateMapWriteMode::ForceNewDataFile, | |||
|
350 | "force-append" => DirstateMapWriteMode::ForceAppend, | |||
|
351 | _ => DirstateMapWriteMode::Auto, | |||
|
352 | }; | |||
|
353 | ||||
|
354 | map.with_dmap_mut(|m| m.set_write_mode(write_mode)); | |||
|
355 | ||||
|
356 | Ok(map) | |||
340 | } else { |
|
357 | } else { | |
341 | let (map, parents) = |
|
358 | let (map, parents) = | |
342 | OwningDirstateMap::new_v1(dirstate_file_contents)?; |
|
359 | OwningDirstateMap::new_v1(dirstate_file_contents)?; |
@@ -254,6 +254,7 b' py_class!(pub class DirstateMap |py| {' | |||||
254 | let rust_write_mode = match write_mode { |
|
254 | let rust_write_mode = match write_mode { | |
255 | 0 => DirstateMapWriteMode::Auto, |
|
255 | 0 => DirstateMapWriteMode::Auto, | |
256 | 1 => DirstateMapWriteMode::ForceNewDataFile, |
|
256 | 1 => DirstateMapWriteMode::ForceNewDataFile, | |
|
257 | 2 => DirstateMapWriteMode::ForceAppend, | |||
257 | _ => DirstateMapWriteMode::Auto, // XXX should we error out? |
|
258 | _ => DirstateMapWriteMode::Auto, // XXX should we error out? | |
258 | }; |
|
259 | }; | |
259 | let result = inner.pack_v2(rust_write_mode); |
|
260 | let result = inner.pack_v2(rust_write_mode); |
@@ -93,7 +93,7 b' Add some unknown files and refresh the d' | |||||
93 | $ hg add dir/o |
|
93 | $ hg add dir/o | |
94 | $ hg remove dir/nested/m |
|
94 | $ hg remove dir/nested/m | |
95 |
|
95 | |||
96 | $ hg st |
|
96 | $ hg st --config devel.dirstate.v2.data_update_mode=force-new | |
97 | A dir/o |
|
97 | A dir/o | |
98 | R dir/nested/m |
|
98 | R dir/nested/m | |
99 | ? dir/n |
|
99 | ? dir/n |
@@ -211,8 +211,223 b' Check that unused bytes counter is reset' | |||||
211 |
|
211 | |||
212 | #endif |
|
212 | #endif | |
213 |
|
213 | |||
|
214 | (non-Rust always rewrites) | |||
|
215 | ||||
|
216 | Test the devel option to control write behavior | |||
|
217 | ============================================== | |||
|
218 | ||||
|
219 | Sometimes, debugging or testing the dirstate requires making sure that we have | |||
|
220 | done a complete rewrite of the data file and have no unreachable data around, | |||
|
221 | sometimes it requires we ensure we don't. | |||
|
222 | ||||
|
223 | We test the option to force this rewrite by creating the situation where an | |||
|
224 | append would happen and check that it doesn't happen. | |||
|
225 | ||||
|
226 | $ cd .. | |||
|
227 | $ hg init force-base | |||
|
228 | $ cd force-base | |||
|
229 | $ mkdir -p dir/nested dir2 | |||
|
230 | $ touch -t 200001010000 f dir/nested/a dir/b dir/c dir/d dir2/e dir/nested dir dir2 | |||
|
231 | $ hg commit -Aqm "recreate a bunch of files to facilitate append" | |||
|
232 | $ hg st --config devel.dirstate.v2.data_update_mode=force-new | |||
|
233 | $ cd .. | |||
|
234 | ||||
|
235 | #if dirstate-v2 | |||
|
236 | $ hg -R force-base debugstate --docket | grep unused | |||
|
237 | number of unused bytes: 0 | |||
|
238 | ||||
|
239 | Check with the option in "auto" mode | |||
|
240 | ------------------------------------ | |||
|
241 | $ cp -a force-base append-mostly-no-force-rewrite | |||
|
242 | $ cd append-mostly-no-force-rewrite | |||
|
243 | $ current_uid=$(find_dirstate_uuid) | |||
|
244 | ||||
|
245 | Change mtime of dir on disk which will be recorded, causing a small enough change | |||
|
246 | to warrant only an append | |||
|
247 | ||||
|
248 | $ touch -t 202212010000 dir2 | |||
|
249 | $ hg st \ | |||
|
250 | > --config rhg.on-unsupported=abort \ | |||
|
251 | > --config devel.dirstate.v2.data_update_mode=auto | |||
|
252 | ||||
|
253 | UUID hasn't changed and a non-zero number of unused bytes means we've appended | |||
|
254 | ||||
|
255 | $ dirstate_uuid_has_not_changed | |||
|
256 | not testing because using Python implementation (no-rust no-rhg !) | |||
|
257 | ||||
|
258 | #if no-rust no-rhg | |||
|
259 | The pure python implementation never appends at the time this is written. | |||
|
260 | $ hg debugstate --docket | grep unused | |||
|
261 | number of unused bytes: 0 (known-bad-output !) | |||
|
262 | #else | |||
|
263 | $ hg debugstate --docket | grep unused | |||
|
264 | number of unused bytes: [1-9]\d* (re) | |||
|
265 | #endif | |||
|
266 | $ cd .. | |||
|
267 | ||||
|
268 | Check the same scenario with the option set to "force-new" | |||
|
269 | --------------------------------------------------------- | |||
|
270 | ||||
|
271 | $ cp -a force-base append-mostly-force-rewrite | |||
|
272 | $ cd append-mostly-force-rewrite | |||
|
273 | $ current_uid=$(find_dirstate_uuid) | |||
|
274 | ||||
|
275 | Change mtime of dir on disk which will be recorded, causing a small enough change | |||
|
276 | to warrant only an append, but we force the rewrite | |||
|
277 | ||||
|
278 | $ touch -t 202212010000 dir2 | |||
|
279 | $ hg st \ | |||
|
280 | > --config rhg.on-unsupported=abort \ | |||
|
281 | > --config devel.dirstate.v2.data_update_mode=force-new | |||
|
282 | ||||
|
283 | UUID has changed and zero unused bytes means a full-rewrite happened | |||
|
284 | ||||
|
285 | ||||
|
286 | #if no-rust no-rhg | |||
|
287 | $ dirstate_uuid_has_not_changed | |||
|
288 | not testing because using Python implementation | |||
|
289 | #else | |||
|
290 | $ dirstate_uuid_has_not_changed | |||
|
291 | [1] | |||
|
292 | #endif | |||
|
293 | $ hg debugstate --docket | grep unused | |||
|
294 | number of unused bytes: 0 | |||
|
295 | $ cd .. | |||
|
296 | ||||
|
297 | ||||
|
298 | Check the same scenario with the option set to "force-append" | |||
|
299 | ------------------------------------------------------------- | |||
|
300 | ||||
|
301 | (should behave the same as "auto" here) | |||
|
302 | ||||
|
303 | $ cp -a force-base append-mostly-force-append | |||
|
304 | $ cd append-mostly-force-append | |||
|
305 | $ current_uid=$(find_dirstate_uuid) | |||
|
306 | ||||
|
307 | Change mtime of dir on disk which will be recorded, causing a small enough change | |||
|
308 | to warrant only an append, which we are forcing here anyway. | |||
|
309 | ||||
|
310 | $ touch -t 202212010000 dir2 | |||
|
311 | $ hg st \ | |||
|
312 | > --config rhg.on-unsupported=abort \ | |||
|
313 | > --config devel.dirstate.v2.data_update_mode=force-append | |||
|
314 | ||||
|
315 | UUID has not changed and some unused bytes exist in the data file | |||
|
316 | ||||
|
317 | $ dirstate_uuid_has_not_changed | |||
|
318 | not testing because using Python implementation (no-rust no-rhg !) | |||
|
319 | ||||
|
320 | #if no-rust no-rhg | |||
|
321 | The pure python implementation never appends at the time this is written. | |||
|
322 | $ hg debugstate --docket | grep unused | |||
|
323 | number of unused bytes: 0 (known-bad-output !) | |||
|
324 | #else | |||
|
325 | $ hg debugstate --docket | grep unused | |||
|
326 | number of unused bytes: [1-9]\d* (re) | |||
|
327 | #endif | |||
|
328 | $ cd .. | |||
|
329 | ||||
|
330 | Check with the option in "auto" mode | |||
|
331 | ------------------------------------ | |||
|
332 | $ cp -a force-base append-mostly-no-force-rewrite | |||
|
333 | $ cd append-mostly-no-force-rewrite | |||
|
334 | $ current_uid=$(find_dirstate_uuid) | |||
|
335 | ||||
|
336 | Change mtime of everything on disk causing a full rewrite | |||
|
337 | ||||
|
338 | $ touch -t 202212010005 `hg files` | |||
|
339 | $ hg st \ | |||
|
340 | > --config rhg.on-unsupported=abort \ | |||
|
341 | > --config devel.dirstate.v2.data_update_mode=auto | |||
|
342 | ||||
|
343 | UUID has changed and zero unused bytes means we've rewritten. | |||
|
344 | ||||
|
345 | #if no-rust no-rhg | |||
|
346 | $ dirstate_uuid_has_not_changed | |||
|
347 | not testing because using Python implementation | |||
|
348 | #else | |||
|
349 | $ dirstate_uuid_has_not_changed | |||
|
350 | [1] | |||
|
351 | #endif | |||
|
352 | ||||
|
353 | $ hg debugstate --docket | grep unused | |||
|
354 | number of unused bytes: 0 (known-bad-output !) | |||
|
355 | $ cd .. | |||
|
356 | ||||
|
357 | Check the same scenario with the option set to "force-new" | |||
|
358 | --------------------------------------------------------- | |||
|
359 | ||||
|
360 | (should be the same as auto) | |||
|
361 | ||||
|
362 | $ cp -a force-base append-mostly-force-rewrite | |||
|
363 | $ cd append-mostly-force-rewrite | |||
|
364 | $ current_uid=$(find_dirstate_uuid) | |||
|
365 | ||||
|
366 | Change mtime of everything on disk causing a full rewrite | |||
|
367 | ||||
|
368 | $ touch -t 202212010005 `hg files` | |||
|
369 | $ hg st \ | |||
|
370 | > --config rhg.on-unsupported=abort \ | |||
|
371 | > --config devel.dirstate.v2.data_update_mode=force-new | |||
|
372 | ||||
|
373 | UUID has changed and a zero number unused bytes means we've rewritten. | |||
|
374 | ||||
|
375 | ||||
|
376 | #if no-rust no-rhg | |||
|
377 | $ dirstate_uuid_has_not_changed | |||
|
378 | not testing because using Python implementation | |||
|
379 | #else | |||
|
380 | $ dirstate_uuid_has_not_changed | |||
|
381 | [1] | |||
|
382 | #endif | |||
|
383 | $ hg debugstate --docket | grep unused | |||
|
384 | number of unused bytes: 0 | |||
|
385 | $ cd .. | |||
|
386 | ||||
|
387 | ||||
|
388 | Check the same scenario with the option set to "force-append" | |||
|
389 | ------------------------------------------------------------- | |||
|
390 | ||||
|
391 | Should append even if "auto" did not | |||
|
392 | ||||
|
393 | $ cp -a force-base append-mostly-force-append | |||
|
394 | $ cd append-mostly-force-append | |||
|
395 | $ current_uid=$(find_dirstate_uuid) | |||
|
396 | ||||
|
397 | Change mtime of everything on disk causing a full rewrite | |||
|
398 | ||||
|
399 | $ touch -t 202212010005 `hg files` | |||
|
400 | $ hg st \ | |||
|
401 | > --config rhg.on-unsupported=abort \ | |||
|
402 | > --config devel.dirstate.v2.data_update_mode=force-append | |||
|
403 | ||||
|
404 | UUID has not changed and some unused bytes exist in the data file | |||
|
405 | ||||
|
406 | $ dirstate_uuid_has_not_changed | |||
|
407 | not testing because using Python implementation (no-rust no-rhg !) | |||
|
408 | ||||
|
409 | #if no-rust no-rhg | |||
|
410 | The pure python implementation is never appending at the time this is written. | |||
|
411 | $ hg debugstate --docket | grep unused | |||
|
412 | number of unused bytes: 0 (known-bad-output !) | |||
|
413 | #else | |||
|
414 | $ hg debugstate --docket | grep unused | |||
|
415 | number of unused bytes: [1-9]\d* (re) | |||
|
416 | #endif | |||
|
417 | $ cd .. | |||
|
418 | ||||
|
419 | ||||
|
420 | ||||
|
421 | Get back into a state suitable for the test of the file. | |||
|
422 | ||||
|
423 | $ cd ./append-mostly | |||
|
424 | ||||
|
425 | #else | |||
|
426 | $ cd ./u | |||
|
427 | #endif | |||
|
428 | ||||
214 | Transaction compatibility |
|
429 | Transaction compatibility | |
215 | ------------------------- |
|
430 | ========================= | |
216 |
|
431 | |||
217 | The transaction preserves the dirstate. |
|
432 | The transaction preserves the dirstate. | |
218 | We should make sure all of it (docket + data) is preserved |
|
433 | We should make sure all of it (docket + data) is preserved |
General Comments 0
You need to be logged in to leave comments.
Login now