tests/test-convert-svn-source.t
author Gregory Szorc <gregory.szorc@gmail.com>
Fri, 13 Jan 2017 20:16:56 -0800
changeset 30818 4c0a5a256ae8
parent 28533 dfd5a6830ea7
child 35400 4441705b7111
permissions -rw-r--r--
localrepo: experimental support for non-zlib revlog compression The final part of integrating the compression manager APIs into revlog storage is the plumbing for repositories to advertise they are using non-zlib storage and for revlogs to instantiate a non-zlib compression engine. The main intent of the compression manager work was to zstd all of the things. Adding zstd to revlogs has proved to be more involved than other places because revlogs are... special. Very small inputs and the use of delta chains (which are themselves a form of compression) are a completely different use case from streaming compression, which bundles and the wire protocol employ. I've conducted numerous experiments with zstd in revlogs and have yet to formalize compression settings and a storage architecture that I'm confident I won't regret later. In other words, I'm not yet ready to commit to a new mechanism for using zstd - or any other compression format - in revlogs. That being said, having some support for zstd (and other compression formats) in revlogs in core is beneficial. It can allow others to conduct experiments. This patch introduces *highly experimental* support for non-zlib compression formats in revlogs. Introduced is a config option to control which compression engine to use. Also introduced is a namespace of "exp-compression-*" requirements to denote support for non-zlib compression in revlogs. I've prefixed the namespace with "exp-" (short for "experimental") because I'm not confident of the requirements "schema" and in no way want to give the illusion of supporting these requirements in the future. I fully intend to drop support for these requirements once we figure out what we're doing with zstd in revlogs. A good portion of the patch is teaching the requirements system about registered compression engines and passing the requested compression engine as an opener option so revlogs can instantiate the proper compression engine for new operations. That's a verbose way of saying "we can now use zstd in revlogs!" On an `hg pull` conversion of the mozilla-unified repo with no extra redelta settings (like aggressivemergedeltas), we can see the impact of zstd vs zlib in revlogs: $ hg perfrevlogchunks -c ! chunk ! wall 2.032052 comb 2.040000 user 1.990000 sys 0.050000 (best of 5) ! wall 1.866360 comb 1.860000 user 1.820000 sys 0.040000 (best of 6) ! chunk batch ! wall 1.877261 comb 1.870000 user 1.860000 sys 0.010000 (best of 6) ! wall 1.705410 comb 1.710000 user 1.690000 sys 0.020000 (best of 6) $ hg perfrevlogchunks -m ! chunk ! wall 2.721427 comb 2.720000 user 2.640000 sys 0.080000 (best of 4) ! wall 2.035076 comb 2.030000 user 1.950000 sys 0.080000 (best of 5) ! chunk batch ! wall 2.614561 comb 2.620000 user 2.580000 sys 0.040000 (best of 4) ! wall 1.910252 comb 1.910000 user 1.880000 sys 0.030000 (best of 6) $ hg perfrevlog -c -d 1 ! wall 4.812885 comb 4.820000 user 4.800000 sys 0.020000 (best of 3) ! wall 4.699621 comb 4.710000 user 4.700000 sys 0.010000 (best of 3) $ hg perfrevlog -m -d 1000 ! wall 34.252800 comb 34.250000 user 33.730000 sys 0.520000 (best of 3) ! wall 24.094999 comb 24.090000 user 23.320000 sys 0.770000 (best of 3) Only modest wins for the changelog. But manifest reading is significantly faster. What's going on? One reason might be data volume. zstd decompresses faster. So given more bytes, it will put more distance between it and zlib. Another reason is size. In the current design, zstd revlogs are *larger*: debugcreatestreamclonebundle (size in bytes) zlib: 1,638,852,492 zstd: 1,680,601,332 I haven't investigated this fully, but I reckon a significant cause of larger revlogs is that the zstd frame/header has more bytes than zlib's. For very small inputs or data that doesn't compress well, we'll tend to store more uncompressed chunks than with zlib (because the compressed size isn't smaller than original). This will make revlog reading faster because it is doing less decompression. Moving on to bundle performance: $ hg bundle -a -t none-v2 (total CPU time) zlib: 102.79s zstd: 97.75s So, marginal CPU decrease for reading all chunks in all revlogs (this is somewhat disappointing). $ hg bundle -a -t <engine>-v2 (total CPU time) zlib: 191.59s zstd: 115.36s This last test effectively measures the difference between zlib->zlib and zstd->zstd for revlogs to bundle. This is a rough approximation of what a server does during `hg clone`. There are some promising results for zstd. But not enough for me to feel comfortable advertising it to users. We'll get there...

