##// END OF EJS Templates
branching: merge stable into default
Raphaël Gomès -
r51256:64cdd80d merge default
parent child Browse files
Show More
@@ -240,3 +240,4 b' c890d8b8bc59b18e5febf60caada629df5356ee2'
240 240 59466b13a3ae0e29a5d4f485393e516cfbb057d0 0 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
@@ -256,3 +256,4 b' c890d8b8bc59b18e5febf60caada629df5356ee2'
256 256 59466b13a3ae0e29a5d4f485393e516cfbb057d0 6.3.2
257 257 8830004967ad865ead89c28a410405a6e71e0796 6.3.3
258 258 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 6.4rc0
259 f14864fffdcab725d9eac6d4f4c07be05a35f59a 6.4
@@ -481,7 +481,7 b' class changelog(revlog.revlog):'
481 481 self._delaybuf = None
482 482 self._divert = False
483 483 # split when we're done
484 self._enforceinlinesize(tr)
484 self._enforceinlinesize(tr, side_write=False)
485 485
486 486 def _writepending(self, tr):
487 487 """create a file containing the unfinalized state for
@@ -512,9 +512,9 b' class changelog(revlog.revlog):'
512 512
513 513 return False
514 514
515 def _enforceinlinesize(self, tr):
515 def _enforceinlinesize(self, tr, side_write=True):
516 516 if not self._delayed:
517 revlog.revlog._enforceinlinesize(self, tr)
517 revlog.revlog._enforceinlinesize(self, tr, side_write=side_write)
518 518
519 519 def read(self, nodeorrev):
520 520 """Obtain data from a parsed changelog revision.
@@ -803,11 +803,12 b' def debugdeltachain(ui, repo, file_=None'
803 803 # security to avoid crash on corrupted revlogs
804 804 total_revs = len(index)
805 805
806 chain_size_cache = {}
807
806 808 def revinfo(rev):
807 809 e = index[rev]
808 810 compsize = e[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH]
809 811 uncompsize = e[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH]
810 chainsize = 0
811 812
812 813 base = e[revlog_constants.ENTRY_DELTA_BASE]
813 814 p1 = e[revlog_constants.ENTRY_PARENT_1]
@@ -870,11 +871,17 b' def debugdeltachain(ui, repo, file_=None'
870 871 deltatype = b'prev'
871 872
872 873 chain = r._deltachain(rev)[0]
873 for iterrev in chain:
874 e = index[iterrev]
875 chainsize += e[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH]
876
877 return p1, p2, compsize, uncompsize, deltatype, chain, chainsize
874 chain_size = 0
875 for iter_rev in reversed(chain):
876 cached = chain_size_cache.get(iter_rev)
877 if cached is not None:
878 chain_size += cached
879 break
880 e = index[iter_rev]
881 chain_size += e[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH]
882 chain_size_cache[rev] = chain_size
883
884 return p1, p2, compsize, uncompsize, deltatype, chain, chain_size
878 885
879 886 fm = ui.formatter(b'debugdeltachain', opts)
880 887
@@ -25,7 +25,7 b' from .revlogutils import ('
25 25
26 26 @interfaceutil.implementer(repository.ifilestorage)
27 27 class filelog:
28 def __init__(self, opener, path):
28 def __init__(self, opener, path, try_split=False):
29 29 self._revlog = revlog.revlog(
30 30 opener,
31 31 # XXX should use the unencoded path
@@ -33,6 +33,7 b' class filelog:'
33 33 radix=b'/'.join((b'data', path)),
34 34 censorable=True,
35 35 canonical_parent_order=False, # see comment in revlog.py
36 try_split=try_split,
36 37 )
37 38 # Full name of the user visible file, relative to the repository root.
38 39 # Used by LFS.
@@ -256,8 +257,8 b' class filelog:'
256 257 class narrowfilelog(filelog):
257 258 """Filelog variation to be used with narrow stores."""
258 259
259 def __init__(self, opener, path, narrowmatch):
260 super(narrowfilelog, self).__init__(opener, path)
260 def __init__(self, opener, path, narrowmatch, try_split=False):
261 super(narrowfilelog, self).__init__(opener, path, try_split=try_split)
261 262 self._narrowmatch = narrowmatch
262 263
263 264 def renamed(self, node):
@@ -191,6 +191,11 b' def _exthook(ui, repo, htype, name, cmd,'
191 191 cwd = encoding.getcwd()
192 192 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag=b'exthook-%s' % (name,))
193 193
194 if repo is not None and repo.currentwlock() is None:
195 repo.invalidatedirstate()
196 if repo is not None and repo.currentlock() is None:
197 repo.invalidate()
198
194 199 duration = util.timer() - starttime
195 200 ui.log(
196 201 b'exthook',
@@ -1810,6 +1810,9 b' class ilocalrepositorymain(interfaceutil'
1810 1810 def lock(wait=True):
1811 1811 """Lock the repository store and return a lock instance."""
1812 1812
1813 def currentlock():
1814 """Return the lock if it's held or None."""
1815
1813 1816 def wlock(wait=True):
1814 1817 """Lock the non-store parts of the repository."""
1815 1818
@@ -1240,7 +1240,12 b' class revlogfilestorage:'
1240 1240 if path.startswith(b'/'):
1241 1241 path = path[1:]
1242 1242
1243 return filelog.filelog(self.svfs, path)
1243 try_split = (
1244 self.currenttransaction() is not None
1245 or txnutil.mayhavepending(self.root)
1246 )
1247
1248 return filelog.filelog(self.svfs, path, try_split=try_split)
1244 1249
1245 1250
1246 1251 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
@@ -1251,7 +1256,13 b' class revlognarrowfilestorage:'
1251 1256 if path.startswith(b'/'):
1252 1257 path = path[1:]
1253 1258
1254 return filelog.narrowfilelog(self.svfs, path, self._storenarrowmatch)
1259 try_split = (
1260 self.currenttransaction() is not None
1261 or txnutil.mayhavepending(self.root)
1262 )
1263 return filelog.narrowfilelog(
1264 self.svfs, path, self._storenarrowmatch, try_split=try_split
1265 )
1255 1266
1256 1267
1257 1268 def makefilestorage(requirements, features, **kwargs):
@@ -1794,10 +1805,29 b' class localrepository:'
1794 1805 )
1795 1806
1796 1807 def _dirstatevalidate(self, node):
1808 okay = True
1797 1809 try:
1798 1810 self.changelog.rev(node)
1811 except error.LookupError:
1812 # If the parent are unknown it might just be because the changelog
1813 # in memory is lagging behind the dirstate in memory. So try to
1814 # refresh the changelog first.
1815 #
1816 # We only do so if we don't hold the lock, if we do hold the lock
1817 # the invalidation at that time should have taken care of this and
1818 # something is very fishy.
1819 if self.currentlock() is None:
1820 self.invalidate()
1821 try:
1822 self.changelog.rev(node)
1823 except error.LookupError:
1824 okay = False
1825 else:
1826 # XXX we should consider raising an error here.
1827 okay = False
1828 if okay:
1799 1829 return node
1800 except error.LookupError:
1830 else:
1801 1831 if not self._dirstatevalidatewarned:
1802 1832 self._dirstatevalidatewarned = True
1803 1833 self.ui.warn(
@@ -3130,6 +3160,10 b' class localrepository:'
3130 3160 """Returns the wlock if it's held, or None if it's not."""
3131 3161 return self._currentlock(self._wlockref)
3132 3162
3163 def currentlock(self):
3164 """Returns the lock if it's held, or None if it's not."""
3165 return self._currentlock(self._lockref)
3166
3133 3167 def checkcommitpatterns(self, wctx, match, status, fail):
3134 3168 """check for commit arguments that aren't committable"""
3135 3169 if match.isexact() or match.prefix():
@@ -302,6 +302,7 b' class revlog:'
302 302 persistentnodemap=False,
303 303 concurrencychecker=None,
304 304 trypending=False,
305 try_split=False,
305 306 canonical_parent_order=True,
306 307 ):
307 308 """
@@ -328,6 +329,7 b' class revlog:'
328 329 self._nodemap_file = None
329 330 self.postfix = postfix
330 331 self._trypending = trypending
332 self._try_split = try_split
331 333 self.opener = opener
332 334 if persistentnodemap:
333 335 self._nodemap_file = nodemaputil.get_nodemap_file(self)
@@ -511,6 +513,8 b' class revlog:'
511 513 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
512 514 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
513 515 entry_point = b'%s.i.a' % self.radix
516 elif self._try_split and self.opener.exists(b'%s.i.s' % self.radix):
517 entry_point = b'%s.i.s' % self.radix
514 518 else:
515 519 entry_point = b'%s.i' % self.radix
516 520
@@ -2015,7 +2019,7 b' class revlog:'
2015 2019 raise error.CensoredNodeError(self.display_id, node, text)
2016 2020 raise
2017 2021
2018 def _enforceinlinesize(self, tr):
2022 def _enforceinlinesize(self, tr, side_write=True):
2019 2023 """Check if the revlog is too big for inline and convert if so.
2020 2024
2021 2025 This should be called after revisions are added to the revlog. If the
@@ -2032,7 +2036,8 b' class revlog:'
2032 2036 raise error.RevlogError(
2033 2037 _(b"%s not found in the transaction") % self._indexfile
2034 2038 )
2035 trindex = None
2039 if troffset:
2040 tr.addbackup(self._indexfile, for_offset=True)
2036 2041 tr.add(self._datafile, 0)
2037 2042
2038 2043 existing_handles = False
@@ -2048,6 +2053,29 b' class revlog:'
2048 2053 # No need to deal with sidedata writing handle as it is only
2049 2054 # relevant with revlog-v2 which is never inline, not reaching
2050 2055 # this code
2056 if side_write:
2057 old_index_file_path = self._indexfile
2058 new_index_file_path = self._indexfile + b'.s'
2059 opener = self.opener
2060
2061 fncache = getattr(opener, 'fncache', None)
2062 if fncache is not None:
2063 fncache.addignore(new_index_file_path)
2064
2065 # the "split" index replace the real index when the transaction is finalized
2066 def finalize_callback(tr):
2067 opener.rename(
2068 new_index_file_path,
2069 old_index_file_path,
2070 checkambig=True,
2071 )
2072
2073 tr.registertmp(new_index_file_path)
2074 if self.target[1] is not None:
2075 finalize_id = b'000-revlog-split-%d-%s' % self.target
2076 else:
2077 finalize_id = b'000-revlog-split-%d' % self.target[0]
2078 tr.addfinalize(finalize_id, finalize_callback)
2051 2079
2052 2080 new_dfh = self._datafp(b'w+')
2053 2081 new_dfh.truncate(0) # drop any potentially existing data
@@ -2055,17 +2083,10 b' class revlog:'
2055 2083 with self._indexfp() as read_ifh:
2056 2084 for r in self:
2057 2085 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2058 if (
2059 trindex is None
2060 and troffset
2061 <= self.start(r) + r * self.index.entry_size
2062 ):
2063 trindex = r
2064 2086 new_dfh.flush()
2065 2087
2066 if trindex is None:
2067 trindex = 0
2068
2088 if side_write:
2089 self._indexfile = new_index_file_path
2069 2090 with self.__index_new_fp() as fp:
2070 2091 self._format_flags &= ~FLAG_INLINE_DATA
2071 2092 self._inline = False
@@ -2079,16 +2100,9 b' class revlog:'
2079 2100 if self._docket is not None:
2080 2101 self._docket.index_end = fp.tell()
2081 2102
2082 # There is a small transactional race here. If the rename of
2083 # the index fails, we should remove the datafile. It is more
2084 # important to ensure that the data file is not truncated
2085 # when the index is replaced as otherwise data is lost.
2086 tr.replace(self._datafile, self.start(trindex))
2087
2088 # the temp file replace the real index when we exit the context
2089 # manager
2090
2091 tr.replace(self._indexfile, trindex * self.index.entry_size)
2103 # If we don't use side-write, the temp file replace the real
2104 # index when we exit the context manager
2105
2092 2106 nodemaputil.setup_persistent_nodemap(tr, self)
2093 2107 self._segmentfile = randomaccessfile.randomaccessfile(
2094 2108 self.opener,
@@ -403,7 +403,7 b" REVLOG_FILES_VOLATILE_EXT = (b'.n', b'.n"
403 403 # some exception to the above matching
404 404 #
405 405 # XXX This is currently not in use because of issue6542
406 EXCLUDED = re.compile(b'.*undo\.[^/]+\.(nd?|i)$')
406 EXCLUDED = re.compile(br'.*undo\.[^/]+\.(nd?|i)$')
407 407
408 408
409 409 def is_revlog(f, kind, st):
@@ -603,6 +603,7 b' class fncache:'
603 603 # hence the encodedir/decodedir dance
604 604 def __init__(self, vfs):
605 605 self.vfs = vfs
606 self._ignores = set()
606 607 self.entries = None
607 608 self._dirty = False
608 609 # set of new additions to fncache
@@ -687,7 +688,12 b' class fncache:'
687 688 self.entries = None
688 689 self.addls = set()
689 690
691 def addignore(self, fn):
692 self._ignores.add(fn)
693
690 694 def add(self, fn):
695 if fn in self._ignores:
696 return
691 697 if self.entries is None:
692 698 self._load()
693 699 if fn not in self.entries:
@@ -105,7 +105,48 b' def _playback('
105 105 unlink=True,
106 106 checkambigfiles=None,
107 107 ):
108 """rollback a transaction :
109 - truncate files that have been appended to
110 - restore file backups
111 - delete temporary files
112 """
113 backupfiles = []
114
115 def restore_one_backup(vfs, f, b, checkambig):
116 filepath = vfs.join(f)
117 backuppath = vfs.join(b)
118 try:
119 util.copyfile(backuppath, filepath, checkambig=checkambig)
120 backupfiles.append((vfs, b))
121 except IOError as exc:
122 e_msg = stringutil.forcebytestr(exc)
123 report(_(b"failed to recover %s (%s)\n") % (f, e_msg))
124 raise
125
126 # gather all backup files that impact the store
127 # (we need this to detect files that are both backed up and truncated)
128 store_backup = {}
129 for entry in backupentries:
130 location, file_path, backup_path, cache = entry
131 vfs = vfsmap[location]
132 is_store = vfs.join(b'') == opener.join(b'')
133 if is_store and file_path and backup_path:
134 store_backup[file_path] = entry
135 copy_done = set()
136
137 # truncate all file `f` to offset `o`
108 138 for f, o in sorted(dict(entries).items()):
139 # if we have a backup for `f`, we should restore it first and truncate
140 # the restored file
141 bck_entry = store_backup.get(f)
142 if bck_entry is not None:
143 location, file_path, backup_path, cache = bck_entry
144 checkambig = False
145 if checkambigfiles:
146 checkambig = (file_path, location) in checkambigfiles
147 restore_one_backup(opener, file_path, backup_path, checkambig)
148 copy_done.add(bck_entry)
149 # truncate the file to its pre-transaction size
109 150 if o or not unlink:
110 151 checkambig = checkambigfiles and (f, b'') in checkambigfiles
111 152 try:
@@ -124,45 +165,52 b' def _playback('
124 165 report(_(b"failed to truncate %s\n") % f)
125 166 raise
126 167 else:
168 # delete empty file
127 169 try:
128 170 opener.unlink(f)
129 171 except FileNotFoundError:
130 172 pass
131
132 backupfiles = []
133 for l, f, b, c in backupentries:
173 # restore backed up files and clean up temporary files
174 for entry in backupentries:
175 if entry in copy_done:
176 continue
177 l, f, b, c = entry
134 178 if l not in vfsmap and c:
135 179 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
136 180 vfs = vfsmap[l]
137 181 try:
182 checkambig = checkambigfiles and (f, l) in checkambigfiles
138 183 if f and b:
139 filepath = vfs.join(f)
140 backuppath = vfs.join(b)
141 checkambig = checkambigfiles and (f, l) in checkambigfiles
142 try:
143 util.copyfile(backuppath, filepath, checkambig=checkambig)
144 backupfiles.append(b)
145 except IOError as exc:
146 e_msg = stringutil.forcebytestr(exc)
147 report(_(b"failed to recover %s (%s)\n") % (f, e_msg))
184 restore_one_backup(vfs, f, b, checkambig)
148 185 else:
149 186 target = f or b
150 187 try:
151 188 vfs.unlink(target)
152 189 except FileNotFoundError:
190 # This is fine because
191 #
192 # either we are trying to delete the main file, and it is
193 # already deleted.
194 #
195 # or we are trying to delete a temporary file and it is
196 # already deleted.
197 #
198 # in both case, our target result (delete the file) is
199 # already achieved.
153 200 pass
154 201 except (IOError, OSError, error.Abort):
155 202 if not c:
156 203 raise
157 204
205 # cleanup transaction state file and the backups file
158 206 backuppath = b"%s.backupfiles" % journal
159 207 if opener.exists(backuppath):
160 208 opener.unlink(backuppath)
161 209 opener.unlink(journal)
162 210 try:
163 for f in backupfiles:
164 if opener.exists(f):
165 opener.unlink(f)
211 for vfs, f in backupfiles:
212 if vfs.exists(f):
213 vfs.unlink(f)
166 214 except (IOError, OSError, error.Abort):
167 215 # only pure backup file remains, it is sage to ignore any error
168 216 pass
@@ -331,7 +379,7 b' class transaction(util.transactional):'
331 379 self._file.flush()
332 380
333 381 @active
334 def addbackup(self, file, hardlink=True, location=b''):
382 def addbackup(self, file, hardlink=True, location=b'', for_offset=False):
335 383 """Adds a backup of the file to the transaction
336 384
337 385 Calling addbackup() creates a hardlink backup of the specified file
@@ -340,17 +388,25 b' class transaction(util.transactional):'
340 388
341 389 * `file`: the file path, relative to .hg/store
342 390 * `hardlink`: use a hardlink to quickly create the backup
391
392 If `for_offset` is set, we expect a offset for this file to have been previously recorded
343 393 """
344 394 if self._queue:
345 395 msg = b'cannot use transaction.addbackup inside "group"'
346 396 raise error.ProgrammingError(msg)
347 397
348 if (
349 file in self._newfiles
350 or file in self._offsetmap
351 or file in self._backupmap
352 ):
398 if file in self._newfiles or file in self._backupmap:
399 return
400 elif file in self._offsetmap and not for_offset:
353 401 return
402 elif for_offset and file not in self._offsetmap:
403 msg = (
404 'calling `addbackup` with `for_offmap=True`, '
405 'but no offset recorded: [%r] %r'
406 )
407 msg %= (location, file)
408 raise error.ProgrammingError(msg)
409
354 410 vfs = self._vfsmap[location]
355 411 dirname, filename = vfs.split(file)
356 412 backupfilename = b"%s.backup.%s" % (self._journal, filename)
@@ -327,7 +327,9 b' if has_https:'
327 327 self.cert_file = cert_file
328 328
329 329 def connect(self):
330 self.sock = socket.create_connection((self.host, self.port))
330 self.sock = socket.create_connection(
331 (self.host, self.port), self.timeout
332 )
331 333
332 334 host = self.host
333 335 realhostport = self.realhostport # pytype: disable=attribute-error
@@ -1,4 +1,4 b''
1 = Mercurial 6.4rc0 =
1 = Mercurial 6.4 =
2 2
3 3 == New Features ==
4 4
@@ -90,6 +90,31 b' Aside from the following (unordered) com'
90 90 * bundlerepo: apply phase data stored in the bundle instead of assuming `draft`
91 91 * config-item: declare undeclared path suboption
92 92 * narrow: read pending file when applicable
93 * rust: fix building on macOS (issue6801)
94 * run-tests: fix a crash when using the coverage options
95 * undo-files: also remove the undo.backupfiles
96 * undo-files: cleanup backup when cleaning undos
97 * undo-files: clean existing files up before writing new one
98 * undo-files: cleanup legacy files when applicable
99 * dirstate-v2: fix an incorrect handling of readdir errors
100 * rust: update zstd dependency
101 * rust: upgrade `rayon` dependency
102 * dirstate: fix the bug in [status] dealing with committed&ignored directories
103 * dirstate: fix a potential traceback when in `copy` and `rename`
104 * histedit: fix diff colors
105 * cext: fix for PyLong refactoring in CPython 3.12
106 * py3: fix for Python 3.12 emitting SyntaxWarning on invalid escape sequences
107 * statprof: with Python 3.12, lineno is (more) often None
108 * transaction: properly clean up backup file outside of .hg/store/
109 * transaction: raise on backup restoration error
110 * revlog: improve the robustness of the splitting process
111 * debugdeltachain: stop summing the same chain over and over
112 * url: don't ignore timeout for https connections
113 * py3: fix for Python 3.12 emitting SyntaxWarning on invalid escape sequences
114 * tests: accept a test output change in [tests/test-serve.t]
115 * rust: fix thread cap (for real this time)
116 * dirstate: try refreshing the changelog when parent are unknown
117 * hooks: invalidate the repo after the hooks
93 118
94 119 == Backwards Compatibility Changes ==
95 120 * rust: upgrade supported Rust toolchain version
@@ -47,16 +47,10 b" pub fn status<'dirstate>("
47 47 options: StatusOptions,
48 48 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
49 49 {
50 // 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 // threads makes `status` slower for each additional thread.
53 // We use `ok()` in case the global threadpool has already been
54 // instantiated in `rhg` or some other caller.
55 // TODO find the underlying cause and fix it, then remove this.
56 rayon::ThreadPoolBuilder::new()
57 .num_threads(16.min(rayon::current_num_threads()))
58 .build_global()
59 .ok();
50 // Also cap for a Python caller of this function, but don't complain if
51 // the global threadpool has already been set since this code path is also
52 // being used by `rhg`, which calls this early.
53 let _ = crate::utils::cap_default_rayon_threads();
60 54
61 55 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 56 if options.list_ignored || options.list_unknown {
@@ -498,3 +498,35 b' where'
498 498 Err(e) => Some(Err(e)),
499 499 })
500 500 }
501
502 /// Force the global rayon threadpool to not exceed 16 concurrent threads
503 /// unless the user has specified a value.
504 /// This is a stop-gap measure until we figure out why using more than 16
505 /// threads makes `status` slower for each additional thread.
506 ///
507 /// TODO find the underlying cause and fix it, then remove this.
508 ///
509 /// # Errors
510 ///
511 /// Returns an error if the global threadpool has already been initialized if
512 /// we try to initialize it.
513 pub fn cap_default_rayon_threads() -> Result<(), rayon::ThreadPoolBuildError> {
514 const THREAD_CAP: usize = 16;
515
516 if std::env::var("RAYON_NUM_THREADS").is_err() {
517 let available_parallelism = std::thread::available_parallelism()
518 .map(usize::from)
519 .unwrap_or(1);
520 let new_thread_count = THREAD_CAP.min(available_parallelism);
521 let res = rayon::ThreadPoolBuilder::new()
522 .num_threads(new_thread_count)
523 .build_global();
524 if res.is_ok() {
525 log::trace!(
526 "Capped the rayon threadpool to {new_thread_count} threads",
527 );
528 }
529 return res;
530 }
531 Ok(())
532 }
@@ -140,6 +140,13 b' fn rhg_main(argv: Vec<OsString>) -> ! {'
140 140
141 141 env_logger::init();
142 142
143 // Make sure nothing in a future version of `rhg` sets the global
144 // threadpool before we can cap default threads. (This is also called
145 // in core because Python uses the same code path, we're adding a
146 // redundant check.)
147 hg::utils::cap_default_rayon_threads()
148 .expect("Rayon threadpool already initialized");
149
143 150 let early_args = EarlyArgs::parse(&argv);
144 151
145 152 let initial_current_dir = early_args.cwd.map(|cwd| {
@@ -249,7 +249,7 b' commit was created, and status is now cl'
249 249
250 250 The status process should return a consistent result and not crash.
251 251
252 #if rust no-rhg dirstate-v2-append
252 #if no-rhg
253 253 $ cat $TESTTMP/status-race-lock.out
254 254 A dir/o
255 255 R dir/nested/m
@@ -258,7 +258,7 b' The status process should return a consi'
258 258 ? q
259 259 $ cat $TESTTMP/status-race-lock.log
260 260 #else
261 #if rhg pre-some-read dirstate-v2-append
261 #if pre-some-read dirstate-v2-append
262 262 $ cat $TESTTMP/status-race-lock.out
263 263 A dir/o
264 264 R dir/nested/m
@@ -268,12 +268,10 b' The status process should return a consi'
268 268 $ cat $TESTTMP/status-race-lock.log
269 269 #else
270 270 $ cat $TESTTMP/status-race-lock.out
271 M dir/o (no-rhg known-bad-output !)
272 271 ? dir/n
273 272 ? p
274 273 ? q
275 274 $ cat $TESTTMP/status-race-lock.log
276 warning: ignoring unknown working parent 02a67a77ee9b! (no-rhg !)
277 275 #endif
278 276 #endif
279 277
@@ -1423,3 +1423,41 b' HGPLAIN setting in hooks'
1423 1423 ### no ######## plain: <unset>
1424 1424 ### auto ###### plain: 1
1425 1425 Mercurial Distributed SCM (*) (glob)
1426
1427 Test hook that change the underlying repo
1428 =========================================
1429
1430 blackbox access the dirstate afterward and can see a changelog / dirstate
1431 desync.
1432
1433
1434 $ cd $TESTTMP
1435 $ cat <<EOF >> $HGRCPATH
1436 > [extensions]
1437 > blackbox=
1438 > [hooks]
1439 > post-merge = hg commit -m "auto merge"
1440 > EOF
1441
1442 $ hg init t
1443 $ cd t
1444 $ touch ".hgignore"
1445 $ hg commit -Am "initial" -d'0 0'
1446 adding .hgignore
1447
1448 $ echo This is file a1 > a
1449 $ hg commit -Am "commit #1" -d'0 0'
1450 adding a
1451
1452 $ hg update 0
1453 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1454 $ echo This is file b1 > b
1455 $ hg commit -Am "commit #2" -d'0 0'
1456 adding b
1457 created new head
1458
1459 $ hg merge 1
1460 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1461 (branch merge, don't forget to commit)
1462
1463 $ cd ..
@@ -55,7 +55,7 b' With -v and -p daytime'
55 55 #if no-windows
56 56 $ KILLQUIETLY=Y
57 57 $ hgserve -p daytime
58 abort: cannot start server at 'localhost:13': Permission denied (?)
58 abort: cannot start server at 'localhost:13': $EACCES$ (?)
59 59 abort: child process failed to start (?)
60 60 abort: no port number associated with service 'daytime' (?)
61 61 listening at http://localhost/ (bound to $LOCALIP:13) (?)
@@ -1,22 +1,103 b''
1 1 Test correctness of revlog inline -> non-inline transition
2 2 ----------------------------------------------------------
3 3
4 Helper extension to intercept renames.
4 Helper extension to intercept renames and kill process
5 5
6 $ cat > $TESTTMP/intercept_rename.py << EOF
6 $ cat > $TESTTMP/intercept_before_rename.py << EOF
7 7 > import os
8 > import sys
8 > import signal
9 > from mercurial import extensions, util
10 >
11 > def extsetup(ui):
12 > def rename(orig, src, dest, *args, **kwargs):
13 > path = util.normpath(dest)
14 > if path.endswith(b'data/file.i'):
15 > os.kill(os.getpid(), signal.SIGKILL)
16 > return orig(src, dest, *args, **kwargs)
17 > extensions.wrapfunction(util, 'rename', rename)
18 > EOF
19
20 $ cat > $TESTTMP/intercept_after_rename.py << EOF
21 > import os
22 > import signal
9 23 > from mercurial import extensions, util
10 24 >
11 25 > def extsetup(ui):
12 26 > def close(orig, *args, **kwargs):
13 27 > path = util.normpath(args[0]._atomictempfile__name)
28 > r = orig(*args, **kwargs)
14 29 > if path.endswith(b'/.hg/store/data/file.i'):
15 > os._exit(80)
16 > return orig(*args, **kwargs)
30 > os.kill(os.getpid(), signal.SIGKILL)
31 > return r
17 32 > extensions.wrapfunction(util.atomictempfile, 'close', close)
33 > def extsetup(ui):
34 > def rename(orig, src, dest, *args, **kwargs):
35 > path = util.normpath(dest)
36 > r = orig(src, dest, *args, **kwargs)
37 > if path.endswith(b'data/file.i'):
38 > os.kill(os.getpid(), signal.SIGKILL)
39 > return r
40 > extensions.wrapfunction(util, 'rename', rename)
18 41 > EOF
19 42
43 $ cat > $TESTTMP/killme.py << EOF
44 > import os
45 > import signal
46 >
47 > def killme(ui, repo, hooktype, **kwargs):
48 > os.kill(os.getpid(), signal.SIGKILL)
49 > EOF
50
51 $ cat > $TESTTMP/reader_wait_split.py << EOF
52 > import os
53 > import signal
54 > from mercurial import extensions, revlog, testing
55 > def _wait_post_load(orig, self, *args, **kwargs):
56 > wait = b'data/file' in self.radix
57 > if wait:
58 > testing.wait_file(b"$TESTTMP/writer-revlog-split")
59 > r = orig(self, *args, **kwargs)
60 > if wait:
61 > testing.write_file(b"$TESTTMP/reader-index-read")
62 > testing.wait_file(b"$TESTTMP/writer-revlog-unsplit")
63 > return r
64 >
65 > def extsetup(ui):
66 > extensions.wrapfunction(revlog.revlog, '_loadindex', _wait_post_load)
67 > EOF
68
69 setup a repository for tests
70 ----------------------------
71
72 $ cat >> $HGRCPATH << EOF
73 > [format]
74 > revlog-compression=none
75 > EOF
76
77 $ hg init troffset-computation
78 $ cd troffset-computation
79 $ printf '%20d' '1' > file
80 $ hg commit -Aqma
81 $ printf '%1024d' '1' > file
82 $ hg commit -Aqmb
83 $ printf '%20d' '1' > file
84 $ hg commit -Aqmc
85 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
86 $ hg commit -AqmD --traceback
87
88 Reference size:
89 $ f -s file
90 file: size=131072
91 $ f -s .hg/store/data/file*
92 .hg/store/data/file.d: size=132139
93 .hg/store/data/file.i: size=256
94
95 $ cd ..
96
97
98 Test a hard crash after the file was split but before the transaction was committed
99 ===================================================================================
100
20 101 Test offset computation to correctly factor in the index entries themselves.
21 102 Also test that the new data size has the correct size if the transaction is aborted
22 103 after the index has been replaced.
@@ -28,30 +109,19 b' and will transition to non-inline storag'
28 109 If the transaction adding c, D is rolled back, then we don't undo the revlog split,
29 110 but truncate the index and the data to remove both c and D.
30 111
31 $ hg init troffset-computation --config format.revlog-compression=none
32 $ cd troffset-computation
33 $ printf '%20d' '1' > file
34 $ hg commit -Aqma
35 $ printf '%1024d' '1' > file
36 $ hg commit -Aqmb
37 $ printf '%20d' '1' > file
38 $ hg commit -Aqmc
39 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
40 $ hg commit -AqmD
41 112
42 $ cd ..
43
44 $ hg clone -r 1 troffset-computation troffset-computation-copy --config format.revlog-compression=none -q
113 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy
45 114 $ cd troffset-computation-copy
46 115
47 116 Reference size:
48
117 $ f -s file
118 file: size=1024
49 119 $ f -s .hg/store/data/file*
50 120 .hg/store/data/file.i: size=1174
51 121
52 122 $ cat > .hg/hgrc <<EOF
53 123 > [hooks]
54 > pretxnchangegroup = python:$TESTDIR/helper-killhook.py:killme
124 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
55 125 > EOF
56 126 #if chg
57 127 $ hg pull ../troffset-computation
@@ -60,27 +130,38 b' Reference size:'
60 130 #else
61 131 $ hg pull ../troffset-computation
62 132 pulling from ../troffset-computation
63 [80]
133 Killed
134 [137]
64 135 #endif
65 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
66 data/file.i 128
136
137
138 The inline revlog still exist, but a split version exist next to it
139
140 $ f -s .hg/store/data/file*
141 .hg/store/data/file.d: size=132139
142 .hg/store/data/file.i: size=132395
143 .hg/store/data/file.i.s: size=256
144
67 145
68 146 The first file.i entry should match the "Reference size" above.
69 147 The first file.d entry is the temporary record during the split,
70 the second entry after the split happened. The sum of the second file.d
71 and the second file.i entry should match the first file.i entry.
148
149 A "temporary file" entry exist for the split index.
72 150
73 151 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
74 152 data/file.i 1174
75 153 data/file.d 0
76 data/file.d 1046
77 data/file.i 128
154 $ cat .hg/store/journal.backupfiles | tr -s '\000' ' ' | tr -s '\00' ' '| grep data/file
155 data/file.i data/journal.backup.file.i 0
156 data/file.i.s 0
157
158 recover is rolling the split back, the fncache is still valid
159
78 160 $ hg recover
79 161 rolling back interrupted transaction
80 162 (verify step skipped, run `hg verify` to check your repository content)
81 163 $ f -s .hg/store/data/file*
82 .hg/store/data/file.d: size=1046
83 .hg/store/data/file.i: size=128
164 .hg/store/data/file.i: size=1174
84 165 $ hg tip
85 166 changeset: 1:cfa8d6e60429
86 167 tag: tip
@@ -89,47 +170,67 b' and the second file.i entry should match'
89 170 summary: b
90 171
91 172 $ hg verify -q
92 warning: revlog 'data/file.d' not in fncache!
93 1 warnings encountered!
94 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
95 173 $ hg debugrebuildfncache --only-data
96 adding data/file.d
97 1 items added, 0 removed from fncache
174 fncache already up to date
98 175 $ hg verify -q
99 176 $ cd ..
100 177
178 Test a hard crash right before the index is move into place
179 ===========================================================
101 180
102 181 Now retry the procedure but intercept the rename of the index and check that
103 182 the journal does not contain the new index size. This demonstrates the edge case
104 183 where the data file is left as garbage.
105 184
106 $ hg clone -r 1 troffset-computation troffset-computation-copy2 --config format.revlog-compression=none -q
185 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy2
107 186 $ cd troffset-computation-copy2
187
188 Reference size:
189 $ f -s file
190 file: size=1024
191 $ f -s .hg/store/data/file*
192 .hg/store/data/file.i: size=1174
193
108 194 $ cat > .hg/hgrc <<EOF
109 195 > [extensions]
110 > intercept_rename = $TESTTMP/intercept_rename.py
111 > [hooks]
112 > pretxnchangegroup = python:$TESTDIR/helper-killhook.py:killme
196 > intercept_rename = $TESTTMP/intercept_before_rename.py
113 197 > EOF
114 198 #if chg
115 199 $ hg pull ../troffset-computation
116 200 pulling from ../troffset-computation
201 searching for changes
202 adding changesets
203 adding manifests
204 adding file changes
117 205 [255]
118 206 #else
119 207 $ hg pull ../troffset-computation
120 208 pulling from ../troffset-computation
121 [80]
209 searching for changes
210 adding changesets
211 adding manifests
212 adding file changes
213 Killed
214 [137]
122 215 #endif
216
217 The inline revlog still exist, but a split version exist next to it
218
219 $ f -s .hg/store/data/file*
220 .hg/store/data/file.d: size=132139
221 .hg/store/data/file.i: size=132395
222 .hg/store/data/file.i.s: size=256
223
123 224 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
124 225 data/file.i 1174
125 226 data/file.d 0
126 data/file.d 1046
227
228 recover is rolling the split back, the fncache is still valid
127 229
128 230 $ hg recover
129 231 rolling back interrupted transaction
130 232 (verify step skipped, run `hg verify` to check your repository content)
131 233 $ f -s .hg/store/data/file*
132 .hg/store/data/file.d: size=1046
133 234 .hg/store/data/file.i: size=1174
134 235 $ hg tip
135 236 changeset: 1:cfa8d6e60429
@@ -141,10 +242,77 b' where the data file is left as garbage.'
141 242 $ hg verify -q
142 243 $ cd ..
143 244
245 Test a hard crash right after the index is move into place
246 ===========================================================
247
248 Now retry the procedure but intercept the rename of the index.
249
250 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-crash-after-rename
251 $ cd troffset-computation-crash-after-rename
252
253 Reference size:
254 $ f -s file
255 file: size=1024
256 $ f -s .hg/store/data/file*
257 .hg/store/data/file.i: size=1174
258
259 $ cat > .hg/hgrc <<EOF
260 > [extensions]
261 > intercept_rename = $TESTTMP/intercept_after_rename.py
262 > EOF
263 #if chg
264 $ hg pull ../troffset-computation
265 pulling from ../troffset-computation
266 searching for changes
267 adding changesets
268 adding manifests
269 adding file changes
270 [255]
271 #else
272 $ hg pull ../troffset-computation
273 pulling from ../troffset-computation
274 searching for changes
275 adding changesets
276 adding manifests
277 adding file changes
278 Killed
279 [137]
280 #endif
281
282 The inline revlog was over written on disk
283
284 $ f -s .hg/store/data/file*
285 .hg/store/data/file.d: size=132139
286 .hg/store/data/file.i: size=256
287
288 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
289 data/file.i 1174
290 data/file.d 0
291
292 recover is rolling the split back, the fncache is still valid
293
294 $ hg recover
295 rolling back interrupted transaction
296 (verify step skipped, run `hg verify` to check your repository content)
297 $ f -s .hg/store/data/file*
298 .hg/store/data/file.i: size=1174
299 $ hg tip
300 changeset: 1:cfa8d6e60429
301 tag: tip
302 user: test
303 date: Thu Jan 01 00:00:00 1970 +0000
304 summary: b
305
306 $ hg verify -q
307 $ cd ..
308
309 Have the transaction rollback itself without any hard crash
310 ===========================================================
311
144 312
145 313 Repeat the original test but let hg rollback the transaction.
146 314
147 $ hg clone -r 1 troffset-computation troffset-computation-copy-rb --config format.revlog-compression=none -q
315 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy-rb
148 316 $ cd troffset-computation-copy-rb
149 317 $ cat > .hg/hgrc <<EOF
150 318 > [hooks]
@@ -160,9 +328,14 b' Repeat the original test but let hg roll'
160 328 rollback completed
161 329 abort: pretxnchangegroup hook exited with status 1
162 330 [40]
331
332 The split was rollback
333
163 334 $ f -s .hg/store/data/file*
164 .hg/store/data/file.d: size=1046
165 .hg/store/data/file.i: size=128
335 .hg/store/data/file.d: size=0
336 .hg/store/data/file.i: size=1174
337
338
166 339 $ hg tip
167 340 changeset: 1:cfa8d6e60429
168 341 tag: tip
@@ -171,8 +344,85 b' Repeat the original test but let hg roll'
171 344 summary: b
172 345
173 346 $ hg verify -q
174 warning: revlog 'data/file.d' not in fncache!
175 1 warnings encountered!
176 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
177 347 $ cd ..
178 348
349 Read race
350 =========
351
352 We check that a client that started reading a revlog (its index) after the
353 split and end reading (the data) after the rollback should be fine
354
355 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-race
356 $ cd troffset-computation-race
357 $ cat > .hg/hgrc <<EOF
358 > [hooks]
359 > pretxnchangegroup=$RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/reader-index-read $TESTTMP/writer-revlog-split
360 > pretxnclose = false
361 > EOF
362
363 start a reader
364
365 $ hg cat --rev 0 file \
366 > --config "extensions.wait_read=$TESTTMP/reader_wait_split.py" \
367 > 2> $TESTTMP/reader.stderr \
368 > > $TESTTMP/reader.stdout &
369
370 Do a failed pull in //
371
372 $ hg pull ../troffset-computation
373 pulling from ../troffset-computation
374 searching for changes
375 adding changesets
376 adding manifests
377 adding file changes
378 transaction abort!
379 rollback completed
380 abort: pretxnclose hook exited with status 1
381 [40]
382 $ touch $TESTTMP/writer-revlog-unsplit
383 $ wait
384
385 The reader should be fine
386 $ cat $TESTTMP/reader.stderr
387 $ cat $TESTTMP/reader.stdout
388 1 (no-eol)
389 $ cd ..
390
391 pending hooks
392 =============
393
394 We checks that hooks properly see the inside of the transaction, while other process don't.
395
396 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-hooks
397 $ cd troffset-computation-hooks
398 $ cat > .hg/hgrc <<EOF
399 > [hooks]
400 > pretxnclose.01-echo = hg cat -r 'max(all())' file | f --size
401 > pretxnclose.02-echo = $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-done $TESTTMP/hook-tr-ready
402 > pretxnclose.03-abort = false
403 > EOF
404
405 $ (
406 > $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/hook-tr-ready;\
407 > hg cat -r 'max(all())' file | f --size;\
408 > touch $TESTTMP/hook-done
409 > ) >stdout 2>stderr &
410
411 $ hg pull ../troffset-computation
412 pulling from ../troffset-computation
413 searching for changes
414 adding changesets
415 adding manifests
416 adding file changes
417 size=131072
418 transaction abort!
419 rollback completed
420 abort: pretxnclose.03-abort hook exited with status 1
421 [40]
422
423 $ cat stdout
424 size=1024
425 $ cat stderr
426
427
428 $ cd ..
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now