[Updated] [++++- ] D11239: debugcommands: introduce a debug command to repair repos affected by issue6528
Alphare (Raphaël Gomès)
phabricator at mercurial-scm.org
Wed Aug 4 20:43:53 UTC 2021
Alphare updated this revision to Diff 29791.
REPOSITORY
rHG Mercurial
CHANGES SINCE LAST UPDATE
https://phab.mercurial-scm.org/D11239?vs=29790&id=29791
BRANCH
stable
CHANGES SINCE LAST ACTION
https://phab.mercurial-scm.org/D11239/new/
REVISION DETAIL
https://phab.mercurial-scm.org/D11239
AFFECTED FILES
mercurial/debugcommands.py
mercurial/revlogutils/rewrite.py
tests/bundles/issue6528.tar
tests/test-completion.t
tests/test-help.t
tests/test-issue6528.t
CHANGE DETAILS
diff --git a/tests/test-issue6528.t b/tests/test-issue6528.t
--- a/tests/test-issue6528.t
+++ b/tests/test-issue6528.t
@@ -3,7 +3,7 @@
===============================================================
Setup
------
+=====
$ hg init base-repo
$ cd base-repo
@@ -93,7 +93,7 @@
Check the lack of corruption
-----------------------------
+============================
$ hg clone --pull base-repo cloned
requesting all changes
@@ -166,3 +166,228 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: c_base_c - create a.txt
+
+Test the command that fixes the issue
+=====================================
+
+Restore a broken repository with multiple broken revisions and a filename that
+would get encoded to test the `report` options.
+It's a tarball because unbundle might magically fix the issue later.
+
+ $ cd ..
+ $ mkdir repo-to-fix
+ $ cd repo-to-fix
+ $ tar xf $TESTDIR/bundles/issue6528.tar
+
+Check that the issue is present
+ $ hg st
+ M D.txt
+ M b.txt
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 05b806ebe5ea 000000000000
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 216a5fe8b8ed 000000000000
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 2a8d3833f2fb 000000000000
+
+Dry-run the fix
+ $ hg debug-repair-issue6528 --dry-run
+ found corrupted revision 1 for filelog 'data/D.txt.i'
+ found corrupted revision 1 for filelog 'data/b.txt.i'
+ found corrupted revision 3 for filelog 'data/b.txt.i'
+ $ hg st
+ M D.txt
+ M b.txt
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 05b806ebe5ea 000000000000
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 216a5fe8b8ed 000000000000
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 2a8d3833f2fb 000000000000
+
+Run the fix
+ $ hg debug-repair-issue6528
+ found corrupted revision 1 for filelog 'data/D.txt.i'
+ repaired revision 1 of 'filelog data/D.txt.i'
+ found corrupted revision 1 for filelog 'data/b.txt.i'
+ found corrupted revision 3 for filelog 'data/b.txt.i'
+ repaired revision 1 of 'filelog data/b.txt.i'
+ repaired revision 3 of 'filelog data/b.txt.i'
+
+Check that the fix worked and that running it twice does nothing
+ $ hg st
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 000000000000 05b806ebe5ea
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 000000000000 216a5fe8b8ed
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 000000000000 2a8d3833f2fb
+ $ hg debug-repair-issue6528
+ no corrupted revisions were found
+ $ hg st
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 000000000000 05b806ebe5ea
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 000000000000 216a5fe8b8ed
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 000000000000 2a8d3833f2fb
+
+Try the using the report options
+--------------------------------
+
+ $ cd ..
+ $ mkdir repo-to-fix-report
+ $ cd repo-to-fix
+ $ tar xf $TESTDIR/bundles/issue6528.tar
+
+ $ hg debug-repair-issue6528 --to-report $TESTTMP/report.txt
+ found corrupted revision 1 for filelog 'data/D.txt.i'
+ found corrupted revision 1 for filelog 'data/b.txt.i'
+ found corrupted revision 3 for filelog 'data/b.txt.i'
+ $ cat $TESTTMP/report.txt
+ 2a80419dfc31d7dfb308ac40f3f138282de7d73b:D.txt
+ a58b36ad6b6545195952793099613c2116f3563b ea4f2f2463cca5b29ddf3461012b8ce5c6dac175:b.txt
+
+ $ hg debug-repair-issue6528 --from-report $TESTTMP/report.txt --dry-run
+ loading report file '$TESTTMP/report.txt'
+ found corrupted revision 1 for filelog 'D.txt'
+ found corrupted revision 1 for filelog 'b.txt'
+ found corrupted revision 3 for filelog 'b.txt'
+ $ hg st
+ M D.txt
+ M b.txt
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 05b806ebe5ea 000000000000
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 216a5fe8b8ed 000000000000
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 2a8d3833f2fb 000000000000
+
+ $ hg debug-repair-issue6528 --from-report $TESTTMP/report.txt
+ loading report file '$TESTTMP/report.txt'
+ found corrupted revision 1 for filelog 'D.txt'
+ repaired revision 1 of 'filelog data/D.txt.i'
+ found corrupted revision 1 for filelog 'b.txt'
+ found corrupted revision 3 for filelog 'b.txt'
+ repaired revision 1 of 'filelog data/b.txt.i'
+ repaired revision 3 of 'filelog data/b.txt.i'
+ $ hg st
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 000000000000 05b806ebe5ea
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 000000000000 216a5fe8b8ed
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 000000000000 2a8d3833f2fb
+
+Check that the revision is not "fixed" again
+
+ $ hg debug-repair-issue6528 --from-report $TESTTMP/report.txt
+ loading report file '$TESTTMP/report.txt'
+ revision 2a80419dfc31d7dfb308ac40f3f138282de7d73b of file 'D.txt' is not corrupted
+ no corrupted revisions were found for 'D.txt'
+ revision a58b36ad6b6545195952793099613c2116f3563b of file 'b.txt' is not corrupted
+ revision ea4f2f2463cca5b29ddf3461012b8ce5c6dac175 of file 'b.txt' is not corrupted
+ no corrupted revisions were found for 'b.txt'
+ $ hg st
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 000000000000 05b806ebe5ea
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 000000000000 216a5fe8b8ed
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 000000000000 2a8d3833f2fb
+
+Try it with a non-inline revlog
+-------------------------------
+
+ $ cd ..
+ $ mkdir $TESTTMP/ext
+ $ cat << EOF > $TESTTMP/ext/small_inline.py
+ > from mercurial import revlog
+ > revlog._maxinline = 8
+ > EOF
+
+ $ cat << EOF >> $HGRCPATH
+ > [extensions]
+ > small_inline=$TESTTMP/ext/small_inline.py
+ > EOF
+
+ $ mkdir repo-to-fix-not-inline
+ $ cd repo-to-fix-not-inline
+ $ tar xf $TESTDIR/bundles/issue6528.tar
+ $ echo b >> b.txt
+ $ hg commit -qm "inline -> separate"
+ $ find .hg -name *b.txt.d
+ .hg/store/data/b.txt.d
+
+Status is correct, but the problem is still there, in the earlier revision
+ $ hg st
+ $ hg up 3
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg st
+ M b.txt
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 05b806ebe5ea 000000000000
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 216a5fe8b8ed 000000000000
+ 4 8 db234885e2fe ea4f2f2463cc 000000000000
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 2a8d3833f2fb 000000000000
+ 2 8 65aecc89bb5d 2a80419dfc31 000000000000
+
+Run the fix on the non-inline revlog
+ $ hg debug-repair-issue6528
+ found corrupted revision 1 for filelog 'data/D.txt.i'
+ repaired revision 1 of 'filelog data/D.txt.i'
+ found corrupted revision 1 for filelog 'data/b.txt.i'
+ found corrupted revision 3 for filelog 'data/b.txt.i'
+ repaired revision 1 of 'filelog data/b.txt.i'
+ repaired revision 3 of 'filelog data/b.txt.i'
+
+Check that it worked
+ $ hg debugrevlogindex b.txt
+ rev linkrev nodeid p1 p2
+ 0 2 05b806ebe5ea 000000000000 000000000000
+ 1 3 a58b36ad6b65 000000000000 05b806ebe5ea
+ 2 6 216a5fe8b8ed 000000000000 000000000000
+ 3 7 ea4f2f2463cc 000000000000 216a5fe8b8ed
+ 4 8 db234885e2fe ea4f2f2463cc 000000000000
+ $ hg debugrevlogindex D.txt
+ rev linkrev nodeid p1 p2
+ 0 6 2a8d3833f2fb 000000000000 000000000000
+ 1 7 2a80419dfc31 000000000000 2a8d3833f2fb
+ 2 8 65aecc89bb5d 2a80419dfc31 000000000000
+ $ hg debug-repair-issue6528
+ no corrupted revisions were found
+ $ hg st
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -975,6 +975,9 @@
$ hg help debug
debug commands (internal and unsupported):
+ debug-repair-issue6528
+ find corrupted revisions and repair them. See issue6528 for
+ more details.
debugancestor
find the ancestor revision of two revisions in a given index
debugantivirusrunning
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -74,6 +74,7 @@
Show debug commands if there are no other candidates
$ hg debugcomplete debug
+ debug-repair-issue6528
debugancestor
debugantivirusrunning
debugapplystreamclonebundle
@@ -266,6 +267,7 @@
config: untrusted, exp-all-known, edit, local, source, shared, non-shared, global, template
continue: dry-run
copy: forget, after, at-rev, force, include, exclude, dry-run
+ debug-repair-issue6528: to-report, from-report, dry-run
debugancestor:
debugantivirusrunning:
debugapplystreamclonebundle:
diff --git a/tests/bundles/issue6528.tar b/tests/bundles/issue6528.tar
new file mode 100644
index 0000000000000000000000000000000000000000..f92105258524076c68bbb0fadb3827fcf801edd3
GIT binary patch
literal 61440
zc%1EB30M<X*B$~WMsNeQXe;BcC@}jZq7?*DTLr|W)(t1g1R^29EUx_&+^t%rRcjR$
z_qx};YWs`Yx@)!WTPdH4)>^b`TU+JxpP2;60uAeQ;l2-dCNpH_oXnZ`oO|!wb0;_i
zs&XJ4Xp-dDC`}@^>jFS1fg%`*aSIeeP?Y<FV6y5}gCMif#28>0VzeoVjDaojd#?Wf
z4 at GcDaAHCg-vec^KSH1wZ-0VDfc?dQ#r|WIj53j}s at tIS_UFEaa{Ez?#wcKaF<@tZ
zt|*f<Ms_T#tV|h#{W*tTdi#@H1b|?qGL<L^!2S;Q*J^Z0LEJS-U`>@IgEufm5M{7G
zg^(1mzZfXq{*^03;6F{5!TvN&;1G;e(fw!#V1Ebu>-1IO|I5aI&i at 1civo-N5k#5D
z=n`11J|S3B=`vi}pGJ%9k8pkh#c2Ws_7?}QUcoAJa`HI1 at D_kgdN{?vj%IZx*lg73
zxE+Zstk<ew1Dm3UwahrZxk`L7fatLBze<~b<!S$sI1So=V!+n^28A+6$0W1HpwVb$
z%5WY3X$Sig6o~=<F9uYsnlWpEH?OX6<UbeoE1Ui<`HwpJe}cq7|Bop6W$oHTeoc3Y
zoy4!1q1v<jdgbX3H~Dqe%e-uUy`}NSANcj at Hhtsxb?Acwv44TO0F?||{wF4wm;|F*
zr&qDY%JcxP_>Vic|1r at 2D-H_y?}!3_;D`eM;D`c$;fMnN;fMl%;)nwO;t1exss^_F
z=h=@{fee at Re-d}#KTaZm|Du3~aRtjL6eJ=eQ9`9s69|U02&GnWEq02Mp=yjI2%KS2
z>0hlXKo!7=|3OC9Fq&0Wri`-lpP(tw|1Syv|Eo7_`L8fAI%OgjG+JZSD72M#tGjIf
z<CJ6lpTG&ge=$&6_QSNa)L+%Z)?P*6GXD1X6aEJ!YE&v#S8*9-=RZYJB;dao0Q|4s
zD31S at 7C>-i(Z4+WCowSoLlgl1S8tTe|H_k5j`}}F(SZMA0Pw$hqd5L+l9SCQrt*3K
z%E5mcjQ<x0fdADS#qnRk7%TNYFFXGUK3xp(UjzXDS8q5!|EuiY|MEQlBT*a&{1*e@
z`Cs*hh5u%qN*}D!7&tDND*qOD&3~uyzuf-;{{aB18aDj5RR60$hD-iC<^SLm3ivMy
zK>knlhHd<3jg^}AUkI1&f5fr<k7G3G{}Tm>G{vmdO4S-I+bJX@#FU)Er-86WlQEc5
z$rY?_Y*L6aQKMC%A^hGTjnQakDH4+fn?m at 0q98sc95fC7v9OK*dVNwdV at NVqwhY(t
z->LqOgZUrCfyMrb35Ke1{Q_LrALabLWBxymgZiH+XbH1pndB5LE6nX-)R^>!aj;Ua
zQ)?37c1D(k+a at N!iL5roy0a~;)*Ikty at 7=_I<>yNbVv$gN;D3WS}T%p7r4AZMA{PO
z&jmAD4Y$w8&qAU%C9(!ssnzSaZM_;cC2EY8Nk}w0ctBilZl?j}W0!H9(ZnVT#~Zox
z6sLKx at J}cGTXJ8w>fR~EY)mv8wA^nn8WXv2I8vo&!>rdQSjU>UUnT4_25~1z;tot6
zCwzhnP?iIQUsZHqNTNQO6~1Ksrtp8Mbck6qj5~mzmjPyUDws`XG+MZwR<C5V{3R;9
zBo^Csg!L&Vjb6uS+joT7;DliAShJBe2*-DU-(onKk$M*HY^S19pZq2W;?)rr|6gTI
ze*rGzKk7993q^qbe^CIQ)Kza-_+MpR{{mdL|1rXb|ESRZ2l+qZfX}O+=6CCv<jorS
z=7EU;ts^_-6!$$H1f{Lv*7aU-;MSgz!KSgMg1x>4dlmd%G4%(i7_9C8lmr8#Vik-s
z$(#~Aioiyz^;&ghO$aCS at FPgc@!x6wcarAYe_;N%s>h$&- at uMCYYc2fJA_>Of84?T
zG>L)szbH`YO{`9-SFuvHP8bCwO<;Abfzhg1t%;Eu*wI>jLJ-$JOyTYWBiFDD8gDeI
ztoui!Qez5Z;F_1hrqsx_Kv__g{3qd0`7hYB;!!~G|7Cjr2aO^)@c&|<6!u4uWJae^
zvqn=n=Z1FS|9PX^*Z(mT1O8tmxI=E%PY72E$^OcU_9xx_!xn9g+Nf&quG_Dx>f!U_
zTi70MW`r6)E at PDn5<!)unoucNC8}mo1uB=ZG(#aOO07U?nxf^_;DFf4bF&sfX299!
zkrz&N`DHvA`P8#nT9?Rmx=)Ldr&7sDl)#iKTCOBXl)^ED+l3Qql~Rt<EU8vfi~_?k
zi at Xtb@+8(G$O~;b@}X);*GJ*6md|PWrnh(0S!#ftJWGoPAr&%&VihdO50+G5jDnKM
zWO7`o!cY{EvzUxTxt(&0y!m$W+^vO|>2}d0cA+2awP)W~y>sdrveK?;F6}Bt9z)6$
zIK`+a1w|4hDktS6M$2(TE~ijj$;qVDI7#6Oi at YphIow)!SswhF|9w~YH@$HF;ei!R
z?*1O|w8q9prhzXQTY2qvhvdT}w at 4t#^ac$xoBL<dS7t1H=hNK7t!lO3tA*00v_ITq
z$Ubt^%Z7nXFTdE`YhvQ9+~G0Pr7 at i&Z at js+-xs}xOt|qo_Jd*b$>mKazaNfzctm=@
zQVG<0VOH%Cf>qs`34#V%wez$V4_>=FkEg!AuT9)@zw`Az%dd4aa{soi5l-3-PnJL*
zde-;-EUiw~)#GOu|7YFU9ZMIStJU>P&B><%vM=WwzF+Z8<gk`seO_17BX9rkEtxHL
z9G|p&^-cZSTT`U>J|LH#$lVp%F0BP5af4t_PftNBlbu#ItVPhOZKLmA9&fsKb53^G
z-1{lb-VVRm^M0{fP4=ws_gV8A-`8Igvvh~ybiWV3Sn!|z{c=N(B%j#Z=1~7LK~265
zy0d(>=RCH<nE<a(=6#wIx2J3Cs1X at mHLpV7p1NjnOTQ@+*o)Jurd6wxf`E6dTGg}`
zUNcWv-MXV`!@Ry<&c1KTJ9LMxJ1%IavsPyAT6M4Qm+9sYrMKR5`eNT-m-S44nBI4A
zPE7^8efi^d8AFK3vwt)mIRk6D at 4<r$I}|?i?k-#ymE7gn66K9!AB~F7TzXOBJ*Byz
zmi1x*{A*E)_^)D2Oob|erLX^4`~NY%QUL0IVxYA4k5}?>seBbcSM7hkv)A7KC{Ba<
zAH_jY{m+Lh-qz0^1@%AK!`*IQKfCcTAZPpHd+U7k+s7m<w>{j9N--%eC8VU3k`|r@
zg6eXm4rHm=2{D8}*{y|s9j{~zXoUG(Bl1r)zWZ|I4!ikA&1${g at pJ%Hic<4mRdxI)
zaN3#wVEm^zDB^!#uEbhD*C^nB;9MF0<i++T9U6OhUpdI;cphscw>{kKCROTF#&v=t
z`1?cnY+y%sf=k^Xbdwf&5Cvn97ODb4(OfB*44SV73qB+;HGJ7i^h)+lugzgkf0nfi
zx_2ei;V1EaCyXmtp*Ce{E_UNeBv(9mXH<z7`tOO-@}DU`{ax~(LU2d^V;GqKMI00#
z|Dno^|An19<%$2V3(J0x#TD><D1~*CRu6t$I%3riF<W!D%w1pO-tC|71_axkuD~}b
z?0r+IJ6GJKwh9$`C%v%r<BBh2>2Bd`I1*kZa{0!TRqV~TcWp?R6mZ;}x8~RXb$0rN
zTyZ+*Uxm{0Ur}}Z=im13%zyCyCvi}G{FfAP%=7aaKi++CV8^D!nID~LUT=B3zJdE}
z4=>~xQ?>D*XPMwb*3PE3B(dH7-;EoGe8O~Y-stJTs*e9Fgr%(uDy$wg9~poD^Pd9u
zF3-ovKW=@x<ox!ob}!E@>GEI%%diT$f<hRKl at n@)WjTi=Q!z9`u{4XTQI<s27+aDH
zdLk at ixH7=1Qq2OZJ%|Zur98cM$FgjKaqBSW?$PUq%q}kwPRdz^6V6~1sZtUO1;NNE
znpUG~rHm$7mLb%Xn#Gm8^eC?M<cg=@$NuEj0{Hu*6#k#{dq&ov*PF^a4#ZXc&$<5(
z#Sl>c69uJu{#(J91G{?ui_ at 4>`=12kKgEGtVf^m{6~+I;`al1EC`riTG0?z9#Kgx9
zuRmEf>Y?r7!c(W%))GkKS<i2NntyEEh}P?aVi!j>Xpdz!oW8h0mtA8=t#BU_zJ<ww
zc1CvDC41L at cUGU7>lc5(%=pH*&U4<K6;oG?jMQI!2Q5;gDB9Y!ANZG2MI~Ih)pTW#
z at BLQQh95l6`o>lnGZ(H{wTvKCxKe?u)FjPtHxi-75fWu(D6S$1hN4(TCgszRqzH^i
zEpb_ at j8E39H0p8jN;n9%e6)mY;k=o`lFyLSLJrT5`S)SvH+*urY2=6;kAuNSGlm{|
z;dFhpmn6{P?!JxZcdxOn!4O<A)3<%JI`dLY*MSc^9G!LKM#uEin}@}QA8r at jXjjd)
z2G5V{&fnbsM#nUE&)~y*z1?s5F59&$J`j5xv0FC2OXoT#TXqWD?+(Sx`ikwJ8z`8}
zO{gCgR^jX(IrFdP%G~(bTS=XK{oi%(HGKcPdv-M at vx-0n5)o=h2qIHZ3b~RXlq^>-
zr!gfWLsfE0t|Apg(N$9vUNzQ`z{V>(!AgB{vc?1p+k!&<g>xr!WgKLA9$olMntvyl
z*DuTK_mKEx9=z1~otc-HraN7;ajo5<C(|4G&)-lhF*_Ch<)fQV=7kdTE-#vYeZtYJ
zO;$?uAyK0{w(6jp>>vBa9DRsa-j}_)L2VXv%<&F7{QkEa9{uDgpOX8*)%a$^j(K at S
zUq9xTba(q4^VSG5d1E)t;F^IFC at iwz>Kzi~@{03GFm{iq`4^QvQor`${Y^vHPq-4k
z?d;xfVw{vsyeome?e6D4f4xsi{IcPaw(Z;helTu<dKdAbTg%+<KXHo>nV`M;Vtjbs
z`)}XNtPvBvs#b<y^p4%z8h`cp+y}9A>c*djty%IsOc%NM#FD6`sadbLTifL7Mu`MU
ztaECpr%=g~v=QWu;R=7X?4DTjZ%zCD?H57k+oT at vg(USkop4<4q-46;9a<H|UD5T@
zlz5-Xb&tL|i at 4D3yA#2|STnz^SGqTx6f*Kcmsi6-=uNmAd+t!-Zu`DHH0^*&<s0yA
zx7{8uC&<sOIn?sK$f$crmj{F9rVahPR+E0)-q;+sYarg?qL)Wai0U=IPOB7UVv8xG
z!v^IBP7u^yCTK8>D?=fNN7}9ipPMm+qA(Iu(p+^^rY2E^N~U0CG{JHW3YNfWL`};v
zl>#riGlgO4j`ydKMp)ck3Q&spZygv~ei<(7f0X0%KZF4Dzl#Ig&Rwan5ox=ju-%BT
zZv53c15_SL!GFu-5W>HNRO>R!aLIq1bl^XRQK0 at W3NR^T+rCGnP@%g=0MY;bC?)?Z
zB*PW|`N>}G^S^1D2K*NV_MJP3)NZgpVi)%dHY4pf8T(B|;pSic(*R0=(#HRabOM*X
z{})3^F#m%nu-Lzn61_?QZ{V`!e_<3xfbqX#z}Eg19_U>H%5Hz01o_|Mz{&m<8R%UC
zT>1Z^@!y5{|0n|T|HJ_p|6Q%&%>N1u^ezFe_+K>syNLfNsQ-xrF#a2i|E{co-X*{l
z|Ea?9p9TCUXabD?6bJu|@!z~dDD(L5u^De1J>-)LdwZTs$?u1x)qiUf+w^_I*Yy;p
z3BK7s$S+R(^4bk-#qX0c#{4>MOw0B&7N`6-bMx!;+5rP3mtLDpD4I`+`0<VUQ$40H
zOsl)IkKYmDtjWUi-!b~T82(pepmzyy!G9E?DJTAufdAqEjQ=j>`0rS#9OJ)v7j%kq
zL4F=tDYpVQXFcw5Kezd*h7mK*=k>|B&@SWl_)~3OjKBQq!~yN)euIp22Y1~0s9Th#
z$46^g`VOPL*A3~ma-Li5n)6ZveFJ>XHnt1_dL%5t;BSD6!`A;-c%r8g;KKi72wqJ6
z59I%g12F&7zh(ZXAy7Hyf8qkv$pb!Fuy^;qBd7O50-8Q}SpUTId$rnO6ITuW!9-6z
z_|zDGY~tv4Z(JC>>e?5<M_SeNpIfJI^z2(br?T*agZHG<d~UDtU%g+lc~i*3uj=`=
zC7#TAE*U6o%FhQ3xiuA}#6jhl{E1g>pHC{{4TX)I9zH96C_d|Uf<$_>=K1A$o$~H|
zhs|vpzGg5I6aHF2bMy2*L3#D!Q$Kr=oxbI!S>}~+Zt$Hp8 at q%?-wj;9D0oi3XHMfL
zvjmNj?KCRW#7|>0TA$qBFctCg3`?5vvnP`FEckF;BwTuI at 8bs(w&v$exz(-rn$MvY
z3G??H*b;m3ey=SdYnwm)DqAyl8!@Oxs7XETvu97d_WlqKZ%BLUp0lvYB0-~r!pR5Q
zXjJM+pDfpiGhbNW2j0Fp?#O>~XWns-ncw1ekDRx+y;%D&FWda)qkge#vC}=<&aAKL
z-+ft9oTQfYXcP2!of|LWqI=(o&b#iN5ZPeNr>SlJZ^>`5GtPfdSGKyT9mxUywXl!>
z6- at Lg0j}adPB_MY5~0BJA8}B@{2$H%o!}ZcVP2kDp##pP*0>tejm+(JRWh{BBK?1d
z9_tME`EquQYDLz=kw>~;nYv+DF1E;jKwb-`dB!h|`VOT>4?k4%*hin<>H22Z^hO;E
z)hO!lDO3Ir=bul6O8JO!uFLx4o(mSO*}XS?ab4NWDgMuXovf=n+~=3qPDkxe?>=N|
z6JxCAQjgZkMc)mpu_nHjXYJz|V<h*-XY4t)GcfwO at 4@kIXX0O5Ae`0G-xUv)Bm0LB
z7$~n$*m*&rk=IwPPbd0ht&}ba*N0rZ`*cPRcHQYf`PSa+8nxXypz$YC&3qW09T#0|
zsD4JO+t_EvRz91o{kYjplke+~<L|6ig#M at t89jTV;$FbD#O5~y8Tp0Ri#OiiAoC|$
zI5+P=%AEPb-A$*$r4rOTD(64Z>9yPW-0q+7Q~1dhM0mZR(;h!Rcfa-Crr-XEXny_U
zVBN+cUhmEA(_&Ga+lJq6435|o`$u~J&(A$w@~TyD^IMnwX6#K5$zJ;NwB#{sp7!?7
zKD**)<G`y+jgQ}p`L=r-ouKI6Lc^9h>&MZs{a4?A82;9E;(q32^|>3h&A%VH*N|Ew
zyF-k*eeRmypoq-KbwrXnx<#956CN8v>ow at OrE8tI+VZ^hx?hi8xMKKvj?e$xJ4DF+
zI^;vL1+XAW(nydr3Mxmw&)5ud4qE3h;@y4Bio=beujlW%meVX<89I68-2R=<-FOF6
zj63q;k4JmXZNA)HN-iHXXIJ)tGXobzE*jG2%)UWxd*?+Q_L#S6(i_Xe>+FGh-b%iG
z>A~ZVe;V+3a at Vvk+HZ}>&d(aVy6&8?jxXOiFL&EBD|nKi at C-pVUymtsrVnTM<MKMG
za6Kq%V*H`n-oG(Fb)DI9_TYLAZXUk3^jvRr at c3=Zj*j at oZ)v}^N4ueeTJ%@-nIF_@
z*lf3TABRh89XlEj at vbB{`su39&)0o2s<CYRq(PhW-L at ycPCx*E2HW^w;Y6Pj;OhBL
zQT|V1`ya)@{9mE~<o{G}*z&)^i9RL3CI3;!=RXubBoOdl1YjWHr&`02|5YKw75|I!
ze~QNcq9FfQ6jUU^$01k9F_q}A{%HWEfK&Xh at N7@ca2fxd-~UE|`M<@1t^KRO^a4t6
z|APEag2sXUMS;csmGq*&65!JQv{U at Y!1yn5P^$O;f%p9VYY}Xz?NzA^m;A>Z{Xa^B
z_dkn+VrQ2Hs5S@=srpxTyXL>+{2wSygXjOE;P01_000000000000000000000000`
GS@?f2GKtIp
literal 0
Hc$@<O00001
diff --git a/mercurial/revlogutils/rewrite.py b/mercurial/revlogutils/rewrite.py
--- a/mercurial/revlogutils/rewrite.py
+++ b/mercurial/revlogutils/rewrite.py
@@ -7,6 +7,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
+import binascii
import contextlib
import os
@@ -472,3 +473,222 @@
new_index_file.write(entry_bin)
revlog._docket.index_end = new_index_file.tell()
revlog._docket.data_end = new_data_file.tell()
+
+
+def _get_filename_from_filelog_index(path):
+ # Drop the extension and the `data/` prefix
+ path_part = path.rsplit(b'.', 1)[0].split(b'/', 1)
+ if len(path_part) < 2:
+ msg = _(b"cannot recognize filelog from filename: '%s'")
+ msg %= path
+ raise error.Abort(msg)
+
+ return path_part[1]
+
+
+def _filelog_from_filename(repo, path):
+ """Returns the filelog for the given `path`. Stolen from `engine.py`"""
+
+ from .. import filelog # avoid cycle
+
+ fl = filelog.filelog(repo.svfs, path)
+ rl = fl._revlog
+ if rl._format_version != constants.REVLOGV1:
+ msg = "expected version 1 revlog, got version '%d'" % rl._format_version
+ raise error.ProgrammingError(msg)
+ return fl
+
+
+def _write_swapped_parents(repo, rl, rev, offset, fp):
+ """Swaps p1 and p2 and overwrites the revlog entry for `rev` in `fp`"""
+ from ..pure import parsers # avoid cycle
+
+ if repo._currentlock(repo._lockref) is None:
+ # Let's be paranoid about it
+ msg = "repo needs to be locked to rewrite parents"
+ raise error.ProgrammingError(msg)
+
+ index_format = parsers.IndexObject.index_format
+ entry = rl.index[rev]
+ new_entry = list(entry)
+ new_entry[5], new_entry[6] = entry[6], entry[5]
+ packed = index_format.pack(*new_entry[:8])
+ fp.seek(offset)
+ fp.write(packed)
+
+
+def _reorder_filelog_parents(repo, fl, to_fix):
+ """
+ Swaps p1 and p2 for all `to_fix` revisions of filelog `fl` and writes the
+ new version to disk, overwriting the old one with a rename.
+ """
+ from ..pure import parsers # avoid cycle
+
+ ui = repo.ui
+ assert len(to_fix) > 0
+ rl = fl._revlog
+
+ index_file = rl._indexfile
+ new_file_path = index_file + b'.tmp-parents-fix'
+ repaired_msg = _(b"repaired revision %d of 'filelog %s'\n")
+ try:
+ util.copyfile(
+ rl.opener.join(index_file),
+ rl.opener.join(new_file_path),
+ checkambig=rl._checkambig,
+ )
+
+ with rl.opener(new_file_path, mode=b"r+") as fp:
+ if rl._inline:
+ index = parsers.InlinedIndexObject(fp.read())
+ for rev in fl.revs():
+ if rev in to_fix:
+ offset = index._calculate_index(rev)
+ _write_swapped_parents(repo, rl, rev, offset, fp)
+ ui.write(repaired_msg % (rev, index_file))
+ else:
+ index_format = parsers.IndexObject.index_format
+ for rev in to_fix:
+ offset = rev * index_format.size
+ _write_swapped_parents(repo, rl, rev, offset, fp)
+ ui.write(repaired_msg % (rev, index_file))
+
+ rl.opener.rename(new_file_path, index_file)
+ rl.clearcaches()
+ rl._loadindex()
+ finally:
+ util.tryunlink(new_file_path)
+
+
+def _is_revision_corrupted(ui, fl, filerev, path):
+ """Mercurial currently (5.9rc0) uses `p1 == nullrev and p2 != nullrev` as a
+ special meaning compared to the reverse in the context of filelog-based
+ copytracing. issue6528 exists because new code assumed that parent ordering
+ didn't matter, so this detects if the revision contains metadata (since
+ it's only used for filelog-based copytracing) and its parents are in the
+ "wrong" order."""
+ try:
+ raw_text = fl.rawdata(filerev)
+ except error.CensoredNodeError:
+ # We don't care about censored nodes as they never carry metadata
+ return False
+ has_meta = raw_text.startswith(b'\x01\n')
+ if has_meta:
+ (p1, p2) = fl.parentrevs(filerev)
+ if p1 != nullrev and p2 == nullrev:
+ msg = b"found corrupted revision %d for filelog '%s'\n"
+ ui.warn(msg % (filerev, path))
+ return True
+ return False
+
+
+def _from_report(ui, repo, context, from_report, dry_run):
+ """
+ Fix the revisions given in the `from_report` file, but still checks if the
+ revisions are indeed corrupted to prevent an unfortunate cyclic situation
+ where we'd swap well-ordered parents again.
+
+ See the doc for `debug_fix_issue6528` for the format documentation.
+ """
+ ui.write(_(b"loading report file '%s'\n") % from_report)
+
+ with context(), open(from_report, mode='rb') as f:
+ for line in f.read().split(b'\n'):
+ if not line:
+ continue
+ filenodes, filename = line.split(b':', 1)
+ fl = _filelog_from_filename(repo, filename)
+ to_fix = set(
+ fl.rev(binascii.unhexlify(n.strip())) for n in filenodes.split()
+ )
+ excluded = set()
+
+ for filerev in to_fix:
+ if not _is_revision_corrupted(ui, fl, filerev, filename):
+ msg = _(b"revision %s of file '%s' is not corrupted\n")
+ msg %= (binascii.hexlify(fl.node(filerev)), filename)
+ ui.warn(msg)
+ excluded.add(filerev)
+
+ to_fix = to_fix - excluded
+ if not to_fix:
+ msg = _(b"no corrupted revisions were found for '%s'\n")
+ ui.write(msg % filename)
+ continue
+ if not dry_run:
+ with ui.uninterruptible():
+ _reorder_filelog_parents(repo, fl, sorted(to_fix))
+
+
+def repair_issue6528(ui, repo, dry_run=False, to_report=None, from_report=None):
+ from .. import store # avoid cycle
+
+ @contextlib.contextmanager
+ def context():
+ if dry_run or to_report: # No need for locking
+ yield
+ else:
+ with repo.wlock(), repo.lock():
+ yield
+
+ if from_report:
+ return _from_report(ui, repo, context, from_report, dry_run)
+
+ report_entries = []
+
+ with context():
+ total = sum(
+ 1
+ for (t, p, _e, _s) in repo.store.datafiles()
+ if p.endswith(b'.i') and t & store.FILEFLAGS_FILELOG
+ )
+
+ progress = ui.makeprogress(
+ _(b"looking for corrupted revisions"),
+ unit=_(b"filelogs"),
+ total=total,
+ )
+ found_nothing = True
+
+ for file_type, path, _encoded, _size in repo.store.datafiles():
+ if (
+ not path.endswith(b'.i')
+ or not file_type & store.FILEFLAGS_FILELOG
+ ):
+ continue
+ progress.increment()
+ filename = _get_filename_from_filelog_index(path)
+ fl = _filelog_from_filename(repo, filename)
+
+ # Set of filerevs (or hex filenodes if `to_report`) that need fixing
+ to_fix = set()
+ for filerev in fl.revs():
+ # TODO speed up by looking at the start of the delta
+ # If it hasn't changed, it's not worth looking at the other revs
+ # in the same chain
+ corrupted = _is_revision_corrupted(ui, fl, filerev, path)
+ if corrupted:
+ found_nothing = False
+ if not dry_run:
+ if to_report:
+ to_fix.add(binascii.hexlify(fl.node(filerev)))
+ else:
+ to_fix.add(filerev)
+
+ if to_fix:
+ to_fix = sorted(to_fix)
+ if to_report:
+ report_entries.append((filename, to_fix))
+ else:
+ with repo.ui.uninterruptible():
+ _reorder_filelog_parents(repo, fl, to_fix)
+
+ if found_nothing:
+ ui.write(_(b"no corrupted revisions were found\n"))
+
+ if to_report and len(report_entries) > 0:
+ with open(to_report, mode="wb") as f:
+ for path, to_fix in report_entries:
+ f.write(b"%s:%s\n" % (b" ".join(to_fix), path))
+
+ progress.complete()
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -71,6 +71,7 @@
registrar,
repair,
repoview,
+ requirements,
revlog,
revset,
revsetlang,
@@ -105,6 +106,7 @@
from .revlogutils import (
deltas as deltautil,
nodemap,
+ rewrite,
sidedata,
)
@@ -1451,6 +1453,60 @@
ui.write(b"%s\n" % f)
+ at command(
+ b"debug-repair-issue6528",
+ [
+ (
+ b'',
+ b'to-report',
+ b'',
+ _(b'build a report of corrupted revisions to this file'),
+ _(b'FILE'),
+ ),
+ (
+ b'',
+ b'from-report',
+ b'',
+ _(b'repair revisions listed in this report file'),
+ _(b'FILE'),
+ ),
+ ]
+ + cmdutil.dryrunopts,
+)
+def debug_repair_issue6528(ui, repo, **opts):
+ """find corrupted revisions and repair them. See issue6528 for more details.
+
+ The `--to-report` and `--from-report` flags allow you to cache and reuse the
+ computation of corrupted revisions for a given repository across clones.
+ The report format is line-based (with empty lines ignored):
+
+ ```
+ <ascii-hex of the corrupted revision>... :<unencoded filelog index filename>
+ ```
+
+ There can be multiple broken revisions per filelog.
+
+ Note that this does *not* mean that this repairs future corrupted revisions,
+ that needs a separate fix at the exchange level that hasn't been written yet
+ (as of 5.9rc0).
+ """
+ cmdutil.check_incompatible_arguments(
+ opts, 'to_report', ['from_report', 'dry_run']
+ )
+ dry_run = opts.get('dry_run')
+ to_report = opts.get('to_report')
+ from_report = opts.get('from_report')
+ # TODO maybe add filelog pattern and revision pattern parameters to help
+ # narrow down the search for users that know what they're looking for?
+
+ if requirements.REVLOGV1_REQUIREMENT not in repo.requirements:
+ raise error.Abort(_(b"can only repair revlogv1 repositories"))
+
+ rewrite.repair_issue6528(
+ ui, repo, dry_run=dry_run, to_report=to_report, from_report=from_report
+ )
+
+
@command(b'debugformat', [] + cmdutil.formatteropts)
def debugformat(ui, repo, **opts):
"""display format information about the current repository
To: Alphare, #hg-reviewers, marmoute
Cc: marmoute, mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-patches/attachments/20210804/9709acfe/attachment-0002.html>
More information about the Mercurial-patches
mailing list