#require svn svn-bindings

  $ filter_svn_output () {
  >     egrep -v 'Committing|Updating|(^$)' | sed -e 's/done$//' || true
  > }

  $ cat >> $HGRCPATH <<EOF
  > [extensions]
  > convert =
  > [convert]
  > svn.trunk = mytrunk
  > EOF

  $ svnadmin create svn-repo
  $ SVNREPOPATH=`pwd`/svn-repo
#if windows
  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
#else
  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
#endif
  $ INVALIDREVISIONID=svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1
  $ VALIDREVISIONID=svn:a2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk/mytrunk@1

Now test that it works with trunk/tags layout, but no branches yet.

Initial svn import

  $ mkdir projB
  $ cd projB
  $ mkdir mytrunk
  $ mkdir tags
  $ cd ..

  $ svn import -m "init projB" projB "$SVNREPOURL/proj%20B" | filter_svn_output | sort
  Adding         projB/mytrunk (glob)
  Adding         projB/tags (glob)
  Committed revision 1.

Update svn repository

  $ svn co "$SVNREPOURL/proj%20B/mytrunk" B | filter_svn_output
  Checked out revision 1.
  $ cd B
  $ echo hello > 'letter .txt'
  $ svn add 'letter .txt' | filter_svn_output
  A         letter .txt
  $ svn ci -m hello | filter_svn_output
  Adding         letter .txt
  Transmitting file data .
  Committed revision 2.

  $ svn-safe-append.py world 'letter .txt'
  $ svn ci -m world | filter_svn_output
  Sending        letter .txt
  Transmitting file data .
  Committed revision 3.

  $ svn copy -m "tag v0.1" "$SVNREPOURL/proj%20B/mytrunk" "$SVNREPOURL/proj%20B/tags/v0.1" | filter_svn_output
  Committed revision 4.

  $ svn-safe-append.py 'nice day today!' 'letter .txt'
  $ svn ci -m "nice day" | filter_svn_output
  Sending        letter .txt
  Transmitting file data .
  Committed revision 5.
  $ cd ..

Convert to hg once and also test localtimezone option

NOTE: This doesn't check all time zones -- it merely determines that
the configuration option is taking effect.

An arbitrary (U.S.) time zone is used here.  TZ=US/Hawaii is selected
since it does not use DST (unlike other U.S. time zones) and is always
a fixed difference from UTC.

  $ TZ=US/Hawaii hg convert --config convert.localtimezone=True "$SVNREPOURL/proj%20B" B-hg
  initializing destination B-hg repository
  scanning source...
  sorting...
  converting...
  3 init projB
  2 hello
  1 world
  0 nice day
  updating tags

Update svn repository again

  $ cd B
  $ svn-safe-append.py "see second letter" 'letter .txt'
  $ echo "nice to meet you" > letter2.txt
  $ svn add letter2.txt | filter_svn_output
  A         letter2.txt
  $ svn ci -m "second letter" | filter_svn_output
  Sending        letter .txt
  Adding         letter2.txt
  Transmitting file data ..
  Committed revision 6.

  $ svn copy -m "tag v0.2" "$SVNREPOURL/proj%20B/mytrunk" "$SVNREPOURL/proj%20B/tags/v0.2" | filter_svn_output
  Committed revision 7.

  $ svn-safe-append.py "blah-blah-blah" letter2.txt
  $ svn ci -m "work in progress" | filter_svn_output
  Sending        letter2.txt
  Transmitting file data .
  Committed revision 8.
  $ cd ..

  $ hg convert -s svn "$SVNREPOURL/proj%20B/non-existent-path" dest
  initializing destination dest repository
  abort: no revision found in module /proj B/non-existent-path
  [255]

########################################

