Show More
@@ -0,0 +1,18 b'' | |||||
|
1 | --- | |||
|
2 | name: Official Review | |||
|
3 | about: Submit a series for review | |||
|
4 | --- | |||
|
5 | ||||
|
6 | /assign_reviewer @mercurial.review | |||
|
7 | ||||
|
8 | Welcome to the Mercurial Merge Request creation process: | |||
|
9 | ||||
|
10 | * Set a simple title for your MR, | |||
|
11 | * All important information should be contained in your changesets' content or description, | |||
|
12 | * You can add some workflow-relevant information here (eg: when this depends on another MR), | |||
|
13 | * If your changes are not ready for review yet, click `Start the title with Draft:` under the title. | |||
|
14 | ||||
|
15 | More details here: | |||
|
16 | ||||
|
17 | * https://www.mercurial-scm.org/wiki/ContributingChanges | |||
|
18 | * https://www.mercurial-scm.org/wiki/Heptapod |
@@ -0,0 +1,23 b'' | |||||
|
1 | #!/bin/bash | |||
|
2 | # | |||
|
3 | # Make sure to patch mercurial to create the delta against nullrev | |||
|
4 | # if deltainfo is None: | |||
|
5 | #- deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev) | |||
|
6 | #+ deltainfo = self._builddeltainfo(revinfo, nullrev, fh) | |||
|
7 | ||||
|
8 | cd "`dirname \"$0\"`" | |||
|
9 | export HGRCPATH= | |||
|
10 | export HGMODULEPOLICY=py | |||
|
11 | ||||
|
12 | rm -rf nullrev-diff | |||
|
13 | ../../hg init nullrev-diff --config format.revlog-compression=zlib | |||
|
14 | cd nullrev-diff | |||
|
15 | echo hi > a | |||
|
16 | ../../../hg commit -Am root-B | |||
|
17 | ../../../hg debugdeltachain a | |||
|
18 | rm -rf .hg/cache/ .hg/wcache/ | |||
|
19 | cd .. | |||
|
20 | ||||
|
21 | tar cf test-revlog-diff-relative-to-nullrev.tar nullrev-diff | |||
|
22 | ||||
|
23 | rm -rf nullrev-diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
@@ -76,7 +76,7 b" if __name__ == '__main__':" | |||||
76 | # specified. When running as test-check-py3-compat.t, we technically |
|
76 | # specified. When running as test-check-py3-compat.t, we technically | |
77 | # would import the correct paths, but it's cleaner to have both cases |
|
77 | # would import the correct paths, but it's cleaner to have both cases | |
78 | # use the same import logic. |
|
78 | # use the same import logic. | |
79 |
sys.path.insert(0, |
|
79 | sys.path.insert(0, os.getcwd()) | |
80 |
|
80 | |||
81 | for f in sys.argv[1:]: |
|
81 | for f in sys.argv[1:]: | |
82 | with warnings.catch_warnings(record=True) as warns: |
|
82 | with warnings.catch_warnings(record=True) as warns: |
@@ -9,7 +9,7 b' import sys' | |||||
9 |
|
9 | |||
10 | # import from the live mercurial repo |
|
10 | # import from the live mercurial repo | |
11 | os.environ['HGMODULEPOLICY'] = 'py' |
|
11 | os.environ['HGMODULEPOLICY'] = 'py' | |
12 | sys.path.insert(0, "..") |
|
12 | sys.path.insert(0, os.path.abspath("..")) | |
13 | from mercurial import demandimport |
|
13 | from mercurial import demandimport | |
14 |
|
14 | |||
15 | demandimport.enable() |
|
15 | demandimport.enable() |
@@ -68,6 +68,52 b' def ismainthread():' | |||||
68 | return threading.current_thread() == threading.main_thread() |
|
68 | return threading.current_thread() == threading.main_thread() | |
69 |
|
69 | |||
70 |
|
70 | |||
|
71 | class _blockingreader(object): | |||
|
72 | def __init__(self, wrapped): | |||
|
73 | self._wrapped = wrapped | |||
|
74 | ||||
|
75 | # Do NOT implement readinto() by making it delegate to | |||
|
76 | # _wrapped.readinto(), since that is unbuffered. The unpickler is fine | |||
|
77 | # with just read() and readline(), so we don't need to implement it. | |||
|
78 | ||||
|
79 | if (3, 8, 0) <= sys.version_info[:3] < (3, 8, 2): | |||
|
80 | ||||
|
81 | # This is required for python 3.8, prior to 3.8.2. See issue6444. | |||
|
82 | def readinto(self, b): | |||
|
83 | pos = 0 | |||
|
84 | size = len(b) | |||
|
85 | ||||
|
86 | while pos < size: | |||
|
87 | ret = self._wrapped.readinto(b[pos:]) | |||
|
88 | if not ret: | |||
|
89 | break | |||
|
90 | pos += ret | |||
|
91 | ||||
|
92 | return pos | |||
|
93 | ||||
|
94 | def readline(self): | |||
|
95 | return self._wrapped.readline() | |||
|
96 | ||||
|
97 | # issue multiple reads until size is fulfilled | |||
|
98 | def read(self, size=-1): | |||
|
99 | if size < 0: | |||
|
100 | return self._wrapped.readall() | |||
|
101 | ||||
|
102 | buf = bytearray(size) | |||
|
103 | view = memoryview(buf) | |||
|
104 | pos = 0 | |||
|
105 | ||||
|
106 | while pos < size: | |||
|
107 | ret = self._wrapped.readinto(view[pos:]) | |||
|
108 | if not ret: | |||
|
109 | break | |||
|
110 | pos += ret | |||
|
111 | ||||
|
112 | del view | |||
|
113 | del buf[pos:] | |||
|
114 | return bytes(buf) | |||
|
115 | ||||
|
116 | ||||
71 | class _blockingreader: |
|
117 | class _blockingreader: | |
72 | def __init__(self, wrapped): |
|
118 | def __init__(self, wrapped): | |
73 | self._wrapped = wrapped |
|
119 | self._wrapped = wrapped |
@@ -456,29 +456,38 b' impl Repo {' | |||||
456 | let data_filename = format!("dirstate.{}", uuid); |
|
456 | let data_filename = format!("dirstate.{}", uuid); | |
457 | let data_filename = self.hg_vfs().join(data_filename); |
|
457 | let data_filename = self.hg_vfs().join(data_filename); | |
458 | let mut options = std::fs::OpenOptions::new(); |
|
458 | let mut options = std::fs::OpenOptions::new(); | |
459 | if append { |
|
459 | options.write(true); | |
460 | options.append(true); |
|
460 | ||
461 | } else { |
|
461 | // Why are we not using the O_APPEND flag when appending? | |
462 | options.write(true).create_new(true); |
|
462 | // | |
|
463 | // - O_APPEND makes it trickier to deal with garbage at the end of | |||
|
464 | // the file, left by a previous uncommitted transaction. By | |||
|
465 | // starting the write at [old_data_size] we make sure we erase | |||
|
466 | // all such garbage. | |||
|
467 | // | |||
|
468 | // - O_APPEND requires to special-case 0-byte writes, whereas we | |||
|
469 | // don't need that. | |||
|
470 | // | |||
|
471 | // - Some OSes have bugs in implementation O_APPEND: | |||
|
472 | // revlog.py talks about a Solaris bug, but we also saw some ZFS | |||
|
473 | // bug: https://github.com/openzfs/zfs/pull/3124, | |||
|
474 | // https://github.com/openzfs/zfs/issues/13370 | |||
|
475 | // | |||
|
476 | if !append { | |||
|
477 | options.create_new(true); | |||
463 | } |
|
478 | } | |
|
479 | ||||
464 | let data_size = (|| { |
|
480 | let data_size = (|| { | |
465 | // TODO: loop and try another random ID if !append and this |
|
481 | // TODO: loop and try another random ID if !append and this | |
466 | // returns `ErrorKind::AlreadyExists`? Collision chance of two |
|
482 | // returns `ErrorKind::AlreadyExists`? Collision chance of two | |
467 | // random IDs is one in 2**32 |
|
483 | // random IDs is one in 2**32 | |
468 | let mut file = options.open(&data_filename)?; |
|
484 | let mut file = options.open(&data_filename)?; | |
469 |
if |
|
485 | if append { | |
470 | // If we're not appending anything, the data size is the |
|
486 | file.seek(SeekFrom::Start(old_data_size as u64))?; | |
471 | // same as in the previous docket. It is *not* the file |
|
487 | } | |
472 | // length, since it could have garbage at the end. |
|
|||
473 | // We don't have to worry about it when we do have data |
|
|||
474 | // to append since we rewrite the root node in this case. |
|
|||
475 | Ok(old_data_size as u64) |
|
|||
476 | } else { |
|
|||
477 |
|
|
488 | file.write_all(&data)?; | |
478 |
|
|
489 | file.flush()?; | |
479 | // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+ |
|
|||
480 |
|
|
490 | file.seek(SeekFrom::Current(0)) | |
481 | } |
|
|||
482 | })() |
|
491 | })() | |
483 | .when_writing_file(&data_filename)?; |
|
492 | .when_writing_file(&data_filename)?; | |
484 |
|
493 |
@@ -32,6 +32,8 b' const REVIDX_KNOWN_FLAGS: u16 = REVISION' | |||||
32 | | REVISION_FLAG_EXTSTORED |
|
32 | | REVISION_FLAG_EXTSTORED | |
33 | | REVISION_FLAG_HASCOPIESINFO; |
|
33 | | REVISION_FLAG_HASCOPIESINFO; | |
34 |
|
34 | |||
|
35 | const NULL_REVLOG_ENTRY_FLAGS: u16 = 0; | |||
|
36 | ||||
35 | #[derive(Debug, derive_more::From)] |
|
37 | #[derive(Debug, derive_more::From)] | |
36 | pub enum RevlogError { |
|
38 | pub enum RevlogError { | |
37 | InvalidRevision, |
|
39 | InvalidRevision, | |
@@ -265,11 +267,29 b' impl Revlog {' | |||||
265 | } |
|
267 | } | |
266 | } |
|
268 | } | |
267 |
|
269 | |||
|
270 | pub fn make_null_entry(&self) -> RevlogEntry { | |||
|
271 | RevlogEntry { | |||
|
272 | revlog: self, | |||
|
273 | rev: NULL_REVISION, | |||
|
274 | bytes: b"", | |||
|
275 | compressed_len: 0, | |||
|
276 | uncompressed_len: 0, | |||
|
277 | base_rev_or_base_of_delta_chain: None, | |||
|
278 | p1: NULL_REVISION, | |||
|
279 | p2: NULL_REVISION, | |||
|
280 | flags: NULL_REVLOG_ENTRY_FLAGS, | |||
|
281 | hash: NULL_NODE, | |||
|
282 | } | |||
|
283 | } | |||
|
284 | ||||
268 | /// Get an entry of the revlog. |
|
285 | /// Get an entry of the revlog. | |
269 | pub fn get_entry( |
|
286 | pub fn get_entry( | |
270 | &self, |
|
287 | &self, | |
271 | rev: Revision, |
|
288 | rev: Revision, | |
272 | ) -> Result<RevlogEntry, RevlogError> { |
|
289 | ) -> Result<RevlogEntry, RevlogError> { | |
|
290 | if rev == NULL_REVISION { | |||
|
291 | return Ok(self.make_null_entry()); | |||
|
292 | } | |||
273 | let index_entry = self |
|
293 | let index_entry = self | |
274 | .index |
|
294 | .index | |
275 | .get_entry(rev) |
|
295 | .get_entry(rev) |
@@ -2505,9 +2505,11 b' class TestSuite(unittest.TestSuite):' | |||||
2505 | done = queue.Queue() |
|
2505 | done = queue.Queue() | |
2506 | running = 0 |
|
2506 | running = 0 | |
2507 |
|
2507 | |||
|
2508 | channels_lock = threading.Lock() | |||
2508 | channels = [""] * self._jobs |
|
2509 | channels = [""] * self._jobs | |
2509 |
|
2510 | |||
2510 | def job(test, result): |
|
2511 | def job(test, result): | |
|
2512 | with channels_lock: | |||
2511 | for n, v in enumerate(channels): |
|
2513 | for n, v in enumerate(channels): | |
2512 | if not v: |
|
2514 | if not v: | |
2513 | channel = n |
|
2515 | channel = n | |
@@ -2515,19 +2517,21 b' class TestSuite(unittest.TestSuite):' | |||||
2515 | else: |
|
2517 | else: | |
2516 | raise ValueError('Could not find output channel') |
|
2518 | raise ValueError('Could not find output channel') | |
2517 | channels[channel] = "=" + test.name[5:].split(".")[0] |
|
2519 | channels[channel] = "=" + test.name[5:].split(".")[0] | |
|
2520 | ||||
|
2521 | r = None | |||
2518 | try: |
|
2522 | try: | |
2519 | test(result) |
|
2523 | test(result) | |
2520 | done.put(None) |
|
|||
2521 | except KeyboardInterrupt: |
|
2524 | except KeyboardInterrupt: | |
2522 | pass |
|
2525 | pass | |
2523 | except: # re-raises |
|
2526 | except: # re-raises | |
2524 |
|
|
2527 | r = ('!', test, 'run-test raised an error, see traceback') | |
2525 | raise |
|
2528 | raise | |
2526 | finally: |
|
2529 | finally: | |
2527 | try: |
|
2530 | try: | |
2528 | channels[channel] = '' |
|
2531 | channels[channel] = '' | |
2529 | except IndexError: |
|
2532 | except IndexError: | |
2530 | pass |
|
2533 | pass | |
|
2534 | done.put(r) | |||
2531 |
|
2535 | |||
2532 | def stat(): |
|
2536 | def stat(): | |
2533 | count = 0 |
|
2537 | count = 0 |
@@ -131,6 +131,10 b' valid.' | |||||
131 | > hg debugstate --docket | grep uuid | sed 's/.*uuid: \(.*\)/\1/' |
|
131 | > hg debugstate --docket | grep uuid | sed 's/.*uuid: \(.*\)/\1/' | |
132 | > } |
|
132 | > } | |
133 |
|
133 | |||
|
134 | $ find_dirstate_data_size () { | |||
|
135 | > hg debugstate --docket | grep 'size of dirstate data' | sed 's/.*size of dirstate data: \(.*\)/\1/' | |||
|
136 | > } | |||
|
137 | ||||
134 | $ dirstate_uuid_has_not_changed () { |
|
138 | $ dirstate_uuid_has_not_changed () { | |
135 | > # Non-Rust always rewrites the whole dirstate |
|
139 | > # Non-Rust always rewrites the whole dirstate | |
136 | > if [ $# -eq 1 ] || ([ -n "$HGMODULEPOLICY" ] && [ -z "${HGMODULEPOLICY##*rust*}" ]) || [ -n "$RHG_INSTALLED_AS_HG" ]; then |
|
140 | > if [ $# -eq 1 ] || ([ -n "$HGMODULEPOLICY" ] && [ -z "${HGMODULEPOLICY##*rust*}" ]) || [ -n "$RHG_INSTALLED_AS_HG" ]; then | |
@@ -161,13 +165,19 b' Nothing changes here' | |||||
161 |
|
165 | |||
162 | Trigger an append with a small change |
|
166 | Trigger an append with a small change | |
163 |
|
167 | |||
164 | $ echo "modified" > dir2/f |
|
168 | $ current_data_size=$(find_dirstate_data_size) | |
|
169 | $ rm dir2/f | |||
165 | $ hg st |
|
170 | $ hg st | |
166 |
|
|
171 | ! dir2/f | |
167 | $ dirstate_data_files | wc -l |
|
172 | $ dirstate_data_files | wc -l | |
168 | *1 (re) |
|
173 | *1 (re) | |
169 | $ dirstate_uuid_has_not_changed |
|
174 | $ dirstate_uuid_has_not_changed | |
170 | not testing because using Python implementation (no-rust no-rhg !) |
|
175 | not testing because using Python implementation (no-rust no-rhg !) | |
|
176 | $ new_data_size=$(find_dirstate_data_size) | |||
|
177 | $ [ "$current_data_size" -eq "$new_data_size" ]; echo $? | |||
|
178 | 0 (no-rust no-rhg !) | |||
|
179 | 1 (rust !) | |||
|
180 | 1 (no-rust rhg !) | |||
171 |
|
181 | |||
172 | Unused bytes counter is non-0 when appending |
|
182 | Unused bytes counter is non-0 when appending | |
173 | $ touch file |
|
183 | $ touch file | |
@@ -176,8 +186,8 b' Unused bytes counter is non-0 when appen' | |||||
176 |
|
186 | |||
177 | Trigger a rust/rhg run which updates the unused bytes value |
|
187 | Trigger a rust/rhg run which updates the unused bytes value | |
178 | $ hg st |
|
188 | $ hg st | |
179 | M dir2/f |
|
|||
180 | A file |
|
189 | A file | |
|
190 | ! dir2/f | |||
181 | $ dirstate_data_files | wc -l |
|
191 | $ dirstate_data_files | wc -l | |
182 | *1 (re) |
|
192 | *1 (re) | |
183 | $ dirstate_uuid_has_not_changed |
|
193 | $ dirstate_uuid_has_not_changed |
@@ -32,6 +32,7 b' Unknown version is rejected' | |||||
32 |
|
32 | |||
33 | Test for CVE-2016-3630 |
|
33 | Test for CVE-2016-3630 | |
34 |
|
34 | |||
|
35 | $ mkdir test2; cd test2 | |||
35 | $ hg init |
|
36 | $ hg init | |
36 |
|
37 | |||
37 | >>> import codecs |
|
38 | >>> import codecs | |
@@ -52,3 +53,31 b' Test for CVE-2016-3630' | |||||
52 | >>> rl = revlog.revlog(tvfs, target=(KIND_OTHER, b'test'), radix=b'a') |
|
53 | >>> rl = revlog.revlog(tvfs, target=(KIND_OTHER, b'test'), radix=b'a') | |
53 | >>> rl.revision(1) |
|
54 | >>> rl.revision(1) | |
54 | mpatchError(*'patch cannot be decoded'*) (glob) |
|
55 | mpatchError(*'patch cannot be decoded'*) (glob) | |
|
56 | ||||
|
57 | $ cd .. | |||
|
58 | ||||
|
59 | ||||
|
60 | Regression test for support for the old repos with strange diff encoding. | |||
|
61 | Apparently it used to be possible (maybe it's still possible, but we don't know how) | |||
|
62 | to create commits whose diffs are encoded relative to a nullrev. | |||
|
63 | This test checks that a repo with that encoding can still be read. | |||
|
64 | ||||
|
65 | This is what we did to produce the repo in test-revlog-diff-relative-to-nullrev.tar: | |||
|
66 | ||||
|
67 | - tweak the code in mercurial/revlogutils/deltas.py to produce such "trivial" deltas: | |||
|
68 | > if deltainfo is None: | |||
|
69 | > - deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev) | |||
|
70 | > + deltainfo = self._builddeltainfo(revinfo, nullrev, fh) | |||
|
71 | - hg init | |||
|
72 | - echo hi > a | |||
|
73 | - hg commit -Am_ | |||
|
74 | - remove some cache files | |||
|
75 | ||||
|
76 | $ tar --force-local -xf "$TESTDIR"/bundles/test-revlog-diff-relative-to-nullrev.tar | |||
|
77 | $ cd nullrev-diff | |||
|
78 | $ hg debugdeltachain a | |||
|
79 | rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio readsize largestblk rddensity srchunks | |||
|
80 | 0 1 2 -1 p1 15 3 15 5.00000 15 0 0.00000 15 15 1.00000 1 | |||
|
81 | $ hg cat --config rhg.cat=true -r 0 a | |||
|
82 | hi | |||
|
83 | $ cd .. |
General Comments 0
You need to be logged in to leave comments.
Login now