Show More
@@ -240,3 +240,4 b' c890d8b8bc59b18e5febf60caada629df5356ee2' | |||
|
240 | 240 | 59466b13a3ae0e29a5d4f485393e516cfbb057d0 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmO1XgoZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVn8nDACU04KbPloLl+if6DQYreESnF9LU8C+qnLC/j5RRuaFNh/ec6C3DzLWqWdmnWA/siV3nUR1bXHfTui95azxJfYvWoXH2R2yam+YhE256B4rDDYWS1LI9kNNM+A33xcPS2HxVowkByhjB5FPKR6I90dX42BYJpTS5s/VPx63wXLznjFWuD7XJ3P0VI7D72j/+6EQCmHaAUEE5bO00Ob2JxmzJlaP+02fYc814PAONE2/ocfR0aExAVS3VA+SJGXnXTVpoaHr7NJKC2sBLFsdnhIRwtCf3rtGEvIJ5v2U2xx0ZEz/mimtGzW5ovkthobV4mojk0DRz7xBtA96pOGSRTD8QndIsdMCUipo8zZ/AGAMByCtsQOX7OYhR6gp+I6+iPh8fTR5oCbkO7cizDDQtXcrR5OT/BDH9xkAF1ghNL8o23a09/wfZ9NPg5zrh/4T/dFfoe2COlkAJJ1ttDPYyQkCfMsoWm3OXk6xJ3ExVbwkZzUDQSzsxGS+oxbFDWJZ64Q= |
|
241 | 241 | 8830004967ad865ead89c28a410405a6e71e0796 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQAsOQZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVl7XC/0W+Wd4gzMUbaot+NVIZTpubNw3KHBDXrlMgwQgCDg7qcqJnVuT1NNEy5sRELjZOO0867k+pBchZaxdmAiFwY1W76+7nwiLBqfCkYgYY0iQe48JHTq9kCgohvx9PSEVbUsScmqAQImd5KzErjhsLj8D2FiFIrcMyqsCBq4ZPs0Ey7lVKu6q3z5eDjlrxUIr0up6yKvgBxhY0GxyTp6DGoinzlFMEadiJlsvlwO4C6UpzKiCGMeKNT5xHK/Hx3ChrOH2Yuu1fHaPLJ+ZpXjR33ileVYlkQrh1D6fWHXcP7ZuwsEKREtgsw1YjYczGFwmhBO362bNi5wy33mBtCvcIAqpsI0rMrExs66qqbfyG+Yp1dvkgzUfdhbYFHA+mvg3/YTSD9dLKzzsb69LM87+dvcLqhBJ0nEAuBmAzU5ECkoArbiwMT96NhhjLPRmJJdHNo0IDos/LBGTgkOZ6iqIx8Xm/tgjBjFJG8B+IVy3laNgun4AZ9Ejc3ahIfhJUIo2j8o= |
|
242 | 242 | 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQBI2AZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVrRZC/wJyPOJoxpjEJZaRoBmWtkOlf0Y0TyEb6wd8tZIVALNDYZMSMqT7UBjFmaZijOYndUW7ZCj1hKShaIw80vY/hjJ3KZMODY9t91SOwmrVaGrCUeF1tXkuhEgwxfkekPWLxYYc688gLb6oc3FBm//lucNGrOWBXw6yhm1dUcndHXXpafjJslKAHwJN7vI5q69SxvS6SlJUzh/RFWYLnbZ2Qi35ixkU12FZiYVzxDl2i7XbhVoT5mit6VTU7Wh4BMSYuorAv937sF9Y6asE7sQUYHC2C2qjp8S5uFXV/IrhCPbJyWVc4ymPm58Eh6SmItC9zHDviFF9aFoZMK/lfK3Dqumu3T9x6ZYcxulpjNsM0/yv9OiiWbw33PnNb74A9uwrxZHB3XexXiigBUlUzO4lJQ5Oe1rhpPfPPRVyxaeZ8/cPmoJjCuwoiG0YtUeNH5PkHi05O0/hLR9PftDY8oMyzOBErSqjMjZ6OTkFFgk3dI9rHU72C1KL9Jh5uHwEQchBmg= |
|
243 | f14864fffdcab725d9eac6d4f4c07be05a35f59a 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQc3KUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVnYZDACh1Bcj8Yu3t8pO22SKWJnz8Ndw9Hvw+ifLaRxFUxKtqUYvy3CIl2qt8k7V13M25qw0061SKgcvNdjtkOhdmtFHNAbqryy0nK9oSZ2GfndmJfMxm9ixF/CcHrx+MmsklEz2woApViHW5PrmgKvZNsStQ5NM457Yx3B4nsT9b8t03NzdNiZRM+RZOkZ+4OdSbiB6hYuTqEFIi2YM+gfVM5Z7H8sEFBkUCtuwUjFGaWThZGGhAcqD5E7p/Lkjv4e4tzyHOzHDgdd+OCAkcbib6/E3Q1MlQ1x7CKpJ190T8R35CzAIMBVoTSI+Ov7OKw1OfGdeCvMVJsKUvqY3zrPawmJB6pG7GoVPEu5pU65H51U3Plq3GhsekUrKWY/BSHV9FOqpKZdnxOAllfWcjLYpbC/fM3l8uuQVcPAs89GvWKnDuE/NWCDYzDAYE++s/H4tP3Chv6yQbPSv/lbccst7OfLLDtXgRHIyEWLo392X3mWzhrkNtfJkBdi39uH9Aoh7pN0= |
@@ -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 |
|
|
2067 |
|
|
|
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 |
# |
|
|
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 |
|
|
165 |
|
|
|
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 |
|
|
|
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( |
|
|
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.4 |
|
|
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 |
|
|
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 |
|
|
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': |
|
|
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 s |
|
|
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 |
|
|
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:$TEST |
|
|
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. |
|
|
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 |
|
|
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 |
|
|
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= |
|
|
165 |
.hg/store/data/file.i: size=1 |
|
|
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