Test incremental conversion

  $ TZ=US/Hawaii hg convert --config convert.localtimezone=True "$SVNREPOURL/proj%20B" B-hg
  scanning source...
  sorting...
  converting...
  1 second letter
  0 work in progress
  updating tags

  $ cd B-hg
  $ hg log -G --template '{rev} {desc|firstline} date: {date|date} files: {files}\n'
  o  7 update tags date: * +0000 files: .hgtags (glob)
  |
  o  6 work in progress date: * -1000 files: letter2.txt (glob)
  |
  o  5 second letter date: * -1000 files: letter .txt letter2.txt (glob)
  |
  o  4 update tags date: * +0000 files: .hgtags (glob)
  |
  o  3 nice day date: * -1000 files: letter .txt (glob)
  |
  o  2 world date: * -1000 files: letter .txt (glob)
  |
  o  1 hello date: * -1000 files: letter .txt (glob)
  |
  o  0 init projB date: * -1000 files: (glob)
  
  $ hg tags -q
  tip
  v0.2
  v0.1
  $ cd ..

Test filemap
  $ echo 'include letter2.txt' > filemap
  $ hg convert --filemap filemap "$SVNREPOURL/proj%20B/mytrunk" fmap
  initializing destination fmap repository
  scanning source...
  sorting...
  converting...
  5 init projB
  4 hello
  3 world
  2 nice day
  1 second letter
  0 work in progress
  $ hg -R fmap branch -q
  default
  $ hg log -G -R fmap --template '{rev} {desc|firstline} files: {files}\n'
  o  1 work in progress files: letter2.txt
  |
  o  0 second letter files: letter2.txt
  
Convert with --full adds and removes files that didn't change

  $ cd B
  $ echo >> "letter .txt"
  $ svn ci -m 'nothing' | filter_svn_output
  Sending        letter .txt
  Transmitting file data .
  Committed revision 9.
  $ cd ..

  $ echo 'rename letter2.txt letter3.txt' > filemap
  $ hg convert --filemap filemap --full "$SVNREPOURL/proj%20B/mytrunk" fmap
  scanning source...
  sorting...
  converting...
  0 nothing
  $ hg -R fmap st --change tip
  A letter .txt
  A letter3.txt
  R letter2.txt

test invalid splicemap1

  $ cat > splicemap <<EOF
  > $INVALIDREVISIONID $VALIDREVISIONID
  > EOF
  $ hg convert --splicemap splicemap "$SVNREPOURL/proj%20B/mytrunk" smap
  initializing destination smap repository
  abort: splicemap entry svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1 is not a valid revision identifier
  [255]

Test stop revision
  $ hg convert --rev 1 "$SVNREPOURL/proj%20B/mytrunk" stoprev
  initializing destination stoprev repository
  scanning source...
  sorting...
  converting...
  0 init projB
  $ hg -R stoprev branch -q
  default

Check convert_revision extra-records.
This is also the only place testing more than one extra field in a revision.

  $ cd stoprev
  $ hg tip --debug | grep extra
  extra:       branch=default
  extra:       convert_revision=svn:........-....-....-....-............/proj B/mytrunk@1 (re)
  $ cd ..

Test converting empty heads (issue3347).
Also tests getting logs directly without debugsvnlog.

  $ svnadmin create svn-empty
  $ svnadmin load -q svn-empty < "$TESTDIR/svn/empty.svndump"
  $ hg --config convert.svn.trunk= --config convert.svn.debugsvnlog=0 convert svn-empty
  assuming destination svn-empty-hg
  initializing destination svn-empty-hg repository
  scanning source...
  sorting...
  converting...
  1 init projA
  0 adddir
  $ hg --config convert.svn.trunk= convert "$SVNREPOURL/../svn-empty/trunk"
  assuming destination trunk-hg
  initializing destination trunk-hg repository
  scanning source...
  sorting...
  converting...
  1 init projA
  0 adddir

Test that a too-new repository format is properly rejected:
  $ mv svn-empty/format format
  $ echo 999 > svn-empty/format
It's important that this command explicitly specify svn, otherwise it
can have surprising side effects (like falling back to a perforce
depot that can be seen from the test environment and slurping from that.)
  $ hg convert --source-type svn svn-empty this-will-fail
  initializing destination this-will-fail repository
  file:/*/$TESTTMP/svn-empty does not look like a Subversion repository to libsvn version 1.*.* (glob)
  abort: svn-empty: missing or unsupported repository
  [255]
  $ mv format svn-empty/format