[Updated] [++++- ] D11239: debugcommands: introduce a debug command to repair repos affected by issue6528

Alphare (Raphaël Gomès) phabricator at mercurial-scm.org
Fri Aug 6 17:51:21 UTC 2021


Alphare updated this revision to Diff 29835.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D11239?vs=29829&id=29835

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,249 @@
      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
+#if windows
+tar interprets `:` in paths (like `C:`) as being remote, force local on Windows
+only since some versions of tar don't have this flag.
+
+  $ tar --force-local -xf $TESTDIR/bundles/issue6528.tar
+#else
+  $ tar xf $TESTDIR/bundles/issue6528.tar
+#endif
+
+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 affected revision 1 for filelog 'data/D.txt.i'
+  found affected revision 1 for filelog 'data/b.txt.i'
+  found affected 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 affected revision 1 for filelog 'data/D.txt.i'
+  repaired revision 1 of 'filelog data/D.txt.i'
+  found affected revision 1 for filelog 'data/b.txt.i'
+  found affected 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 affected 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
+#if windows
+tar interprets `:` in paths (like `C:`) as being remote, force local on Windows
+only since some versions of tar don't have this flag.
+
+  $ tar --force-local -xf $TESTDIR/bundles/issue6528.tar
+#else
+  $ tar xf $TESTDIR/bundles/issue6528.tar
+#endif
+
+  $ hg debug-repair-issue6528 --to-report $TESTTMP/report.txt
+  found affected revision 1 for filelog 'data/D.txt.i'
+  found affected revision 1 for filelog 'data/b.txt.i'
+  found affected 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 affected revision 1 for filelog 'D.txt'
+  found affected revision 1 for filelog 'b.txt'
+  found affected 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 affected revision 1 for filelog 'D.txt'
+  repaired revision 1 of 'filelog data/D.txt.i'
+  found affected revision 1 for filelog 'b.txt'
+  found affected 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 affected
+  no affected revisions were found for 'D.txt'
+  revision a58b36ad6b6545195952793099613c2116f3563b of file 'b.txt' is not affected
+  revision ea4f2f2463cca5b29ddf3461012b8ce5c6dac175 of file 'b.txt' is not affected
+  no affected 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
+#if windows
+tar interprets `:` in paths (like `C:`) as being remote, force local on Windows
+only since some versions of tar don't have this flag.
+
+  $ tar --force-local -xf $TESTDIR/bundles/issue6528.tar
+#else
+  $ tar xf $TESTDIR/bundles/issue6528.tar
+#endif
+  $ 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 affected revision 1 for filelog 'data/D.txt.i'
+  repaired revision 1 of 'filelog data/D.txt.i'
+  found affected revision 1 for filelog 'data/b.txt.i'
+  found affected 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 affected 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 affected 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,224 @@
     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)
+    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
+    if rl._format_version != constants.REVLOGV1:
+        msg = "expected version 1 revlog, got version '%d'" % rl._format_version
+        raise error.ProgrammingError(msg)
+
+    index_file = rl._indexfile
+    new_file_path = index_file + b'.tmp-parents-fix'
+    repaired_msg = _(b"repaired revision %d of 'filelog %s'\n")
+
+    with ui.uninterruptible():
+        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_affected(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:
+            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 affected 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)) for n in filenodes.split(b',')
+            )
+            excluded = set()
+
+            for filerev in to_fix:
+                if _is_revision_affected(ui, fl, filerev, filename):
+                    msg = b"found affected revision %d for filelog '%s'\n"
+                    ui.warn(msg % (filerev, filename))
+                else:
+                    msg = _(b"revision %s of file '%s' is not affected\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 affected revisions were found for '%s'\n")
+                ui.write(msg % filename)
+                continue
+            if not dry_run:
+                _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():
+        files = list(
+            (file_type, path)
+            for (file_type, path, _e, _s) in repo.store.datafiles()
+            if path.endswith(b'.i') and file_type & store.FILEFLAGS_FILELOG
+        )
+
+        progress = ui.makeprogress(
+            _(b"looking for affected revisions"),
+            unit=_(b"filelogs"),
+            total=len(files),
+        )
+        found_nothing = True
+
+        for file_type, path in files:
+            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
+                affected = _is_revision_affected(ui, fl, filerev, path)
+                if affected:
+                    msg = b"found affected revision %d for filelog '%s'\n"
+                    ui.warn(msg % (filerev, path))
+                    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:
+                    _reorder_filelog_parents(repo, fl, to_fix)
+
+        if found_nothing:
+            ui.write(_(b"no affected revisions were found\n"))
+
+        if to_report and report_entries:
+            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,63 @@
         ui.write(b"%s\n" % f)
 
 
+ at command(
+    b"debug-repair-issue6528",
+    [
+        (
+            b'',
+            b'to-report',
+            b'',
+            _(b'build a report of affected 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 affected 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 affected revisions for a given repository across clones.
+    The report format is line-based (with empty lines ignored):
+
+    ```
+    <ascii-hex of the affected revision>,... <unencoded filelog index filename>
+    ```
+
+    There can be multiple broken revisions per filelog, they are separated by
+    a comma with no spaces. The only space is between the revision(s) and the
+    filename.
+
+    Note that this does *not* mean that this repairs future affected 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:
+        msg = b"can only repair revlogv1 repositories, v2 is not affected"
+        raise error.Abort(_(msg))
+
+    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/20210806/79344f04/attachment-0002.html>


More information about the Mercurial-patches mailing list