cvs2svn-2.5.0/0000775000175100017510000000000013206450463014177 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/0000775000175100017510000000000013206450463016065 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tagged-branch-n-trunk-cvsrepos/0000775000175100017510000000000013206450463024011 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tagged-branch-n-trunk-cvsrepos/a.txt,v0000664000175100017510000000700012203665125025230 0ustar mhaggermhagger00000000000000head 1.27; access ; symbols some-branch:1.24.0.22 some-tag:1.24.22.2 ; locks ; strict; comment @# @; 1.27 date 2002.11.05.12.31.01; author Mats; state Dev; branches ; next 1.26; 1.26 date 2002.10.11.14.28.44; author Mats; state Dev; branches ; next 1.25; 1.25 date 2002.06.25.13.54.10; author Mats; state Dev; branches ; next 1.24; 1.24 date 2000.12.15.13.22.31; author Mats; state Dev; branches 1.24.22.1; next 1.23; 1.23 date 2000.12.15.13.14.45; author Mats; state Dev; branches ; next 1.22; 1.22 date 2000.11.07.10.36.13; author Mats; state Dev; branches ; next 1.21; 1.21 date 2000.10.26.13.11.15; author Mats; state Dev; branches ; next 1.20; 1.20 date 2000.09.26.07.07.19; author mats; state Dev; branches ; next 1.19; 1.19 date 2000.08.09.10.36.51; author mats; state Dev; branches ; next 1.18; 1.18 date 2000.07.06.13.25.36; author mats; state Dev; branches ; next 1.17; 1.17 date 2000.07.06.12.22.47; author mats; state Dev; branches ; next 1.16; 1.16 date 2000.05.18.16.46.37; author mats; state Dev; branches ; next 1.15; 1.15 date 2000.04.25.11.48.55; author mats; state Dev; branches ; next 1.14; 1.14 date 2000.04.25.07.11.38; author mats; state Dev; branches ; next 1.13; 1.13 date 2000.04.05.13.19.20; author mats; state Dev; branches ; next 1.12; 1.12 date 2000.03.31.14.47.59; author mats; state Dev; branches ; next 1.11; 1.11 date 2000.03.23.17.10.45; author mats; state Dev; branches ; next 1.10; 1.10 date 2000.03.22.14.20.50; author mats; state Dev; branches ; next 1.9; 1.9 date 2000.03.15.14.15.52; author mats; state Dev; branches ; next 1.8; 1.8 date 2000.03.08.19.46.55; author mats; state Dev; branches ; next 1.7; 1.7 date 2000.03.08.18.53.46; author mats; state Dev; branches ; next 1.6; 1.6 date 2000.03.08.14.45.46; author mats; state Dev; branches ; next 1.5; 1.5 date 2000.03.06.21.31.28; author mats; state Dev; branches ; next 1.4; 1.4 date 2000.03.06.19.27.30; author mats; state Dev; branches ; next 1.3; 1.3 date 2000.03.06.17.23.03; author mats; state Dev; branches ; next 1.2; 1.2 date 2000.03.03.16.23.52; author mats; state Dev; branches ; next 1.1; 1.1 date 2000.03.02.08.39.26; author mats; state Dev; branches ; next ; 1.24.22.1 date 2000.12.15.13.22.31; author Mats; state Dev; branches ; next 1.24.22.2; 1.24.22.2 date 2002.03.22.15.29.53; author Mats; state Dev; branches ; next ; desc @@ 1.27 log @Log socket level errors@ text @ 1.27 @ 1.26 log @log@ text @d1 1 a1 1 1.26 @ 1.25 log @log@ text @d1 1 a1 1 1.25 @ 1.24 log @log@ text @d1 1 a1 1 1.24 @ 1.24.22.1 log @Duplicate revision @ text @@ 1.24.22.2 log @ff@ text @d1 1 a1 1 1.24 @ 1.23 log @log@ text @d1 1 a1 1 1.23 @ 1.22 log @log@ text @d1 1 a1 1 1.22 @ 1.21 log @log@ text @d1 1 a1 1 1.21 @ 1.20 log @log@ text @d1 1 a1 1 1.20 @ 1.19 log @log@ text @d1 1 a1 1 1.19 @ 1.18 log @log@ text @d1 1 a1 1 1.18 @ 1.17 log @log@ text @d1 1 a1 1 1.17 @ 1.16 log @log@ text @d1 1 a1 1 1.16 @ 1.15 log @log@ text @d1 1 a1 1 1.15 @ 1.14 log @log@ text @d1 1 a1 1 1.14 @ 1.13 log @log@ text @d1 1 a1 1 1.13 @ 1.12 log @log@ text @d1 1 a1 1 1.12 @ 1.11 log @log@ text @d1 1 a1 1 1.11 @ 1.10 log @log@ text @d1 1 a1 1 1.10 @ 1.9 log @log@ text @d1 1 a1 1 1.9 @ 1.8 log @log@ text @d1 1 a1 1 1.8 @ 1.7 log @log@ text @d1 1 a1 1 1.7 @ 1.6 log @log@ text @d1 1 a1 1 1.6 @ 1.5 log @log@ text @d1 1 a1 1 1.5 @ 1.4 log @log@ text @d1 1 a1 1 1.4 @ 1.3 log @log@ text @d1 1 a1 1 1.3 @ 1.2 log @log@ text @d1 1 a1 1 1.2 @ 1.1 log @Initial Revision@ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/tagged-branch-n-trunk-cvsrepos/b.txt,v0000664000175100017510000000144112203665125025234 0ustar mhaggermhagger00000000000000head 1.6; access ; symbols some-branch:1.5.0.22 some-tag:1.5 ; locks ; strict; comment @# @; 1.6 date 2002.10.11.14.28.44; author Mats; state Dev; branches ; next 1.5; 1.5 date 2000.03.31.14.48.32; author mats; state Dev; branches ; next 1.4; 1.4 date 2000.03.08.20.59.57; author mats; state Dev; branches ; next 1.3; 1.3 date 2000.03.06.17.27.00; author mats; state Dev; branches ; next 1.2; 1.2 date 2000.03.02.13.11.20; author mats; state Dev; branches ; next 1.1; 1.1 date 2000.03.02.08.39.24; author mats; state Dev; branches ; next ; desc @@ 1.6 log @log@ text @ 1.6 @ 1.5 log @log@ text @d1 1 a1 1 1.5 @ 1.4 log @log@ text @d1 1 a1 1 1.4 @ 1.3 log @ @ text @d1 1 a1 1 1.3 @ 1.2 log @log@ text @d1 1 a1 1 1.2 @ 1.1 log @Initial Revision@ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/enroot-race-cvsrepos/0000775000175100017510000000000013206450463022145 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/enroot-race-cvsrepos/proj/0000775000175100017510000000000013206450463023117 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/enroot-race-cvsrepos/proj/a.txt,v0000664000175100017510000000116012203665124024336 0ustar mhaggermhagger00000000000000head 1.3; access; symbols mybranch:1.3.0.2; locks; strict; comment @# @; 1.3 date 2004.02.04.17.02.08; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.02.04.17.02.05; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit next change. @ text @Next change. @ 1.2 log @Commit first change. @ text @d1 1 a1 1 First change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is a.txt. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/enroot-race-cvsrepos/proj/c.txt,v0000664000175100017510000000172012203665124024342 0ustar mhaggermhagger00000000000000head 1.4; access; symbols mybranch:1.4.0.2; locks; strict; comment @# @; 1.4 date 2004.02.04.17.02.07; author kfogel; state Exp; branches 1.4.2.1; next 1.3; 1.3 date 2004.02.04.17.02.06; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.02.04.17.02.05; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches; next ; 1.4.2.1 date 2004.02.04.17.02.08; author kfogel; state Exp; branches; next ; desc @@ 1.4 log @Commit yet another change to c.txt. @ text @Yet another commit on c.txt. @ 1.4.2.1 log @Commit next change. @ text @d1 1 a1 1 Next change. @ 1.3 log @Commit another change to c.txt. @ text @d1 1 a1 1 Another commit on c.txt. @ 1.2 log @Commit first change. @ text @d1 1 a1 1 First change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is c.txt. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/enroot-race-cvsrepos/proj/b.txt,v0000664000175100017510000000116012203665124024337 0ustar mhaggermhagger00000000000000head 1.3; access; symbols mybranch:1.3.0.2; locks; strict; comment @# @; 1.3 date 2004.02.04.17.02.08; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.02.04.17.02.05; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.04.17.02.04; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit next change. @ text @Next change. @ 1.2 log @Commit first change. @ text @d1 1 a1 1 First change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is b.txt. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/0000775000175100017510000000000013206450463024035 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/makerepo.sh0000775000175100017510000000225612203665124026202 0ustar mhaggermhagger00000000000000#! /bin/sh # This is the script used to create the branch-from-empty-dir CVS # repository. (The repository is checked into svn; this script is # only here for its documentation value.) # # The script should be started from the main cvs2svn directory. # # The repository itself tickles a problem that I was having with an # uncommitted version of better-symbol-selection when BRANCH2 is # grafted onto BRANCH1. name=branch-from-empty-dir repo=`pwd`/test-data/$name-cvsrepos wc=`pwd`/cvs2svn-tmp/$name-wc [ -e $repo/CVSROOT ] && rm -rf $repo/CVSROOT [ -e $repo/proj ] && rm -rf $repo/proj [ -e $wc ] && rm -rf $wc cvs -d $repo init cvs -d $repo co -d $wc . cd $wc mkdir proj cvs add proj cd proj mkdir subdir cvs add subdir echo '1.1' >subdir/b.txt cvs add subdir/b.txt cvs commit -m 'Adding subdir/b.txt:1.1' . rm subdir/b.txt cvs rm subdir/b.txt cvs commit -m 'Removing subdir/b.txt' . cvs rtag -r 1.2 -b BRANCH1 proj/subdir/b.txt cvs rtag -r 1.2 -b BRANCH2 proj/subdir/b.txt echo '1.1' >a.txt cvs add a.txt cvs commit -m 'Adding a.txt:1.1' . cvs tag -b BRANCH1 a.txt cvs update -r BRANCH1 echo '1.1.2.1' >a.txt cvs commit -m 'Committing a.txt:1.1.2.1' a.txt cvs tag -b BRANCH2 a.txt cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/proj/0000775000175100017510000000000013206450463025007 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/proj/a.txt,v0000664000175100017510000000057312203665124026235 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.2.1.0.2 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.04.30.15.20.32; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.04.30.15.20.33; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding a.txt:1.1 @ text @1.1 @ 1.1.2.1 log @Committing a.txt:1.1.2.1 @ text @d1 1 a1 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/proj/subdir/0000775000175100017510000000000013206450463026277 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/proj/subdir/Attic/0000775000175100017510000000000013206450463027343 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-empty-dir-cvsrepos/proj/subdir/Attic/b.txt,v0000664000175100017510000000053412203665124030567 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH2:1.2.0.4 BRANCH1:1.2.0.2; locks; strict; comment @# @; 1.2 date 2007.04.30.15.20.32; author mhagger; state dead; branches; next 1.1; 1.1 date 2007.04.30.15.20.31; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Removing subdir/b.txt @ text @1.1 @ 1.1 log @Adding subdir/b.txt:1.1 @ text @@ cvs2svn-2.5.0/test-data/repeatedly-defined-symbols-cvsrepos/0000775000175100017510000000000013206450463025147 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/repeatedly-defined-symbols-cvsrepos/proj/0000775000175100017510000000000013206450463026121 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/repeatedly-defined-symbols-cvsrepos/proj/default,v0000664000175100017510000000034612203665125027734 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2 BRANCH:1.1.0.2 TAG:1.1 TAG:1.1; locks; strict; comment @# @; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/invalid-symbol-cvsrepos/0000775000175100017510000000000013206450463022660 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/invalid-symbol-cvsrepos/file.txt,v0000664000175100017510000000026212203665124024600 0ustar mhaggermhagger00000000000000head 1.1; access; symbols SYMBOL:1; locks; strict; comment @ * @; 1.1 date 95.11.09.02.31.53; author author2; state Exp; branches; next ; desc @@ 1.1 log @log 43@ text @@ cvs2svn-2.5.0/test-data/invalid-symbol-cvsrepos/cvs2svn-ignore.options0000664000175100017510000000141312203665124027157 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # Fix a problem with an invalid symbol by ignoring it using a # SymbolMapper. from cvs2svn_lib.symbol_transform import SymbolMapper execfile('cvs2svn-example.options') name = 'invalid-symbol' ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/%s--options=cvs2svn-ignore.options-svnrepos' % (name,), ) run_options.clear_projects() filename = 'test-data/%s-cvsrepos/file.txt,v' % (name,) symbol_mapper = SymbolMapper([ (filename, 'SYMBOL', '1', None), ]) run_options.add_project( r'test-data/%s-cvsrepos' % (name,), trunk_path='trunk', branches_path='branches', tags_path='tags', symbol_transforms=[ symbol_mapper, ], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/invalid-symbol-cvsrepos/cvs2svn-ignore2.options0000664000175100017510000000125612203665124027246 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # Fix a problem with an invalid symbol by ignoring it using an # IgnoreSymbolTransform. from cvs2svn_lib.symbol_transform import IgnoreSymbolTransform execfile('cvs2svn-example.options') name = 'invalid-symbol' ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/%s--options=cvs2svn-ignore2.options-svnrepos' % (name,), ) run_options.clear_projects() run_options.add_project( r'test-data/%s-cvsrepos' % (name,), trunk_path='trunk', branches_path='branches', tags_path='tags', symbol_transforms=[ IgnoreSymbolTransform(r'SYMBOL'), ], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/vendor-branch-delete-add-cvsrepos/0000775000175100017510000000000013206450463024445 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/vendor-branch-delete-add-cvsrepos/proj/0000775000175100017510000000000013206450463025417 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/vendor-branch-delete-add-cvsrepos/proj/file.txt,v0000664000175100017510000000103112203665125027333 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendorbranch:1.1.1; locks; strict; comment @# @; expand @o@; 1.2 date 2003.03.03.03.03.03; author cc; state Exp; branches; next 1.1; 1.1 date 2001.01.01.01.01.01; author aa; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.01.01.01.01.01; author aa; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2002.02.02.02.02.02; author bb; state dead; branches; next ; desc @@ 1.2 log @1.2 @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @1.1.1.1 @ text @@ 1.1.1.2 log @1.1.1.2 @ text @@ cvs2svn-2.5.0/test-data/issue-100-cvsrepos/0000775000175100017510000000000013206450463021355 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/issue-100-cvsrepos/file2.txt,v0000775000175100017510000000142712203665124023366 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendor:1.1.1; locks; strict; comment @# @; 1.2 date 2005.02.17.01.59.23; author ianr; state Exp; branches; next 1.1; 1.1 date 2003.02.04.22.27.56; author ianr; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.02.04.22.27.56; author ianr; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.11.28.11.21.29; author ianr; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2003.02.04.22.27.56; author ianr; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2005.02.28.05.08.33; author ianr; state Exp; branches; next ; desc @@ 1.2 log @log 1 @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @import 1 @ text @@ 1.1.1.2 log @duplicated log message @ text @@ 1.1.1.3 log @revert @ text @@ 1.1.1.4 log @duplicated log message @ text @@ cvs2svn-2.5.0/test-data/issue-100-cvsrepos/file1.txt,v0000775000175100017510000000142612203665124023364 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendor:1.1.1; locks; strict; comment @# @; 1.2 date 2004.11.28.11.21.29; author ianr; state Exp; branches; next 1.1; 1.1 date 2003.02.04.22.27.56; author ianr; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.02.04.22.27.56; author ianr; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.10.11.02.26.01; author ianr; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2003.02.04.22.27.56; author ianr; state Exp; branches; next ; desc @@ 1.2 log @duplicated log message @ text @file1.txt<1.2> @ 1.1 log @Initial revision @ text @d1 1 a1 1 file1.txt<1.1> @ 1.1.1.1 log @import 1 @ text @d1 1 a1 1 file1.txt<1.1.1.1> @ 1.1.1.2 log @import 2 @ text @d1 1 a1 1 file1.txt<1.1.1.2> @ 1.1.1.3 log @revert @ text @d1 1 a1 1 file1.txt<1.1.1.3> @ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/0000775000175100017510000000000013206450463022316 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/0000775000175100017510000000000013206450463023270 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463024334 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/e.txt,v0000664000175100017510000000077312203665125025571 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1 TAG2:1.1.4.1; locks; strict; comment @# @; 1.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches 1.1.4.1; next ; commitid Jeo5F7Vn6nyLrl2u; 1.1.4.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches; next ; commitid l4kyUGsxvoE1sl2u; desc @@ 1.1 log @file e.txt was initially added on branch BRANCH. @ text @@ 1.1.4.1 log @file e.txt was added on branch BRANCH2 on 2009-09-04 09:46:18 +0000 @ text @@ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/d.txt,v0000664000175100017510000000067612203665125025572 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1; locks; strict; comment @# @; 1.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches 1.1.4.2; next ; commitid Jeo5F7Vn6nyLrl2u; 1.1.4.2 date 2009.09.04.09.46.18; author mhagger; state Exp; branches; next ; commitid l4kyUGsxvoE1sl2u; desc @@ 1.1 log @file d.txt was initially added on branch BRANCH. @ text @@ 1.1.4.2 log @Add files on BRANCH2. @ text @@ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/f.txt,v0000664000175100017510000000043412203665125025564 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1; locks; strict; comment @# @; 1.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches; next ; commitid Jeo5F7Vn6nyLrl2u; desc @@ 1.1 log @file e.txt was initially added on branch BRANCH. @ text @@ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/c.txt,v0000664000175100017510000000123312203665125025557 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1 TAG2:1.1.4.1; locks; strict; comment @# @; 1.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches 1.1.4.1; next ; commitid Jeo5F7Vn6nyLrl2u; 1.1.4.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches; next 1.1.4.2; commitid l4kyUGsxvoE1sl2u; 1.1.4.2 date 2009.09.04.09.46.18; author mhagger; state Exp; branches; next ; commitid l4kyUGsxvoE1sl2u; desc @@ 1.1 log @file c.txt was initially added on branch BRANCH. @ text @@ 1.1.4.1 log @file c.txt was added on branch BRANCH2 on 2009-09-04 09:46:18 +0000 @ text @@ 1.1.4.2 log @Add files on BRANCH2. @ text @@ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/Attic/b.txt,v0000664000175100017510000000147312203665125025564 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1 TAG2:1.1.4.1; locks; strict; comment @# @; 1.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches 1.1.2.1 1.1.4.1; next ; commitid Jeo5F7Vn6nyLrl2u; 1.1.2.1 date 2009.09.04.09.45.32; author mhagger; state Exp; branches; next ; commitid Jeo5F7Vn6nyLrl2u; 1.1.4.1 date 2009.09.04.09.45.32; author mhagger; state dead; branches; next 1.1.4.2; commitid l4kyUGsxvoE1sl2u; 1.1.4.2 date 2009.09.04.09.46.18; author mhagger; state Exp; branches; next ; commitid l4kyUGsxvoE1sl2u; desc @@ 1.1 log @file b.txt was initially added on branch BRANCH. @ text @@ 1.1.4.1 log @file b.txt was added on branch BRANCH2 on 2009-09-04 09:46:18 +0000 @ text @@ 1.1.4.2 log @Add files on BRANCH2. @ text @@ 1.1.2.1 log @Add files on BRANCH. @ text @@ cvs2svn-2.5.0/test-data/many-deletes-cvsrepos/proj/a.txt,v0000664000175100017510000000035112203665125024511 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2009.09.04.09.44.53; author mhagger; state Exp; branches; next ; commitid FASyv2HRCxOxrl2u; desc @@ 1.1 log @Add a.txt @ text @@ cvs2svn-2.5.0/test-data/repeated-deltatext-cvsrepos/0000775000175100017510000000000013206450463023514 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/repeated-deltatext-cvsrepos/file.txt,v0000664000175100017510000000131612203665125025436 0ustar mhaggermhagger00000000000000head 1.3; access; symbols uves_2_0_0:1.3 UVES-1_3_0-beta1a:1.1; locks; strict; comment @# @; 1.3 date 2003.06.30.12.59.08; author amodigli; state Exp; branches; next 1.2; 1.2 date 2003.06.30.12.54.52; author amodigli; state Exp; branches; next 1.1; 1.1 date 2002.02.11.17.59.51; author amodigli; state Exp; branches; next ; desc @@ 1.3 log @uves-2.0.0-rep @ text @ COMMON /QC_LOG/MID_S_N_CENT,OBJ_POS_CENT, + FWHM,N_CURR_ORD !to not pass a parameter to G_PROF @ 1.2 log @uves-2.0.0 @ text @@ 1.1 log @1st release @ text @@ 1.1 log @Created @ text @ COMMON /QC_LOG/MID_S_N_CENT,OBJ_POS_CENT, + FWHM,N_CURR_ORD !to not pass a parameter to G_PROF @ cvs2svn-2.5.0/test-data/crossed-branches-cvsrepos/0000775000175100017510000000000013206450463023154 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/crossed-branches-cvsrepos/proj/0000775000175100017510000000000013206450463024126 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/crossed-branches-cvsrepos/proj/file2.txt,v0000664000175100017510000000126312203665124026132 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH3:1.2.0.4 BRANCH4:1.2.0.2 BRANCH1:1.1.0.4 BRANCH2:1.1.0.2; locks; strict; comment @# @; 1.2 date 2007.01.20.14.54.27; author mhagger; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2007.01.20.14.45.02; author mhagger; state Exp; branches; next ; 1.2.2.1 date 2007.01.20.14.57.11; author mhagger; state Exp; branches; next ; 1.2.4.1 date 2007.01.20.14.56.46; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Revisions 1.2 @ text @1.2 @ 1.2.2.1 log @Shared commit message @ text @d1 1 a1 1 BRANCH4 commit @ 1.2.4.1 log @Shared commit message @ text @d1 1 a1 1 BRANCH3 commit @ 1.1 log @Adding two files @ text @d1 1 @ cvs2svn-2.5.0/test-data/crossed-branches-cvsrepos/proj/file1.txt,v0000664000175100017510000000126312203665124026131 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH4:1.2.0.4 BRANCH3:1.2.0.2 BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2007.01.20.14.54.27; author mhagger; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2007.01.20.14.45.02; author mhagger; state Exp; branches; next ; 1.2.2.1 date 2007.01.20.14.56.46; author mhagger; state Exp; branches; next ; 1.2.4.1 date 2007.01.20.14.57.11; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Revisions 1.2 @ text @1.2 @ 1.2.4.1 log @Shared commit message @ text @d1 1 a1 1 BRANCH4 commit @ 1.2.2.1 log @Shared commit message @ text @d1 1 a1 1 BRANCH3 commit @ 1.1 log @Adding two files @ text @d1 1 @ cvs2svn-2.5.0/test-data/main-cvsrepos/0000775000175100017510000000000013206450463020653 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/cvs2git.options0000664000175100017510000000035012203665125023646 0ustar mhaggermhagger00000000000000# (Be in -*- mode: python; coding: utf-8 -*- mode.) # An options file to test converting to git. # Actually, the example file cvs2git-example.options is fully # functional, so we just load it: execfile('cvs2git-example.options') cvs2svn-2.5.0/test-data/main-cvsrepos/cvs2hg.options0000664000175100017510000000035412203665125023465 0ustar mhaggermhagger00000000000000# (Be in -*- mode: python; coding: utf-8 -*- mode.) # An options file to test converting to Mercurial. # Actually, the example file cvs2hg-example.options is fully # functional, so we just load it: execfile('cvs2hg-example.options') cvs2svn-2.5.0/test-data/main-cvsrepos/cvs2svn-multiproject.options0000664000175100017510000000201312203665125026406 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # As a partial check that the example options file is functional, we # use it as the basis for this test. We only need to overwrite the # output option to get the output repository in the location expected # by the test infrastructure. import os execfile('cvs2svn-example.options') ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/main--options=cvs2svn-multiproject.options-svnrepos', ) ctx.cross_project_commits = False ctx.cross_branch_commits = False run_options.clear_projects() for project in [ 'full-prune', 'full-prune-reappear', 'interleaved', 'partial-prune', 'proj', 'single-files', ]: run_options.add_project( os.path.join(r'test-data/main-cvsrepos', project), trunk_path='%s/trunk' % (project,), branches_path='%s/branches' % (project,), tags_path='%s/tags' % (project,), initial_directories=['%s/releases' % (project,)], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/main-cvsrepos/proj/0000775000175100017510000000000013206450463021625 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/default,v0000664000175100017510000000265312203665125023443 0ustar mhaggermhagger00000000000000head 1.2; access; symbols B_SPLIT:1.2.0.4 B_MIXED:1.2.0.2 T_MIXED:1.2 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.2.2.1 date 2003.05.23.00.31.36; author jrandom; state Exp; branches; next ; 1.2.4.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @This is an example file description.@ 1.2 log @Second commit to proj, affecting all 7 files. @ text @This is the file `default' in the top level of the project. Every directory in the `proj' project has a file named `default'. This line was added in the second commit (affecting all 7 files). @ 1.2.4.1 log @First change on branch B_SPLIT. This change excludes sub3/default, because it was not part of this commit, and sub1/subsubB/default, which is not even on the branch yet. @ text @a5 2 First change on branch B_SPLIT. @ 1.2.2.1 log @Modify three files, on branch B_MIXED. @ text @a5 2 This line was added on branch B_MIXED only (affecting 3 files). @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub3/0000775000175100017510000000000013206450463022501 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub3/default,v0000664000175100017510000000305312203665125024312 0ustar mhaggermhagger00000000000000head 1.3; access; symbols B_SPLIT:1.3.0.2 B_MIXED:1.2.0.2 T_MIXED:1.2 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2003.05.23.00.15.26; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.3.2.1 date 2003.06.03.04.33.13; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Second commit to proj, affecting all 7 files. @ text @This is sub3/default. Every directory in the `proj' project has a file named `default'. This line was added by the first commit (affecting two files). This line was added in the second commit (affecting all 7 files). @ 1.3.2.1 log @This change affects sub3/default and sub1/subsubB/default, on branch B_SPLIT. Note that the latter file did not even exist on this branch until after some other files had had revisions committed on B_SPLIT. @ text @a7 4 This change affects sub3/default and sub1/subsubB/default, on branch B_SPLIT. Note that the latter file did not even exist on this branch until after some other files had had revisions committed on B_SPLIT. @ 1.2 log @First commit to proj, affecting two files. @ text @d6 2 @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/0000775000175100017510000000000013206450463022500 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/subsubA/0000775000175100017510000000000013206450463024104 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/subsubA/default,v0000664000175100017510000000255112203665125025717 0ustar mhaggermhagger00000000000000head 1.2; access; symbols B_SPLIT:1.2.0.2 B_MIXED:1.1.0.2 T_MIXED:1.1 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1 1.1.2.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.1.2.1 date 2003.05.23.00.31.36; author jrandom; state Exp; branches; next ; 1.2.2.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Second commit to proj, affecting all 7 files. @ text @This is sub2/subsub2/default. Every directory in the `proj' project has a file named `default'. This line was added in the second commit (affecting all 7 files). @ 1.2.2.1 log @First change on branch B_SPLIT. This change excludes sub3/default, because it was not part of this commit, and sub1/subsubB/default, which is not even on the branch yet. @ text @a5 2 First change on branch B_SPLIT. @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.2.1 log @Modify three files, on branch B_MIXED. @ text @a3 2 This line was added on branch B_MIXED only (affecting 3 files). @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/default,v0000664000175100017510000000265212203665125024315 0ustar mhaggermhagger00000000000000head 1.3; access; symbols B_SPLIT:1.3.0.2 B_MIXED:1.2.0.2 T_MIXED:1.2 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2003.05.23.00.48.51; author jrandom; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.3.2.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @A single commit affecting one file on branch B_MIXED and one on trunk. @ text @This is sub2/default. Every directory in the `proj' project has a file named `default'. This line was added in the second commit (affecting all 7 files). The same commit added these two lines here on trunk, and two similar lines to ./branch_B_MIXED_only on branch B_MIXED. @ 1.3.2.1 log @First change on branch B_SPLIT. This change excludes sub3/default, because it was not part of this commit, and sub1/subsubB/default, which is not even on the branch yet. @ text @a8 2 First change on branch B_SPLIT. @ 1.2 log @Second commit to proj, affecting all 7 files. @ text @d6 3 @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/Attic/0000775000175100017510000000000013206450463023544 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub2/Attic/branch_B_MIXED_only,v0000664000175100017510000000136512203665125027422 0ustar mhaggermhagger00000000000000head 1.1; access; symbols B_MIXED:1.1.0.2; locks; strict; comment @# @; 1.1 date 2003.05.23.00.25.26; author jrandom; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2003.05.23.00.25.26; author jrandom; state Exp; branches; next 1.1.2.2; 1.1.2.2 date 2003.05.23.00.48.51; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @file branch_B_MIXED_only was initially added on branch B_MIXED. @ text @@ 1.1.2.1 log @Add a file on branch B_MIXED. @ text @a0 1 This file was added on branch B_MIXED. It never existed on trunk. @ 1.1.2.2 log @A single commit affecting one file on branch B_MIXED and one on trunk. @ text @a1 3 The same commit added these two lines here on branch B_MIXED, and two similar lines to ./default on trunk. @ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/0000775000175100017510000000000013206450463022477 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/subsubA/0000775000175100017510000000000013206450463024103 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/subsubA/default,v0000664000175100017510000000253612203665125025721 0ustar mhaggermhagger00000000000000head 1.3; access; symbols B_SPLIT:1.3.0.4 B_MIXED:1.3.0.2 T_MIXED:1.3 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.3.4.1; next 1.2; 1.2 date 2003.05.23.00.15.26; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.3.4.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Second commit to proj, affecting all 7 files. @ text @This is sub1/subsubA/default. Every directory in the `proj' project has a file named `default'. This line was added by the first commit (affecting two files). This line was added in the second commit (affecting all 7 files). @ 1.3.4.1 log @First change on branch B_SPLIT. This change excludes sub3/default, because it was not part of this commit, and sub1/subsubB/default, which is not even on the branch yet. @ text @a7 2 First change on branch B_SPLIT. @ 1.2 log @First commit to proj, affecting two files. @ text @d6 2 @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/subsubB/0000775000175100017510000000000013206450463024104 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/subsubB/default,v0000664000175100017510000000367112203665125025723 0ustar mhaggermhagger00000000000000head 1.3; access; symbols B_SPLIT:1.3.0.2 B_MIXED:1.2.0.2 T_MIXED:1.2 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2003.06.03.04.29.14; author jrandom; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.3.2.1 date 2003.06.03.04.33.13; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @A trunk change to sub1/subsubB/default. This was committed about an hour after an earlier change that affected most files on branch B_SPLIT. This file is not on that branch yet, but after this commit, we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist at the time the rest of B_SPLIT was created. @ text @This is sub1/subsubB/default. Every directory in the `proj' project has a file named `default'. This line was added in the second commit (affecting all 7 files). This bit was committed on trunk about an hour after an earlier change to everyone else on branch B_SPLIT. Afterwards, we'll finally branch this file to B_SPLIT, but rooted in a revision that didn't exist at the time the rest of B_SPLIT was created. @ 1.3.2.1 log @This change affects sub3/default and sub1/subsubB/default, on branch B_SPLIT. Note that the latter file did not even exist on this branch until after some other files had had revisions committed on B_SPLIT. @ text @a10 4 This change affects sub3/default and sub1/subsubB/default, on branch B_SPLIT. Note that the latter file did not even exist on this branch until after some other files had had revisions committed on B_SPLIT. @ 1.2 log @Second commit to proj, affecting all 7 files. @ text @d6 5 @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/sub1/default,v0000664000175100017510000000254112203665125024311 0ustar mhaggermhagger00000000000000head 1.2; access; symbols B_SPLIT:1.2.0.4 B_MIXED:1.2.0.2 T_MIXED:1.2 B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 B_FROM_INITIALS:1.1.1.1.0.2 T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 T_ALL_INITIAL_FILES:1.1.1.1 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.2.2.1 date 2003.05.23.00.31.36; author jrandom; state Exp; branches; next ; 1.2.4.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Second commit to proj, affecting all 7 files. @ text @This is sub1/default. Every directory in the `proj' project has a file named `default'. This line was added in the second commit (affecting all 7 files). @ 1.2.4.1 log @First change on branch B_SPLIT. This change excludes sub3/default, because it was not part of this commit, and sub1/subsubB/default, which is not even on the branch yet. @ text @a5 2 First change on branch B_SPLIT. @ 1.2.2.1 log @Modify three files, on branch B_MIXED. @ text @a5 2 This line was added on branch B_MIXED only (affecting 3 files). @ 1.1 log @Initial revision @ text @d4 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/proj/README0000664000175100017510000000452112203665125022506 0ustar mhaggermhagger00000000000000(This README file is not an RCS ,v file, so cvs2svn won't notice it.) This directory, the `proj' project, is for testing cvs2svn's ability to group revisions correctly along tags and branches. Here is its history: 1. The initial import (revision 1.1 of everybody) created a directory structure with a file named `default' in each dir: ./ default sub1/default subsubA/default subsubB/default sub2/default subsubA/default sub3/default 2. Then tagged everyone with T_ALL_INITIAL_FILES. 3. Then tagged everyone except sub1/subsubB/default with T_ALL_INITIAL_FILES_BUT_ONE. 4. Then created branch B_FROM_INITIALS on everyone. 5. Then created branch B_FROM_INITIALS_BUT_ONE on everyone except /sub1/subsubB/default. 6. Then committed modifications to two files: sub3/default, and sub1/subsubA/default. 7. Then committed a modification to all 7 files. 8. Then backdated sub3/default to revision 1.2, and sub2/subsubA/default to revision 1.1, and tagged with T_MIXED. 9. Same as 8, but tagged with -b to create branch B_MIXED. 10. Switched the working copy to B_MIXED, and added sub2/branch_B_MIXED_only. (That's why the RCS file is in sub2/Attic/ -- it never existed on trunk.) 11. In one commit, modified default, sub1/default, and sub2/subsubA/default, on branch B_MIXED. 12. Did "cvs up -A" on sub2/default, then in one commit, made a change to sub2/default and sub2/branch_B_MIXED_only. So this commit should be spread between the branch and the trunk. 13. Do "cvs up -A" to get everyone back to trunk, then make a new branch B_SPLIT on everyone except sub1/subsubB/default,v. 14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear) and commit a change that affects everyone except sub3/default. 15. An hour or so later, "cvs up -A" to get sub1/subsubB/default back, then commit a change on that file, on trunk. (It's important that this change happened after the previous commits on B_SPLIT.) 16. Branch sub1/subsubB/default to B_SPLIT, then "cvs up -r B_SPLIT" to switch the whole working copy to the branch. 17. Commit a change on B_SPLIT, to sub1/subsubB/default and sub3/default. cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune/0000775000175100017510000000000013206450463022744 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune/Attic/0000775000175100017510000000000013206450463024010 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune/Attic/second,v0000664000175100017510000000112712203665125025450 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 96.08.20.23.53.47; author jrandom; state dead; branches; next 1.1; 1.1 date 95.03.31.07.44.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 95.03.31.07.44.02; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Remove the file 'second'. Since 'first' was already removed, removing 'second' empties the directory, so the directory itself gets pruned. @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Original sources from CVS-1.4A2 munged to fit our directory structure. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune/Attic/first,v0000664000175100017510000000121212203665125025317 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @; @; 1.3 date 95.12.30.18.37.22; author jrandom; state dead; branches; next 1.2; 1.2 date 95.12.11.00.27.53; author jrandom; state dead; branches; next 1.1; 1.1 date 93.06.18.05.46.07; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 93.06.18.05.46.08; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Remove the file 'first' again, which should have no effect. @ text @@ 1.2 log @Remove the file 'first', for the first time. (Note that its sibling 'second' still exists.) @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/0000775000175100017510000000000013206450463023436 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/permanent,v0000664000175100017510000000075612203665125025623 0ustar mhaggermhagger00000000000000head 1.1; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.1 date 94.06.18.05.46.08; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 94.06.18.05.46.08; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This file was added in between the addition of sub/first and sub/second, to demonstrate that when those two are both removed, the pruning stops with sub/. @ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/sub/0000775000175100017510000000000013206450463024227 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/sub/Attic/0000775000175100017510000000000013206450463025273 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/sub/Attic/second,v0000664000175100017510000000112712203665125026733 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 96.08.20.23.53.47; author jrandom; state dead; branches; next 1.1; 1.1 date 95.03.31.07.44.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 95.03.31.07.44.02; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Remove the file 'second'. Since 'first' was already removed, removing 'second' empties the directory, so the directory itself gets pruned. @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Original sources from CVS-1.4A2 munged to fit our directory structure. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/partial-prune/sub/Attic/first,v0000664000175100017510000000121212203665125026602 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @; @; 1.3 date 95.12.30.18.37.22; author jrandom; state dead; branches; next 1.2; 1.2 date 95.12.11.00.27.53; author jrandom; state dead; branches; next 1.1; 1.1 date 93.06.18.05.46.07; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 93.06.18.05.46.08; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Remove the file 'first' again, which should have no effect. @ text @@ 1.2 log @Remove the file 'first', for the first time. (Note that its sibling 'second' still exists.) @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/cvs2svn.options0000664000175100017510000000064312203665125023676 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # As a partial check that the example options file is functional, we # use it as the basis for this test. We only need to overwrite the # output option to get the output repository in the location expected # by the test infrastructure. execfile('cvs2svn-example.options') ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/main--options=cvs2svn.options-svnrepos', ) cvs2svn-2.5.0/test-data/main-cvsrepos/cvs2svn-crossproject.options0000664000175100017510000000172012203665125026411 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # As a partial check that the example options file is functional, we # use it as the basis for this test. We only need to overwrite the # output option to get the output repository in the location expected # by the test infrastructure. import os execfile('cvs2svn-example.options') ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/main--options=cvs2svn-crossproject.options-svnrepos', ) ctx.cross_project_commits = True ctx.cross_branch_commits = False run_options.clear_projects() for project in [ 'full-prune', 'full-prune-reappear', 'interleaved', 'partial-prune', 'proj', 'single-files', ]: run_options.add_project( os.path.join(r'test-data/main-cvsrepos', project), trunk_path='%s/trunk' % (project,), branches_path='%s/branches' % (project,), tags_path='%s/tags' % (project,), symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/0000775000175100017510000000000013206450463023234 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/single-double-quote",v0000664000175100017510000000057012203665125027350 0ustar mhaggermhagger00000000000000head 1.2; access; symbols after:1.2; locks maxb:1.2; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @hello modified after checked in @ 1.1 log @*** empty log message *** @ text @d2 2 @ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/"double-double-quotes",v0000664000175100017510000000057012203665125027566 0ustar mhaggermhagger00000000000000head 1.2; access; symbols after:1.2; locks maxb:1.2; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @hello modified after checked in @ 1.1 log @*** empty log message *** @ text @d2 2 @ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/space fname,v0000664000175100017510000000065612203665125025571 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks ; strict; comment @# @; 1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @Just a test for spaces in the file name. @ 1.1.1.1 log @imported @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/twoquick,v0000664000175100017510000000057012203665125025270 0ustar mhaggermhagger00000000000000head 1.2; access; symbols after:1.2; locks maxb:1.2; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @hello modified after checked in @ 1.1 log @*** empty log message *** @ text @d2 2 @ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/can't-avoid-quotes,v0000664000175100017510000000057012203665125027034 0ustar mhaggermhagger00000000000000head 1.2; access; symbols after:1.2; locks maxb:1.2; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @hello modified after checked in @ 1.1 log @*** empty log message *** @ text @d2 2 @ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/quotin'-in-dirname/0000775000175100017510000000000013206450463026643 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/quotin'-in-dirname/foo,v0000664000175100017510000000057012203665125027614 0ustar mhaggermhagger00000000000000head 1.2; access; symbols after:1.2; locks maxb:1.2; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @hello modified after checked in @ 1.1 log @*** empty log message *** @ text @d2 2 @ cvs2svn-2.5.0/test-data/main-cvsrepos/single-files/attr-exec,v0000775000175100017510000000065012203665125025320 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks ; strict; comment @# @; 1.1 date 2003.01.25.13.43.57; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.01.25.13.43.57; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @#!/bin/sh echo Hello world! @ 1.1.1.1 log @initial checkin @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/0000775000175100017510000000000013206450463024541 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/appears-later,v0000664000175100017510000000070412203665125027466 0ustar mhaggermhagger00000000000000head 1.1; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.1 date 2003.06.10.20.19.48; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.06.10.20.19.48; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This file is added to a directory that had earlier been pruned, to demonstrate that the directory reappears. @ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/sub/0000775000175100017510000000000013206450463025332 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/sub/Attic/0000775000175100017510000000000013206450463026376 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/sub/Attic/second,v0000664000175100017510000000112712203665125030036 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 96.08.20.23.53.47; author jrandom; state dead; branches; next 1.1; 1.1 date 95.03.31.07.44.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 95.03.31.07.44.02; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Remove the file 'second'. Since 'first' was already removed, removing 'second' empties the directory, so the directory itself gets pruned. @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Original sources from CVS-1.4A2 munged to fit our directory structure. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/full-prune-reappear/sub/Attic/first,v0000664000175100017510000000121212203665125027705 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @; @; 1.3 date 95.12.30.18.37.22; author jrandom; state dead; branches; next 1.2; 1.2 date 95.12.11.00.27.53; author jrandom; state dead; branches; next 1.1; 1.1 date 93.06.18.05.46.07; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 93.06.18.05.46.08; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Remove the file 'first' again, which should have no effect. @ text @@ 1.2 log @Remove the file 'first', for the first time. (Note that its sibling 'second' still exists.) @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/0000775000175100017510000000000013206450463023155 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/4,v0000664000175100017510000000110012203665125023474 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing numbers only. @ text @This is file 4, always committed with other numbers, never with letters. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/1,v0000664000175100017510000000110012203665125023471 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing numbers only. @ text @This is file 1, always committed with other numbers, never with letters. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/5,v0000664000175100017510000000110012203665125023475 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing numbers only. @ text @This is file 5, always committed with other numbers, never with letters. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/d,v0000664000175100017510000000110012203665125023554 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing letters only. @ text @This is file d, always committed with other letters, never with numbers. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/3,v0000664000175100017510000000110012203665125023473 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing numbers only. @ text @This is file 3, always committed with other numbers, never with letters. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/b,v0000664000175100017510000000110012203665125023552 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing letters only. @ text @This is file b, always committed with other letters, never with numbers. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/c,v0000664000175100017510000000110012203665125023553 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing letters only. @ text @This is file c, always committed with other letters, never with numbers. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/e,v0000664000175100017510000000110012203665125023555 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing letters only. @ text @This is file e, always committed with other letters, never with numbers. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/2,v0000664000175100017510000000110012203665125023472 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing numbers only. @ text @This is file 2, always committed with other numbers, never with letters. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/main-cvsrepos/interleaved/a,v0000664000175100017510000000110012203665125023551 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2003.06.03.00.20.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Committing letters only. @ text @This is file a, always committed with other letters, never with numbers. A random change on trunk. @ 1.1 log @Initial revision @ text @d2 2 @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/add-cvsignore-to-branch-cvsrepos/0000775000175100017510000000000013206450463024327 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-cvsignore-to-branch-cvsrepos/dir/0000775000175100017510000000000013206450463025105 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-cvsignore-to-branch-cvsrepos/dir/file.txt,v0000664000175100017510000000044612203665124027031 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.01.28.12.14.36; author author8; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2004.05.03.15.31.02; author author1; state Exp; branches; next ; desc @@ 1.1 log @@ text @@ 1.1.2.1 log @@ text @@ cvs2svn-2.5.0/test-data/add-cvsignore-to-branch-cvsrepos/dir/.cvsignore,v0000664000175100017510000000027012203665123027343 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.09.30.09.26.41; author author11; state Exp; branches; next ; desc @@ 1.1 log @@ text @*.o@ cvs2svn-2.5.0/test-data/enroot-race-obo-cvsrepos/0000775000175100017510000000000013206450463022722 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/enroot-race-obo-cvsrepos/file,v0000664000175100017510000000030412203665124024021 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.01.01.12.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/split-branch-cvsrepos/0000775000175100017510000000000013206450463022315 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/split-branch-cvsrepos/module/0000775000175100017510000000000013206450463023602 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/split-branch-cvsrepos/module/branched-from-trunk,v0000664000175100017510000000052712203665125027642 0ustar mhaggermhagger00000000000000head 1.1; access; symbols demo-node-0:1.1.0.2; locks; strict; comment @;; @; 1.1 date 98.02.19.21.28.43; author russel; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 98.02.25.18.56.53; author adam; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.2.1 log @This revision will go wrong @ text @@ cvs2svn-2.5.0/test-data/split-branch-cvsrepos/module/branched-from-branch,v0000664000175100017510000000076412203665125027737 0ustar mhaggermhagger00000000000000head 1.1; access; symbols demo-node-0:1.1.1.1.0.2 first_working:1.1.1; locks; strict; comment @;; @; 1.1 date 98.02.08.01.48.39; author russel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 98.02.08.01.48.39; author russel; state Exp; branches 1.1.1.1.2.1; next ; 1.1.1.1.2.1 date 98.02.25.18.56.53; author adam; state dead; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Create a branch @ text @@ 1.1.1.1.2.1 log @This revision will go wrong @ text @@ cvs2svn-2.5.0/test-data/split-branch-cvsrepos/README0000664000175100017510000000076112203665125023200 0ustar mhaggermhagger00000000000000This tree is for reproducing http://subversion.tigris.org/issues/show_bug.cgi?id=1421 It is based on issue-1421-small-cvsrepos.tgz from Blair Zajac. Previously, this directory contained a different set of test data, which began to pass after a partial fix in r6534. However, similar errors still occurred with other data sets. The issue was reopened, and the test data changed to more reliably test the bug. It continued to fail until the final fix in r7006. See the issue for more details. cvs2svn-2.5.0/test-data/issue-106-cvsrepos/0000775000175100017510000000000013206450463021363 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/issue-106-cvsrepos/a.txt,v0000664000175100017510000000053112203665124022603 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.15; access; symbols branch:1.1.15; locks; strict; comment @# @; 1.1 date 2002.12.13.08.30.00; author joeschmo; state Exp; branches 1.1.15.1; next ; 1.1.15.1 date 2002.12.13.08.30.00; author joeschmo; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.15.1 log @a.txt 1.1.15.1 @ text @@ cvs2svn-2.5.0/test-data/issue-106-cvsrepos/d/0000775000175100017510000000000013206450463021606 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/issue-106-cvsrepos/d/b.txt,v0000664000175100017510000000053112203665124023027 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.15; access; symbols branch:1.1.15; locks; strict; comment @# @; 1.1 date 2002.12.13.08.29.37; author joeschmo; state Exp; branches 1.1.15.1; next ; 1.1.15.1 date 2002.12.13.08.29.37; author joeschmo; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.15.1 log @b.txt 1.1.15.1 @ text @@ cvs2svn-2.5.0/test-data/issue-99-cvsrepos/0000775000175100017510000000000013206450463021316 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/issue-99-cvsrepos/file2,v0000664000175100017510000000100512203665125022477 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols orinoco_0_13d:1.1.1.2 orinoco_0_13c:1.1.1.1 David:1.1.1; locks; strict; comment @ * @; expand @o@; 1.1 date 2003.07.08.20.56.26; author proski; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.07.08.20.56.26; author proski; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2003.07.08.20.56.50; author proski; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Version 0.13c @ text @@ 1.1.1.2 log @Version 0.13d @ text @@ cvs2svn-2.5.0/test-data/issue-99-cvsrepos/file1,v0000664000175100017510000000053212203665125022502 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols David:1.1.1; locks; strict; comment @# @; expand @o@; 1.1 date 2003.07.08.20.57.54; author proski; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.07.08.20.57.54; author proski; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Version 0.13e @ text @@ cvs2svn-2.5.0/test-data/unlabeled-branch-cvsrepos/0000775000175100017510000000000013206450463023115 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/unlabeled-branch-cvsrepos/cvs2svn-ignore.options0000664000175100017510000000143212203665125027416 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # Try ignoring an unlabeled branch using a SymbolMapper (it should # fail). from cvs2svn_lib.symbol_transform import SymbolMapper execfile('cvs2svn-example.options') name = 'unlabeled-branch' ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/%s--options=cvs2svn-ignore.options-svnrepos' % (name,), ) run_options.clear_projects() filename = 'test-data/%s-cvsrepos/proj/a.txt,v' % (name,) symbol_mapper = SymbolMapper([ (filename, 'unlabeled-1.1.4', '1.1.4', None), ]) run_options.add_project( r'test-data/%s-cvsrepos' % (name,), trunk_path='trunk', branches_path='branches', tags_path='tags', symbol_transforms=[ symbol_mapper, ], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/unlabeled-branch-cvsrepos/proj/0000775000175100017510000000000013206450463024067 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/unlabeled-branch-cvsrepos/proj/a.txt,v0000664000175100017510000000077412203665125025321 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2009.08.21.23.07.52; author mhagger; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2009.08.21.23.08.54; author mhagger; state Exp; branches; next ; 1.1.4.1 date 2009.08.21.23.10.11; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Initial commit. @ text @1.1 @ 1.1.4.1 log @Commit on unlabeled branch. @ text @d1 1 a1 1 1.1.4.1 @ 1.1.2.1 log @Commit on BRANCH. @ text @d1 1 a1 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/tag-with-no-revision-cvsrepos/0000775000175100017510000000000013206450463023721 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tag-with-no-revision-cvsrepos/file.txt,v0000664000175100017510000000061712203665125025646 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG:1.1.2.1 SUBBRANCH:1.1.2.1.0.2; locks; strict; comment @// @; 1.3 date 2004.03.19.19.47.32; author joeschmo; state dead; branches; next 1.2; 1.2 date 98.06.22.21.46.37; author joeschmo; state Exp; branches; next 1.1; 1.1 date 98.06.05.10.40.26; author joeschmo; state dead; branches; next ; desc @@ 1.3 log @@ text @@ 1.2 log @@ text @@ 1.1 log @@ text @@ cvs2svn-2.5.0/test-data/tag-with-no-revision-cvsrepos/README0000664000175100017510000000026512203665125024603 0ustar mhaggermhagger00000000000000This repository is corrupt in a way that appears rather common among our users. It contains a tag and a branch that refer to revision 1.1.2.1, but revision 1.1.2.1 does not exist. cvs2svn-2.5.0/test-data/resync-pass2-pull-forward-cvsrepos/0000775000175100017510000000000013206450463024674 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-pass2-pull-forward-cvsrepos/file2.txt,v0000664000175100017510000000041712203665125026701 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; 1.2 date 90.04.19.15.10.41; author user1; state Exp; branches; next 1.1; 1.1 date 90.04.19.15.10.41; author user1; state Exp; branches; next ; desc @@ 1.2 log @Summary: foo @ text @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/resync-pass2-pull-forward-cvsrepos/file1.txt,v0000664000175100017510000000041712203665125026700 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; 1.2 date 90.04.19.15.10.30; author user1; state Exp; branches; next 1.1; 1.1 date 90.04.19.15.10.29; author user1; state Exp; branches; next ; desc @@ 1.2 log @Summary: foo @ text @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/0000775000175100017510000000000013206450463023101 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/powerpc/0000775000175100017510000000000013206450463024560 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/powerpc/file1.txt,v0000664000175100017510000000060612203665125026564 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @ * @; 1.3 date 2002.06.24.05.48.14; author author49; state Exp; branches; next 1.2; 1.2 date 2000.04.26.02.46.59; author author19; state dead; branches; next 1.1; 1.1 date 2000.04.21.20.33.29; author author19; state Exp; branches; next ; desc @@ 1.3 log @log 2492@ text @@ 1.2 log @log 4326@ text @@ 1.1 log @log 4129@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/powerpc/bits/0000775000175100017510000000000013206450463025521 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/powerpc/bits/Attic/0000775000175100017510000000000013206450463026565 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror-cvsrepos/powerpc/bits/Attic/file2.txt,v0000664000175100017510000000043212203665125030567 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @ * @; 1.2 date 2002.06.24.05.48.16; author author49; state dead; branches; next 1.1; 1.1 date 2000.04.26.02.51.11; author author19; state Exp; branches; next ; desc @@ 1.2 log @log 2492@ text @@ 1.1 log @log 4326@ text @@ cvs2svn-2.5.0/test-data/strange-default-branch-cvsrepos/0000775000175100017510000000000013206450463024247 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/strange-default-branch-cvsrepos/file5347,v0000664000175100017510000000175712203665125025707 0ustar mhaggermhagger00000000000000head 1.2; branch 1.2.4.3.2.1.2; access; symbols symbol1:1.2.0.4 symbol2:1.2.4.3.0.2 symbol3:1.2.4.3.2.1.0.2; locks; strict; comment @# @; 1.2 date 2003.08.18.09.31.37; author author9; state Exp; branches 1.2.4.1; next 1.1; 1.1 date 2003.07.03.13.58.23; author author9; state Exp; branches; next ; 1.2.4.1 date 2003.09.29.07.36.35; author author8; state Exp; branches; next 1.2.4.2; 1.2.4.2 date 2003.10.01.11.47.47; author author8; state Exp; branches; next 1.2.4.3; 1.2.4.3 date 2003.10.02.10.09.19; author author8; state Exp; branches 1.2.4.3.2.1; next ; 1.2.4.3.2.1 date 2003.11.18.17.40.18; author author9; state Exp; branches 1.2.4.3.2.1.2.1; next ; 1.2.4.3.2.1.2.1 date 2003.12.29.13.02.08; author author9; state Exp; branches; next ; desc @@ 1.2 log @log 12594@ text @@ 1.2.4.1 log @log 12595@ text @@ 1.2.4.2 log @log 12596@ text @@ 1.2.4.3 log @log 12597@ text @@ 1.2.4.3.2.1 log @log 12598@ text @@ 1.2.4.3.2.1.2.1 log @log 12560@ text @@ 1.1 log @log 12506@ text @@ cvs2svn-2.5.0/test-data/missing-vendor-branch-cvsrepos/0000775000175100017510000000000013206450463024126 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/missing-vendor-branch-cvsrepos/file,v0000664000175100017510000000030212203665125025224 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2006.09.06.19.14.41; author author3; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/default-branch-and-1-2-cvsrepos/0000775000175100017510000000000013206450463023643 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/default-branch-and-1-2-cvsrepos/proj/0000775000175100017510000000000013206450463024615 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/default-branch-and-1-2-cvsrepos/proj/a.txt,v0000664000175100017510000000231412203665124026036 0ustar mhaggermhagger00000000000000head 1.2; branch 1.1.1; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.2 date 2004.02.09.15.43.14; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.2 log @First regular commit, to a.txt, on vtag-3. @ text @This is vtag-3 (on vbranchA) of a.txt. A regular change to a.txt. @ 1.1 log @Initial revision @ text @d1 2 a2 1 This is vtag-1 (on vbranchA) of a.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of a.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of a.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of a.txt. @ cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/0000775000175100017510000000000013206450463024747 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/proj/0000775000175100017510000000000013206450463025721 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/proj/a.txt,v0000664000175100017510000000033612203665124027144 0ustar mhaggermhagger00000000000000head 1.1; access; symbols TAG-ALL-FILES:1.1; locks; strict; comment @# @; 1.1 date 2004.02.19.15.43.13; author fitz; state Exp; branches; next ; desc @@ 1.1 log @Committing files a.txt and b.txt on trunk. @ text @@ cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/proj/trunk-changed-later.txt,v0000664000175100017510000000164212203665124032564 0ustar mhaggermhagger00000000000000head 1.2; access; symbols TAG-ALL-FILES:1.1 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.2 date 2004.02.19.15.43.13; author fitz; state Exp; branches; next 1.1; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next ; desc @@ 1.2 log @Commit to trunk-changed-later.txt later in its life @ text @This is a change to trunk-changed-later.txt intended to generate multiple closings (since it was on a default branch prior to this commit). @ 1.1 log @Initial revision @ text @d1 3 a3 1 This is vtag-1 (on vbranchA) of trunk-changed-later.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of trunk-changed-later.txt. @ cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/proj/b.txt,v0000664000175100017510000000033612203665124027145 0ustar mhaggermhagger00000000000000head 1.1; access; symbols TAG-ALL-FILES:1.1; locks; strict; comment @# @; 1.1 date 2004.02.19.15.43.13; author fitz; state Exp; branches; next ; desc @@ 1.1 log @Committing files a.txt and b.txt on trunk. @ text @@ cvs2svn-2.5.0/test-data/invalid-closings-on-trunk-cvsrepos/proj/deleted-on-vendor-branch.txt,v0000664000175100017510000000122112203665124033464 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols TAG-ALL-FILES:1.1 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state dead; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of deleted-on-vendor-branch.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of deleted-on-vendor-branch.txt. @ cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/0000775000175100017510000000000013206450463023164 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/0000775000175100017510000000000013206450463024136 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/file1.txt,v0000664000175100017510000000034712203665125026144 0ustar mhaggermhagger00000000000000head 1.1; access; symbols tree-serialize-branch:1.1.0.2 tree-serialize-branchpoint:1.1; locks; strict; comment @# @; 1.1 date 2000.07.22.08.08.22; author author1; state Exp; branches; next ; desc @@ 1.1 log @log 1@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/subdir/0000775000175100017510000000000013206450463025426 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/subdir/file2.txt,v0000664000175100017510000000056012203665125027432 0ustar mhaggermhagger00000000000000head 1.1; access; symbols tree-serialize-branch:1.1.0.2 tree-serialize-branchpoint:1.1 NET:1.1.1; locks; strict; comment @# @; 1.1 date 99.05.04.19.30.26; author author2; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 99.05.04.19.30.26; author author2; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @log 5@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/subdir/Attic/0000775000175100017510000000000013206450463026472 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/subdir/Attic/file4.txt,v0000664000175100017510000000063012203665125030476 0ustar mhaggermhagger00000000000000head 1.2; access; symbols NET:1.1.1; locks; strict; comment @# @; 1.2 date 99.05.05.10.04.36; author author2; state dead; branches; next 1.1; 1.1 date 99.05.04.19.30.26; author author2; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 99.05.04.19.30.26; author author2; state Exp; branches; next ; desc @@ 1.2 log @log 6@ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @log 5@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror3-cvsrepos/proj/subdir/file3.txt,v0000664000175100017510000000052112203665125027430 0ustar mhaggermhagger00000000000000head 1.1; access; symbols tree-serialize-branch:1.1.0.2 NET:1.1.1; locks; strict; comment @ * @; 1.1 date 99.05.04.19.30.27; author author2; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 99.05.04.19.30.27; author author2; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @log 5@ text @@ cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/0000775000175100017510000000000013206450463022201 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/symbol-mess-parent-hints-invalid.txt0000664000175100017510000000016012203665125031246 0ustar mhaggermhagger000000000000000 MOSTLY_BRANCH branch . . 0 MOSTLY_TAG tag . . 0 BRANCH_WITH_COMMIT branch . BLOCKED_BY_BRANCH cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/makerepo.sh0000775000175100017510000000413112203665125024341 0ustar mhaggermhagger00000000000000#! /bin/sh # This is the script used to create the symbol-mess CVS repository. # (The repository is checked into svn; this script is only here for # its documentation value.) # The script should be started from the main cvs2svn directory. repo=`pwd`/test-data/symbol-mess-cvsrepos wc=`pwd`/cvs2svn-tmp/symbol-mess-wc [ -e $repo/CVSROOT ] && rm -rf $repo/CVSROOT [ -e $repo/dir ] && rm -rf $repo/dir [ -e $wc ] && rm -rf $wc cvs -d $repo init cvs -d $repo co -d $wc . cd $wc mkdir dir cvs add dir echo 'line1' >dir/file1 echo 'line1' >dir/file2 echo 'line1' >dir/file3 cvs add dir/file1 dir/file2 dir/file3 cvs commit -m 'Adding files on trunk' dir cd dir # One plain old garden-variety tag and one branch: cvs tag TAG cvs tag -b BRANCH # A branch with a commit on it: cvs tag -b BRANCH_WITH_COMMIT cvs up -r BRANCH_WITH_COMMIT echo 'line2' >>file1 cvs commit -m 'Commit on branch BRANCH_WITH_COMMIT' file1 cvs up -A # Make some symbols for testing majority rule strategy: cvs tag MOSTLY_TAG file1 file2 cvs tag -b MOSTLY_TAG file3 cvs tag -b MOSTLY_BRANCH file1 file2 cvs tag MOSTLY_BRANCH file3 # A branch that is blocked by another branch (but with no commits): cvs tag -b BLOCKED_BY_BRANCH file1 file2 file3 cvs up -r BLOCKED_BY_BRANCH echo 'line2' >>file1 cvs commit -m 'Establish branch BLOCKED_BY_BRANCH' file1 cvs tag -b BLOCKING_BRANCH cvs up -A # A branch that is blocked by another branch with a commit: cvs tag -b BLOCKED_BY_COMMIT file1 file2 file3 cvs up -r BLOCKED_BY_COMMIT echo 'line2' >>file1 cvs commit -m 'Establish branch BLOCKED_BY_COMMIT' file1 cvs tag -b BLOCKING_COMMIT cvs up -r BLOCKING_COMMIT echo 'line3' >>file1 cvs commit -m 'Committing blocking commit on BLOCKING_COMMIT' file1 cvs up -A # A branch that is blocked by an unnamed branch with a commit: cvs tag -b BLOCKED_BY_UNNAMED file1 file2 file3 cvs up -r BLOCKED_BY_UNNAMED echo 'line2' >>file1 cvs commit -m 'Establish branch BLOCKED_BY_UNNAMED' file1 cvs tag -b TEMP cvs up -r TEMP echo 'line3' >>file1 cvs commit -m 'Committing blocking commit on TEMP' file1 cvs up -A # Now delete the name from the blocking branch. cvs tag -d -B TEMP cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/symbol-mess-symbol-hints.txt0000664000175100017510000000006612203665125027643 0ustar mhaggermhagger000000000000000 MOSTLY_BRANCH branch . . 0 MOSTLY_TAG tag . . cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/symbol-mess-path-hints.txt0000664000175100017510000000032012203665125027263 0ustar mhaggermhagger00000000000000. .trunk. trunk /a/strange/trunk/path . . MOSTLY_BRANCH branch /special/branch/path . . MOSTLY_TAG tag /special/tag/path . . BRANCH_WITH_COMMIT . /special/other/branch/path . cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/symbol-mess-parent-hints-wildcards.txt0000664000175100017510000000014512203665125031577 0ustar mhaggermhagger00000000000000. MOSTLY_BRANCH branch . . . MOSTLY_TAG tag . . . BRANCH_WITH_COMMIT . . BRANCH cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/symbol-mess-parent-hints.txt0000664000175100017510000000014512203665125027625 0ustar mhaggermhagger000000000000000 MOSTLY_BRANCH branch . . 0 MOSTLY_TAG tag . . 0 BRANCH_WITH_COMMIT branch . BRANCH cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/dir/0000775000175100017510000000000013206450463022757 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/dir/file2,v0000664000175100017510000000064312203665125024147 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BLOCKED_BY_UNNAMED:1.1.0.16 BLOCKING_COMMIT:1.1.0.14 BLOCKED_BY_COMMIT:1.1.0.12 BLOCKING_BRANCH:1.1.0.10 BLOCKED_BY_BRANCH:1.1.0.8 MOSTLY_BRANCH:1.1.0.6 MOSTLY_TAG:1.1 BRANCH_WITH_COMMIT:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1; locks; strict; comment @# @; 1.1 date 2006.06.24.23.20.28; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @line1 @ cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/dir/file1,v0000664000175100017510000000262612203665125024151 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BLOCKED_BY_UNNAMED:1.1.0.12 BLOCKING_COMMIT:1.1.10.1.0.2 BLOCKED_BY_COMMIT:1.1.0.10 BLOCKING_BRANCH:1.1.8.1.0.2 BLOCKED_BY_BRANCH:1.1.0.8 MOSTLY_BRANCH:1.1.0.6 MOSTLY_TAG:1.1 BRANCH_WITH_COMMIT:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1; locks; strict; comment @# @; 1.1 date 2006.06.24.23.20.28; author mhagger; state Exp; branches 1.1.4.1 1.1.8.1 1.1.10.1 1.1.12.1; next ; 1.1.4.1 date 2006.06.24.23.20.29; author mhagger; state Exp; branches; next ; 1.1.8.1 date 2006.06.24.23.20.31; author mhagger; state Exp; branches; next ; 1.1.10.1 date 2006.06.24.23.20.33; author mhagger; state Exp; branches 1.1.10.1.2.1; next ; 1.1.10.1.2.1 date 2006.06.24.23.20.34; author mhagger; state Exp; branches; next ; 1.1.12.1 date 2006.06.24.23.20.36; author mhagger; state Exp; branches 1.1.12.1.2.1; next ; 1.1.12.1.2.1 date 2006.06.24.23.20.37; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @line1 @ 1.1.12.1 log @Establish branch BLOCKED_BY_UNNAMED @ text @a1 1 line2 @ 1.1.12.1.2.1 log @Committing blocking commit on TEMP @ text @a2 1 line3 @ 1.1.10.1 log @Establish branch BLOCKED_BY_COMMIT @ text @a1 1 line2 @ 1.1.10.1.2.1 log @Committing blocking commit on BLOCKING_COMMIT @ text @a2 1 line3 @ 1.1.8.1 log @Establish branch BLOCKED_BY_BRANCH @ text @a1 1 line2 @ 1.1.4.1 log @Commit on branch BRANCH_WITH_COMMIT @ text @a1 1 line2 @ cvs2svn-2.5.0/test-data/symbol-mess-cvsrepos/dir/file3,v0000664000175100017510000000064312203665125024150 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BLOCKED_BY_UNNAMED:1.1.0.16 BLOCKING_COMMIT:1.1.0.14 BLOCKED_BY_COMMIT:1.1.0.12 BLOCKING_BRANCH:1.1.0.10 BLOCKED_BY_BRANCH:1.1.0.8 MOSTLY_BRANCH:1.1 MOSTLY_TAG:1.1.0.6 BRANCH_WITH_COMMIT:1.1.0.4 BRANCH:1.1.0.2 TAG:1.1; locks; strict; comment @# @; 1.1 date 2006.06.24.23.20.28; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @line1 @ cvs2svn-2.5.0/test-data/symlinks-cvsrepos/0000775000175100017510000000000013206450463021600 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symlinks-cvsrepos/proj/0000775000175100017510000000000013206450463022552 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symlinks-cvsrepos/proj/file.txt,v0000664000175100017510000000024312203665125024472 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.04.08.08.10.10; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @@ text @@ cvs2svn-2.5.0/test-data/symlinks-cvsrepos/proj/README.txt0000664000175100017510000000030512203665125024245 0ustar mhaggermhagger00000000000000This directory is used by the symlinks() test. The test creates a symlink in this directory before starting cvs2svn. This README file itself is ignored by cvs2svn because it doesn't end in ",v". cvs2svn-2.5.0/test-data/symlinks-cvsrepos/proj/dir1/0000775000175100017510000000000013206450463023411 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symlinks-cvsrepos/proj/dir1/README.txt0000664000175100017510000000030512203665125025104 0ustar mhaggermhagger00000000000000This directory is used by the symlinks() test. The test creates a symlink in this directory before starting cvs2svn. This README file itself is ignored by cvs2svn because it doesn't end in ",v". cvs2svn-2.5.0/test-data/branch-from-vendor-branch-cvsrepos/0000775000175100017510000000000013206450463024653 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-vendor-branch-cvsrepos/data,v0000664000175100017510000000115212203665124025746 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols my-branch:1.1.1.1.0.2 vendor-tag:1.1.1.1 vendor-branch:1.1.1; locks; strict; comment @# @; 1.1 date 2010.04.08.15.37.56; author fosterj; state Exp; branches 1.1.1.1; next ; commitid 2i5HeSdvL0B9s8uu; 1.1.1.1 date 2010.04.08.15.37.56; author fosterj; state Exp; branches 1.1.1.1.2.1; next ; commitid 2i5HeSdvL0B9s8uu; 1.1.1.1.2.1 date 2010.04.08.15.38.58; author fosterj; state Exp; branches; next ; commitid eDJ6tPpuBwVxs8uu; desc @@ 1.1 log @Initial revision @ text @x @ 1.1.1.1 log @Test import @ text @@ 1.1.1.1.2.1 log @Branch commit @ text @d1 1 a1 1 y @ ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000cvs2svn-2.5.0/test-data/branch-from-vendor-branch-cvsrepos/branch-from-vendor-branch-symbol-hints.txtcvs2svn-2.5.0/test-data/branch-from-vendor-branch-cvsrepos/branch-from-vendor-branch-symbol-hints.tx0000664000175100017510000000052712203665124034604 0ustar mhaggermhagger00000000000000# Columns: #project_id symbol_name conversion symbol_path preferred_parent_name 0 .trunk. trunk trunk . 0 my-branch branch branches/my-branch .trunk. 0 vendor-tag exclude . . 0 vendor-branch exclude . . cvs2svn-2.5.0/test-data/revision-reorder-bug-cvsrepos/0000775000175100017510000000000013206450463024000 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/revision-reorder-bug-cvsrepos/file.txt,v0000664000175100017510000000060412203665125025721 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2003.09.11.13.57.15; author qwerty; state Exp; branches; next 1.2; 1.2 date 2003.09.11.13.57.15; author qwerty; state dead; branches; next 1.1; 1.1 date 2003.09.09.15.47.53; author qwerty; state Exp; branches; next ; desc @@ 1.3 log @x @ text @x x x x x @ 1.2 log @x @ text @d1 1 a1 1 x @ 1.1 log @x @ text @@ cvs2svn-2.5.0/test-data/unicode-log-cvsrepos/0000775000175100017510000000000013206450463022134 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/unicode-log-cvsrepos/testunicode,v0000664000175100017510000000033112203665125024643 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.02.13.21.13.21; author kylo; state Exp; branches ; next ; desc @@ 1.1 log @This is a test message with unicode: Ã¥ @ text @@ cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/0000775000175100017510000000000013206450463023377 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/0000775000175100017510000000000013206450463024651 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/empty-directory/0000775000175100017510000000000013206450463030011 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/empty-directory/README.txt0000664000175100017510000000007612203665124031510 0ustar mhaggermhagger00000000000000This empty directory should be created when b.txt is created. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/empty-directory/empty-subdirectory/0000775000175100017510000000000013206450463033663 5ustar mhaggermhagger00000000000000././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/empty-directory/empty-subdirectory/README.txtcvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/empty-directory/empty-subdirectory/README.0000664000175100017510000000014312203665124034615 0ustar mhaggermhagger00000000000000This directory should be created when its parent directory is created, namely when b.txt is added. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/direct/b.txt,v0000664000175100017510000000101312203665124026066 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG:1.3 BRANCH:1.3.0.2; locks; strict; comment @# @; 1.3 date 2010.01.17.03.34.23; author mhagger; state Exp; branches; next 1.2; commitid 0WdTFEWnGdyo3Fju; 1.2 date 2010.01.17.03.31.15; author mhagger; state dead; branches; next 1.1; commitid Ok3DlSXoWUFj2Fju; 1.1 date 2010.01.16.06.19.51; author mhagger; state Exp; branches; next ; commitid T0xGHIyHXxz90yju; desc @@ 1.3 log @Re-add b.txt. @ text @b2 @ 1.2 log @Remove b.txt. @ text @d1 1 a1 1 b @ 1.1 log @Add b.txt. @ text @@ cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/a.txt,v0000664000175100017510000000034412203665124024621 0ustar mhaggermhagger00000000000000head 1.1; access; symbols TAG:1.1 BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2010.01.16.06.17.56; author mhagger; state Exp; branches; next ; commitid 1nYTVRk8r2OuZxju; desc @@ 1.1 log @Add a.txt. @ text @a @ cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/0000775000175100017510000000000013206450463025200 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/empty-directory/0000775000175100017510000000000013206450463030340 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/empty-directory/README.txt0000664000175100017510000000014112203665124032030 0ustar mhaggermhagger00000000000000This directory should be created when the addition of c.txt triggers the creation of its parent. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/empty-directory/empty-subdirectory/0000775000175100017510000000000013206450463034212 5ustar mhaggermhagger00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/empty-directory/empty-subdirectory/README.txtcvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/empty-directory/empty-subdirectory/READM0000664000175100017510000000013112203665124034756 0ustar mhaggermhagger00000000000000This directory should be created when its parent is created, namely when c.txt is added. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/subdirectory/0000775000175100017510000000000013206450463027716 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/indirect/subdirectory/c.txt,v0000664000175100017510000000101312203665124031134 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG:1.3 BRANCH:1.3.0.2; locks; strict; comment @# @; 1.3 date 2010.01.17.03.34.55; author mhagger; state Exp; branches; next 1.2; commitid 0ur5CblrKrQz3Fju; 1.2 date 2010.01.17.03.31.45; author mhagger; state dead; branches; next 1.1; commitid 7o2iSAs9MgMu2Fju; 1.1 date 2010.01.16.06.24.25; author mhagger; state Exp; branches; next ; commitid qH19tYzVmpCI1yju; desc @@ 1.3 log @Re-add c.txt. @ text @c2 @ 1.2 log @Remove c.txt. @ text @d1 1 a1 1 c @ 1.1 log @Add c.txt. @ text @@ cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/0000775000175100017510000000000013206450463024711 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/d.txt,v0000664000175100017510000000113512203665124026135 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols VENDORTAG2:1.1.1.2 VENDORTAG:1.1.1.1 VENDORBRANCH:1.1.1; locks; strict; comment @# @; 1.1 date 2010.01.17.04.15.38; author mhagger; state Exp; branches 1.1.1.1; next ; commitid 3LFcsqdQSLZxhFju; 1.1.1.1 date 2010.01.17.04.15.38; author mhagger; state Exp; branches; next 1.1.1.2; commitid 3LFcsqdQSLZxhFju; 1.1.1.2 date 2010.01.17.04.24.25; author mhagger; state Exp; branches; next ; commitid ziMoRPJIXZOykFju; desc @@ 1.1 log @Initial revision @ text @d @ 1.1.1.1 log @Import d.txt. @ text @@ 1.1.1.2 log @Re-import d.txt. @ text @d1 1 a1 1 d2 @ cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/empty-directory/0000775000175100017510000000000013206450463030051 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/empty-directory/README.txt0000664000175100017510000000007612203665124031550 0ustar mhaggermhagger00000000000000This empty directory should be created when d.txt is created. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/empty-directory/empty-subdirectory/0000775000175100017510000000000013206450463033723 5ustar mhaggermhagger00000000000000././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/empty-directory/empty-subdirectory/README.txtcvs2svn-2.5.0/test-data/empty-directories-cvsrepos/import/empty-directory/empty-subdirectory/README.0000664000175100017510000000014312203665124034655 0ustar mhaggermhagger00000000000000This directory should be created when its parent directory is created, namely when d.txt is added. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/root-empty-directory/0000775000175100017510000000000013206450463027520 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/root-empty-directory/README.txt0000664000175100017510000000025012203665124031211 0ustar mhaggermhagger00000000000000This directory does not contain any RCS files, so if the --include-empty-directories option is used it should be created in the commit that initializes the repository. cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/root-empty-directory/empty-subdirectory/0000775000175100017510000000000013206450463033372 5ustar mhaggermhagger00000000000000././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000cvs2svn-2.5.0/test-data/empty-directories-cvsrepos/root-empty-directory/empty-subdirectory/README.txtcvs2svn-2.5.0/test-data/empty-directories-cvsrepos/root-empty-directory/empty-subdirectory/README.tx0000664000175100017510000000007512203665124034704 0ustar mhaggermhagger00000000000000This directory should be created when its parent is created. cvs2svn-2.5.0/test-data/add-on-branch2-cvsrepos/0000775000175100017510000000000013206450463022406 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-on-branch2-cvsrepos/file1,v0000664000175100017510000000053412203665124023573 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH:1.1.0.2; locks; strict; comment @# @; 1.1 date 2005.12.05.22.35.31; author gward; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2005.12.05.22.35.31; author gward; state Exp; branches; next ; desc @@ 1.1 log @file file1 was added on branch BRANCH @ text @@ 1.1.2.1 log @add file on branch @ text @@ cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/0000775000175100017510000000000013206450463024634 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/proj/0000775000175100017510000000000013206450463025606 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463026652 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/proj/Attic/file1,v0000664000175100017510000000047412203665124030042 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2007.04.08.10.09.19; author mhagger; state dead; branches; next 1.1; 1.1 date 2007.04.08.08.09.02; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @*** empty log message *** @ text @@ 1.1 log @*** empty log message *** @ text @@ cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/proj/file1/0000775000175100017510000000000013206450463026606 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/attic-directory-conflict-cvsrepos/proj/file1/file2.txt,v0000664000175100017510000000027512203665124030614 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.04.08.08.10.10; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @*** empty log message *** @ text @@ cvs2svn-2.5.0/test-data/missing-deltatext-cvsrepos/0000775000175100017510000000000013206450463023374 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/missing-deltatext-cvsrepos/file001,v0000664000175100017510000000132512203665125024721 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2003.02.14.12.21.04; author author1; state dead; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2003.02.14.12.21.04; author author1; state Exp; branches; next ; 1.1.4.1 date 2003.02.14.18.28.37; author author1; state Exp; branches; next 1.1.4.2; 1.1.4.2 date 2003.02.28.13.31.47; author author1; state dead; branches; next 1.1.4.3; 1.1.4.3 date 2003.02.28.14.25.24; author author2; state Exp; branches; next 1.1.4.4; 1.1.4.4 date 2003.05.02.21.55.01; author author3; state dead; branches; next ; desc @@ 1.1 log @log 1@ text @@ 1.1.4.1 log @log 2@ text @@ 1.1.4.2 log @log 3@ text @@ 1.1.4.3 log @log 4@ text @@ 1.1.2.1 log @log 5@ text @@ cvs2svn-2.5.0/test-data/missing-deltatext-cvsrepos/README0000664000175100017510000000030712203665125024253 0ustar mhaggermhagger00000000000000Please note that revision 1.1.4.4 of the RCS file has a "delta" but no "deltatext". cvs2svn should test for this problem and emit a suitable error. This test file was submitted by Sebastian Marek. cvs2svn-2.5.0/test-data/file-in-attic-too-cvsrepos/0000775000175100017510000000000013206450463023153 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/file-in-attic-too-cvsrepos/file.txt,v0000664000175100017510000000031312203665124025070 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2004.07.12.20.01.23; author fitz; state Exp; branches; next ; desc @@ 1.1 log @initial import @ text @This is a file. So there. @ cvs2svn-2.5.0/test-data/file-in-attic-too-cvsrepos/Attic/0000775000175100017510000000000013206450463024217 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/file-in-attic-too-cvsrepos/Attic/file.txt,v0000664000175100017510000000031312203665124026134 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2004.07.12.20.01.23; author fitz; state Exp; branches; next ; desc @@ 1.1 log @initial import @ text @This is a file. So there. @ cvs2svn-2.5.0/test-data/CVSROOT/0000775000175100017510000000000013206450463017224 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/CVSROOT/README0000664000175100017510000000113012203665123020074 0ustar mhaggermhagger00000000000000This CVSROOT/ directory is only here to convince CVS to treat the neighboring directories as CVS repository modules. Without it, CVS operations fail with an error like: cvs [checkout aborted]: .../main-cvsrepos/CVSROOT: No such file or directory Of course, CVS doesn't seem to require that there actually be any files in CVSROOT/, which kind of makes one wonder why it cares about the directory at all. Although this directly is only strictly needed when the --use-cvs option is used, cvs2svn checks that every project has an associated CVSROOT directory to avoid complicating its bookkeeping. cvs2svn-2.5.0/test-data/log-message-eols-cvsrepos/0000775000175100017510000000000013206450463023072 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/log-message-eols-cvsrepos/lottalogs,v0000664000175100017510000000067012203665125025271 0ustar mhaggermhagger00000000000000head 1.2; access ; symbols ; locks ; strict; comment @# @; 1.2 date 2003.08.28.15.00.00; author jrandom; state Exp; branches ; next 1.1; 1.1 date 2003.07.28.15.00.00; author jrandom; state Exp; branches ; next ; desc @@ 1.2 log @The CR at the end of this line should be turned into a LF. @ text @Nothing to see here. @ 1.1 log @The CRLF at the end of this line should be turned into a LF. @ text @@ cvs2svn-2.5.0/test-data/branch-delete-first-cvsrepos/0000775000175100017510000000000013206450463023551 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-delete-first-cvsrepos/README.txt0000664000175100017510000000053312203665124025246 0ustar mhaggermhagger00000000000000This repository exhibits has interesting characteristic that the very first thing that happen on a branch is that its sole file is deleted. A bug in cvs2svn caused this to delay branch creation until the end of the program (where we're finished off branches and tags), which resulted in the file's deletion from the branch never really happening. cvs2svn-2.5.0/test-data/branch-delete-first-cvsrepos/file,v0000664000175100017510000000134112203665124024652 0ustar mhaggermhagger00000000000000head 1.3; access; symbols branch-3:1.1.0.6 branch-2:1.1.0.4 branch-1:1.1.0.2 import:1.1; locks; strict; comment @# @; 1.3 date 2003.01.17.06.56.24; author joeuser; state Exp; branches; next 1.2; 1.2 date 2003.01.17.06.48.54; author joeuser; state Exp; branches; next 1.1; 1.1 date 2003.01.15.06.21.59; author joeuser; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2003.01.17.00.34.20; author joeuser; state dead; branches; next ; 1.1.4.1 date 2003.01.16.00.16.39; author joeuser; state dead; branches; next ; desc @@ 1.3 log @1.3 @ text @ 1.3 @ 1.2 log @1.2 @ text @d1 1 a1 1 1.2 @ 1.1 log @1.1 @ text @d1 1 a1 1 1.1 @ 1.1.2.1 log @1.1.2.1 @ text @@ 1.1.4.1 log @1.1.4.1 @ text @@ cvs2svn-2.5.0/test-data/branch-from-default-branch-cvsrepos/0000775000175100017510000000000013206450463025002 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-default-branch-cvsrepos/proj/0000775000175100017510000000000013206450463025754 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-default-branch-cvsrepos/proj/file.txt,v0000664000175100017510000000171212203665124027675 0ustar mhaggermhagger00000000000000head 1.2; access; symbols branch-off-of-default-branch:1.1.1.2.0.2 upstream:1.1.1; locks; strict; comment @# @; 1.2 date 2003.03.25.12.51.44; author fitz; state Exp; branches; next 1.1; 1.1 date 2000.10.20.07.15.19; author fitz; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2000.10.20.07.15.19; author fitz; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2002.01.10.11.03.58; author fitz; state Exp; branches 1.1.1.2.2.1; next ; 1.1.1.2.2.1 date 2003.03.18.01.36.42; author fitz; state Exp; branches; next ; desc @@ 1.2 log @This is a log message. @ text @This is revision 1.2 of file.txt @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the initial import of file.txt @ 1.1.1.1 log @Initial Import. @ text @@ 1.1.1.2 log @This is a log message. @ text @d1 1 a1 1 This is the first commit on the default branch. @ 1.1.1.2.2.1 log @This is a log message. @ text @d1 1 a1 1 This is a commit on a branch off of the default branch. @ cvs2svn-2.5.0/test-data/unicode-author-cvsrepos/0000775000175100017510000000000013206450463022655 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/unicode-author-cvsrepos/testunicode,v0000664000175100017510000000145312203665125025372 0ustar mhaggermhagger00000000000000head 1.6; access; symbols; locks; strict; comment @# @; 1.6 date 2008.02.03.22.15.23; author hülsmann; state Exp; branches; next 1.5; 1.5 date 2008.02.03.22.15.22; author hülsmann; state Exp; branches; next 1.4; 1.4 date 2008.02.03.22.15.21; author ringström; state Exp; branches; next 1.3; 1.3 date 2008.02.03.22.15.20; author ringström; state Exp; branches; next 1.2; 1.2 date 2008.02.03.22.15.18; author @Äibej@; state Exp; branches; next 1.1; 1.1 date 2007.02.13.21.13.21; author @Äibej@; state Exp; branches; next ; desc @@ 1.6 log @Revision 1.6 @ text @6 @ 1.5 log @Revision 1.5 @ text @d1 1 a1 1 5 @ 1.4 log @Revision 1.4 @ text @d1 1 a1 1 4 @ 1.3 log @Revision 1.3 @ text @d1 1 a1 1 3 @ 1.2 log @Revision 1.2 @ text @d1 1 a1 1 2 @ 1.1 log @Revision 1.1 @ text @d1 1 a1 1 1 @ cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/0000775000175100017510000000000013206450463022324 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/makerepo.sh0000775000175100017510000000457512203665124024477 0ustar mhaggermhagger00000000000000#! /bin/sh # This is the script used to create the add-on-branch CVS repository. # (The repository is checked into svn; this script is only here for # its documentation value.) The script should be started from the # main cvs2svn directory. # The output of this script depends on the CVS version. Newer CVS # versions add dead revisions (b.txt:1.1.2.1 and c.txt:1.2.2.1) on the # branch, presumably to indicate that the file didn't exist on the # branch during the period of time between the branching point and # when the 1.x.2.2 revisions were committed. Older versions of CVS do # not add these extra revisions. The point of this test is to handle # the new CVS behavior, so set this variable to point at a newish CVS # executable: cvs=$HOME/download/cvs-1.11.21/src/cvs name=add-on-branch repo=`pwd`/test-data/$name-cvsrepos wc=`pwd`/cvs2svn-tmp/$name-wc [ -e $repo/CVSROOT ] && rm -rf $repo/CVSROOT [ -e $repo/proj ] && rm -rf $repo/proj [ -e $wc ] && rm -rf $wc $cvs -d $repo init $cvs -d $repo co -d $wc . cd $wc mkdir proj $cvs add proj cd $wc/proj echo "Create a file a.txt on trunk:" echo '1.1' >a.txt $cvs add a.txt $cvs commit -m 'Adding a.txt:1.1' . echo "Create BRANCH1 on file a.txt:" $cvs tag -b BRANCH1 echo "Create BRANCH2 on file a.txt:" $cvs tag -b BRANCH2 echo "Create BRANCH3 on file a.txt:" $cvs tag -b BRANCH3 f=b.txt b=BRANCH1 echo "Add file $f on trunk:" $cvs up -A echo "1.1" >$f $cvs add $f $cvs commit -m "Adding $f:1.1" echo "Add file $f on $b:" $cvs up -r $b # Ensure that times are distinct: sleep 2 echo "1.1.2.2" >$f $cvs add $f $cvs commit -m "Adding $f:1.1.2.2" f=c.txt b=BRANCH2 echo "Add file $f on trunk:" $cvs up -A echo "1.1" >$f $cvs add $f $cvs commit -m "Adding $f:1.1" echo "Delete file $f on trunk:" rm $f $cvs remove $f $cvs commit -m "Removing $f:1.2" echo "Add file $f on $b:" $cvs up -r $b # Ensure that times are distinct: sleep 2 echo "1.2.2.2" >$f $cvs add $f $cvs commit -m "Adding $f:1.2.2.2" f=d.txt b=BRANCH3 echo "Add file $f on trunk:" $cvs up -A echo "1.1" >$f $cvs add $f $cvs commit -m "Adding $f:1.1" echo "Add file $f on $b:" $cvs up -r $b # Ensure that times are distinct: sleep 2 echo "1.1.2.2" >$f $cvs add $f $cvs commit -m "Adding $f:1.1.2.2" echo "Modify file $f on trunk:" $cvs up -A echo "1.2" >$f $cvs commit -m "Changing $f:1.2" # Erase the unneeded stuff out of CVSROOT: rm -rf $repo/CVSROOT mkdir $repo/CVSROOT cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/0000775000175100017510000000000013206450463023276 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463024342 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/Attic/c.txt,v0000664000175100017510000000121512203665124025564 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH2:1.2.0.2; locks; strict; comment @# @; 1.2 date 2007.07.05.18.27.28; author mhagger; state dead; branches 1.2.2.1; next 1.1; 1.1 date 2007.07.05.18.27.27; author mhagger; state Exp; branches; next ; 1.2.2.1 date 2007.07.05.18.27.28; author mhagger; state dead; branches; next 1.2.2.2; 1.2.2.2 date 2007.07.05.18.27.30; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Removing c.txt:1.2 @ text @1.1 @ 1.2.2.1 log @file c.txt was added on branch BRANCH2 on 2007-07-05 18:27:30 +0000 @ text @d1 1 @ 1.2.2.2 log @Adding c.txt:1.2.2.2 @ text @a0 1 1.2.2.2 @ 1.1 log @Adding c.txt:1.1 @ text @@ cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/d.txt,v0000664000175100017510000000123312203665124024521 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH3:1.1.0.2; locks; strict; comment @# @; 1.2 date 2007.07.05.20.37.52; author mhagger; state Exp; branches; next 1.1; 1.1 date 2007.07.05.18.27.32; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.07.05.18.27.32; author mhagger; state dead; branches; next 1.1.2.2; 1.1.2.2 date 2007.07.05.18.27.35; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Changing d.txt:1.2 @ text @1.2 @ 1.1 log @Adding d.txt:1.1 @ text @d1 1 a1 1 1.1 @ 1.1.2.1 log @file d.txt was added on branch BRANCH3 on 2007-07-05 18:27:35 +0000 @ text @d1 1 @ 1.1.2.2 log @Adding d.txt:1.1.2.2 @ text @a0 1 1.1.2.2 @ cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/a.txt,v0000664000175100017510000000035312203665124024520 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH3:1.1.0.6 BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.07.05.18.27.21; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding a.txt:1.1 @ text @1.1 @ cvs2svn-2.5.0/test-data/add-on-branch-cvsrepos/proj/b.txt,v0000664000175100017510000000102712203665124024520 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.07.05.18.27.22; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.07.05.18.27.22; author mhagger; state dead; branches; next 1.1.2.2; 1.1.2.2 date 2007.07.05.18.27.25; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding b.txt:1.1 @ text @1.1 @ 1.1.2.1 log @file b.txt was added on branch BRANCH1 on 2007-07-05 18:27:25 +0000 @ text @d1 1 @ 1.1.2.2 log @Adding b.txt:1.1.2.2 @ text @a0 1 1.1.2.2 @ cvs2svn-2.5.0/test-data/newphrases-cvsrepos/0000775000175100017510000000000013206450463022106 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/newphrases-cvsrepos/file001,v0000664000175100017510000000301112203665125023425 0ustar mhaggermhagger00000000000000head 1.7; access; symbols symbol00000:1.7 symbol00001:1.7.0.8 symbol00002:1.7 symbol00003:1.7.0.6 symbol00004:1.7 symbol00005:1.7.0.4 symbol00006:1.7 symbol00007:1.7.0.2 symbol00008:1.7 symbol00009:1.3 symbol00010:1.3.0.2; locks; strict; comment @ * @; this-is-a-newphrase:1.3 ; 1.7 date 2003.04.23.12.15.16; author author1; state Exp; branches; next 1.6; 1.6 date 2003.04.10.08.39.59; author author1; state Exp; branches; next 1.5; 1.5 date 2003.01.27.16.35.38; author author1; state Exp; branches; next 1.4; 1.4 date 2002.11.28.11.50.18; author author1; state Exp; branches; next 1.3; 1.3 date 2002.09.19.08.30.17; author author2; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2002.08.06.12.15.33; author author2; state Exp; branches; next 1.1; 1.1 date 2002.03.07.09.51.58; author author1; state Exp; branches; next ; 1.3.2.1 date 2003.02.10.08.43.05; author author2; state Exp; branches; next ; desc @@ 1.7 log @log 1@ text @This text was last seen in HEAD (revision 1.7) @ 1.6 log @log 2@ text @d1 1 a1 1 This text was last seen in revision 1.6 @ 1.5 log @log 3@ text @d1 1 a1 1 This text was last seen in revision 1.5 @ 1.4 log @log 4@ text @d1 1 a1 1 This text was last seen in revision 1.4 @ 1.3 log @log 5@ text @d1 1 a1 1 This text was last seen in revision 1.3 @ 1.3.2.1 log @log 6@ text @d1 1 a1 1 This text was committed in revision 1.3.2.1 @ 1.2 log @log 7@ text @d1 1 a1 1 This text was last seen in revision 1.2 @ 1.1 log @log 8@ text @d1 1 a1 1 This text was last seen in revision 1.1 @ cvs2svn-2.5.0/test-data/bogus-tag-cvsrepos/0000775000175100017510000000000013206450463021617 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/bogus-tag-cvsrepos/bogus-tag,v0000664000175100017510000000104212203665124023667 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols ends_with_slash/:1.1; locks ; strict; comment @# @; 1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @The content of this revision is unimportant, what matters is that there's a tag named 'ends_with_slash/' on it, an illegal tag name that cvs2svn.py should detect early. @ text @Nothing to see here. @ 1.1.1.1 log @imported @ text @@ cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/0000775000175100017510000000000013206450463022207 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/proj/0000775000175100017510000000000013206450463023161 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/proj/d.txt,v0000664000175100017510000000065712203665124024415 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH2:1.1.2.1.0.2 BRANCH1:1.1.0.2; locks; strict; comment @// @; 1.2 date 2005.08.17.02.27.39; author author1; state Exp; branches; next 1.1; 1.1 date 2005.02.25.18.17.06; author author8; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2005.02.25.23.10.11; author author8; state Exp; branches; next ; desc @@ 1.2 log @log 1@ text @@ 1.1 log @log 16@ text @@ 1.1.2.1 log @log 16@ text @@ cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/proj/a.txt,v0000664000175100017510000000120712203665124024402 0ustar mhaggermhagger00000000000000head 1.3; access; symbols BRANCH2:1.2.0.4 BRANCH1:1.2.0.2; locks; strict; comment @# @; 1.3 date 2005.03.23.16.45.45; author author2; state Exp; branches; next 1.2; 1.2 date 2004.02.27.22.01.32; author author3; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2003.12.22.20.09.23; author author4; state Exp; branches; next ; 1.2.2.1 date 2005.05.05.04.07.24; author author2; state Exp; branches; next ; 1.2.4.1 date 2005.05.05.20.56.37; author author6; state Exp; branches; next ; desc @@ 1.3 log @log 5@ text @@ 1.2 log @log 6@ text @@ 1.2.4.1 log @log 7@ text @@ 1.2.2.1 log @log 9@ text @@ 1.1 log @log 10@ text @@ cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/proj/c.txt,v0000664000175100017510000000067212203665124024411 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.8 BRANCH1:1.1.0.2; locks; strict; comment @// @; 1.1 date 2005.10.03.17.35.55; author author8; state Exp; branches 1.1.2.1 1.1.8.1; next ; 1.1.2.1 date 2005.10.04.10.54.11; author author8; state Exp; branches; next ; 1.1.8.1 date 2005.10.05.20.46.30; author author6; state Exp; branches; next ; desc @@ 1.1 log @log 17@ text @@ 1.1.8.1 log @log 18@ text @@ 1.1.2.1 log @log 19@ text @@ cvs2svn-2.5.0/test-data/double-fill2-cvsrepos/proj/b.txt,v0000664000175100017510000000104312203665124024401 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @// @; 1.2 date 2005.04.05.11.08.53; author author7; state Exp; branches; next 1.1; 1.1 date 2005.02.22.00.10.19; author author7; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2005.04.05.11.09.38; author author7; state Exp; branches; next ; 1.1.4.1 date 2005.04.05.22.20.39; author author6; state Exp; branches; next ; desc @@ 1.2 log @log 13@ text @@ 1.1 log @log 14@ text @@ 1.1.4.1 log @log 15@ text @@ 1.1.2.1 log @log 13@ text @@ cvs2svn-2.5.0/test-data/keywords-cvsrepos/0000775000175100017510000000000013206450463021576 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.kv,v0000664000175100017510000000115112203665125023162 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @v@; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: jrandom 2004/07/19 20:57:24 foo.kv,v 1.1 2004/07/19 20:57:24 jrandom Exp This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d5 1 a5 1 $Author$ d7 1 a7 1 $Date$ d9 2 a10 1 $Id$ @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.kkvl,v0000664000175100017510000000121012203665125023505 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @kvl@; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author: jrandom $ $Date: 2004/07/19 20:57:24 $ $Id: foo.kkvl,v 1.1 2004/07/19 20:57:24 jrandom Exp $ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d5 1 a5 1 $Author$ d7 1 a7 1 $Date$ d9 2 a10 1 $Id$ @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.default,v0000664000175100017510000000117512203665125024174 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author: jrandom $ $Date: 2004/07/19 20:57:24 $ $Id: foo.default,v 1.1 2004/07/19 20:57:24 jrandom Exp $ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d5 1 a5 1 $Author$ d7 1 a7 1 $Date$ d9 2 a10 1 $Id$ @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.kb,v0000664000175100017510000000100112203665125023130 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @b@; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author$ $Date$ $Id$ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d10 1 @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.kkv,v0000664000175100017510000000117112203665125023337 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author: jrandom $ $Date: 2004/07/19 20:57:24 $ $Id: foo.kkv,v 1.1 2004/07/19 20:57:24 jrandom Exp $ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d5 1 a5 1 $Author$ d7 1 a7 1 $Date$ d9 2 a10 1 $Id$ @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.kk,v0000664000175100017510000000100112203665125023141 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @k@; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author$ $Date$ $Id$ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d10 1 @ cvs2svn-2.5.0/test-data/keywords-cvsrepos/foo.ko,v0000664000175100017510000000100112203665125023145 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @o@; 1.2 date 2004.07.28.10.42.27; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision, appending text to the first revision. @ text @This is the first revision in this file. It has three keywords: $Author$ $Date$ $Id$ This second revision appends some text to the first revision. @ 1.1 log @Add a file. @ text @d10 1 @ cvs2svn-2.5.0/test-data/tagging-after-delete-cvsrepos/0000775000175100017510000000000013206450463023706 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tagging-after-delete-cvsrepos/test/0000775000175100017510000000000013206450463024665 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tagging-after-delete-cvsrepos/test/Attic/0000775000175100017510000000000013206450463025731 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/tagging-after-delete-cvsrepos/test/Attic/b,v0000664000175100017510000000044412203665125026340 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.11.28.18.18.39; author mark; state dead; branches; next 1.1; 1.1 date 2006.11.28.18.18.24; author mark; state Exp; branches; next ; desc @@ 1.2 log @removed file b @ text @file b @ 1.1 log @added files @ text @@ cvs2svn-2.5.0/test-data/tagging-after-delete-cvsrepos/test/a,v0000664000175100017510000000027512203665125025275 0ustar mhaggermhagger00000000000000head 1.1; access; symbols tag1:1.1; locks; strict; comment @# @; 1.1 date 2006.11.28.18.18.23; author mark; state Exp; branches; next ; desc @@ 1.1 log @added files @ text @file a @ cvs2svn-2.5.0/test-data/bogus-branch-copy-cvsrepos/0000775000175100017510000000000013206450463023251 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/bogus-branch-copy-cvsrepos/file2.txt,v0000664000175100017510000000163112203665124025254 0ustar mhaggermhagger00000000000000head 1.4; access; symbols branch-boom:1.3.0.2; locks; strict; comment @# @; 1.4 date 2005.07.21.15.31.18; author harry; state Exp; branches; next 1.3; 1.3 date 2005.04.01.20.47.29; author harry; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2005.03.29.23.11.45; author harry; state Exp; branches; next 1.1; 1.1 date 2005.03.23.23.20.09; author sally; state dead; branches; next ; 1.3.2.1 date 2005.04.01.20.47.29; author harry; state dead; branches; next 1.3.2.2; 1.3.2.2 date 2005.04.01.23.13.40; author harry; state Exp; branches; next ; desc @@ 1.4 log @merged sally to trunk @ text @@ 1.3 log @merged sally to trunk @ text @@ 1.3.2.1 log @file file2.txt was added on branch branch-boom on 2005-04-01 23:13:40 +0000 @ text @@ 1.3.2.2 log @merged trunk to ira @ text @@ 1.2 log @merged sally to trunk @ text @@ 1.1 log @file file2.txt.py was initially added on branch branch-sally. @ text @@ cvs2svn-2.5.0/test-data/bogus-branch-copy-cvsrepos/Attic/0000775000175100017510000000000013206450463024315 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/bogus-branch-copy-cvsrepos/Attic/file3.txt,v0000664000175100017510000000137012203665124026321 0ustar mhaggermhagger00000000000000head 1.1; access; symbols branch-boom:1.1.0.2; locks; strict; comment @# @; 1.1 date 2005.03.04.18.57.55; author ira; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2005.03.04.18.57.55; author ira; state Exp; branches; next 1.1.2.2; 1.1.2.2 date 2005.03.28.22.18.05; author ira; state Exp; branches; next 1.1.2.3; 1.1.2.3 date 2005.03.29.20.21.54; author ira; state Exp; branches; next 1.1.2.4; 1.1.2.4 date 2005.04.05.15.47.56; author ira; state dead; branches; next ; desc @@ 1.1 log @file file3.txt was initially added on branch branch-boom. @ text @@ 1.1.2.1 log @This is a log message. @ text @@ 1.1.2.2 log @This is another log message. @ text @@ 1.1.2.3 log @This is yet another log message. @ text @@ 1.1.2.4 log @ci @ text @@ cvs2svn-2.5.0/test-data/bogus-branch-copy-cvsrepos/file1.txt,v0000664000175100017510000000125512203665124025255 0ustar mhaggermhagger00000000000000head 1.2; access; symbols branch-boom:1.2.0.16; locks; strict; comment @# @; 1.2 date 2005.03.29.23.11.45; author harry; state Exp; branches 1.2.16.1; next 1.1; 1.1 date 2005.03.24.22.00.30; author sally; state dead; branches; next ; 1.2.16.1 date 2005.03.29.23.11.45; author harry; state dead; branches; next 1.2.16.2; 1.2.16.2 date 2005.04.01.23.13.40; author harry; state Exp; branches; next ; desc @@ 1.2 log @merged sally to trunk @ text @@ 1.2.16.1 log @file file1.txt was added on branch branch-boom on 2005-04-01 23:13:40 +0000 @ text @@ 1.2.16.2 log @merged trunk to ira @ text @@ 1.1 log @file file1.txt was initially added on branch branch-sally. @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/0000775000175100017510000000000013206450463023260 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/0000775000175100017510000000000013206450463024527 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/BUILDING,v0000664000175100017510000000161012203665125026106 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.4 branch-beta2-rewrite:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @defines that affect compilation _WIN32 this should be defined for Win32 platforms DEBUG_MUTEXES define this to turn on mutex debugging. this will log locks/unlocks. CHECK_MUTEXES (DEBUG_MUTEXES must also be defined) checks to make sure mutex operations make sense. ie, multi_mutex is locked when locking multiple mutexes, etc. THREAD_DEBUG (define to 1-4) turns on the thread.log logging @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/COPYING,v0000664000175100017510000006225612203665125026036 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.4 branch-beta2-rewrite:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.26.35; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.35; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @ GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/TODO,v0000664000175100017510000000123512203665125025461 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.4 branch-beta2-rewrite:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @- make DEBUG_MUTEXES and CHECK_MUTEXES work - recursive locking/unlocking (easy) - reader/writer locking (easy) - make a mode were _log is disabled (normal mode) (easy) @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/README,v0000664000175100017510000000145412203665125025654 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.4 branch-beta2-rewrite:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.26.32; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.32; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is the cross platform thread and syncronization library. It depends on the avl library. This is a massively cleaned and picked through version of the code from the icecast 1.3.x base. It has not been heavily tested *YET*. But since it's just cleanups, it really shouldn't have that many problems. jack. @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/Makefile.am,v0000664000175100017510000000317512203665125027032 0ustar mhaggermhagger00000000000000head 1.4; access; symbols libshout-2_0:1.4 libshout-2_0b3:1.4 libshout-2_0b2:1.3 libshout_2_0b1:1.3 libogg2-zerocopy:1.1.1.1.0.4 branch-beta2-rewrite:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.4 date 2003.07.03.12.59.06; author brendan; state Exp; branches; next 1.3; 1.3 date 2003.03.09.22.56.46; author karl; state Exp; branches; next 1.2; 1.2 date 2003.03.08.00.46.58; author karl; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.26.32; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.32; author jack; state Exp; branches; next ; desc @@ 1.4 log @Get everthing in make dist, and remove bitkeeper cruft @ text @## Process this with automake to create Makefile.in AUTOMAKE_OPTIONS = foreign EXTRA_DIST = BUILDING COPYING README TODO noinst_LTLIBRARIES = libicethread.la noinst_HEADERS = thread.h libicethread_la_SOURCES = thread.c libicethread_la_CFLAGS = @@XIPH_CFLAGS@@ INCLUDES = -I$(srcdir)/.. debug: $(MAKE) all CFLAGS="@@DEBUG@@" profile: $(MAKE) all CFLAGS="@@PROFILE@@" @ 1.3 log @reduce include file namespace clutter for libshout and the associated smaller libs. @ text @d5 2 a13 3 # SCCS stuff (for BitKeeper) GET = true @ 1.2 log @more on the XIPH_CFLAGS. For the smaller libs like thread etc put any passed flags into the compiling rules. Also configure in libshout now sets up the XIPH_CFLAGS @ text @d11 1 a11 1 INCLUDES = -I$(srcdir)/../avl -I$(srcdir)/../log @ 1.1 log @Initial revision @ text @d9 1 d17 1 a17 1 $(MAKE) all CFLAGS="@@DEBUG@@" d20 1 a20 1 $(MAKE) all CFLAGS="@@PROFILE@@" @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/.cvsignore,v0000664000175100017510000000074012203665125026771 0ustar mhaggermhagger00000000000000head 1.2; access; symbols libshout-2_0:1.2 libshout-2_0b3:1.2 libshout-2_0b2:1.2 libshout_2_0b1:1.2 libogg2-zerocopy:1.2.0.4 branch-beta2-rewrite:1.2.0.2; locks; strict; comment @# @; 1.2 date 2001.09.10.03.04.11; author jack; state Exp; branches; next 1.1; 1.1 date 2001.09.10.03.00.41; author jack; state Exp; branches; next ; desc @@ 1.2 log @.cvsignore is fun! @ text @Makefile Makefile.in .deps .libs *.la *.lo @ 1.1 log @Still more .cvsignore @ text @d4 3 @ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/thread.c,v0000664000175100017510000013154112203665125026410 0ustar mhaggermhagger00000000000000head 1.25; access; symbols libshout-2_0:1.24 libshout-2_0b3:1.24 libshout-2_0b2:1.24 libshout_2_0b1:1.24 libogg2-zerocopy:1.17.0.2 branch-beta2-rewrite:1.5.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @ * @; 1.25 date 2003.07.14.02.17.52; author brendan; state Exp; branches; next 1.24; 1.24 date 2003.03.15.02.10.18; author msmith; state Exp; branches; next 1.23; 1.23 date 2003.03.12.03.59.55; author karl; state Exp; branches; next 1.22; 1.22 date 2003.03.09.22.56.46; author karl; state Exp; branches; next 1.21; 1.21 date 2003.03.08.16.05.38; author karl; state Exp; branches; next 1.20; 1.20 date 2003.03.04.15.31.34; author msmith; state Exp; branches; next 1.19; 1.19 date 2003.01.17.09.01.04; author msmith; state Exp; branches; next 1.18; 1.18 date 2002.12.29.09.55.50; author msmith; state Exp; branches; next 1.17; 1.17 date 2002.11.22.13.00.44; author msmith; state Exp; branches; next 1.16; 1.16 date 2002.09.24.07.09.08; author msmith; state Exp; branches; next 1.15; 1.15 date 2002.08.16.14.23.17; author msmith; state Exp; branches; next 1.14; 1.14 date 2002.08.13.01.08.15; author msmith; state Exp; branches; next 1.13; 1.13 date 2002.08.10.03.22.44; author msmith; state Exp; branches; next 1.12; 1.12 date 2002.08.09.06.52.07; author msmith; state Exp; branches; next 1.11; 1.11 date 2002.08.05.14.48.03; author msmith; state Exp; branches; next 1.10; 1.10 date 2002.08.03.08.14.56; author msmith; state Exp; branches; next 1.9; 1.9 date 2002.04.30.06.50.47; author msmith; state Exp; branches; next 1.8; 1.8 date 2002.03.05.23.59.38; author jack; state Exp; branches; next 1.7; 1.7 date 2002.02.08.03.51.19; author jack; state Exp; branches; next 1.6; 1.6 date 2002.02.07.01.04.09; author jack; state Exp; branches; next 1.5; 1.5 date 2001.10.21.02.04.27; author jack; state Exp; branches; next 1.4; 1.4 date 2001.10.20.22.27.52; author jack; state Exp; branches; next 1.3; 1.3 date 2001.10.20.05.35.30; author jack; state Exp; branches; next 1.2; 1.2 date 2001.10.20.03.39.10; author jack; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches; next ; desc @@ 1.25 log @Assign LGP to thread module @ text @/* threads.c: Thread Abstraction Functions * * Copyright (c) 1999, 2000 the icecast team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #else #include #include #include #endif #include #include #include #ifdef THREAD_DEBUG #include #endif #ifdef _WIN32 #define __FUNCTION__ __FILE__ #endif #ifdef THREAD_DEBUG #define CATMODULE "thread" #define LOG_ERROR(y) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y) #define LOG_ERROR3(y, z1, z2, z3) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y, z1, z2, z3) #define LOG_ERROR7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 1, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) #define LOG_WARN(y) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y) #define LOG_WARN3(y, z1, z2, z3) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3) #define LOG_WARN5(y, z1, z2, z3, z4, z5) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) #define LOG_WARN7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 2, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) #define LOG_INFO(y) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y) #define LOG_INFO4(y, z1, z2, z3, z4) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4) #define LOG_INFO5(y, z1, z2, z3, z4, z5) log_write(_logid, 3, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) #define LOG_DEBUG(y) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y) #define LOG_DEBUG2(y, z1, z2) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2) #define LOG_DEBUG5(y, z1, z2, z3, z4, z5) log_write(_logid, 4, CATMODULE "/", __FUNCTION__, y, z1, z2, z3, z4, z5) #endif /* thread starting structure */ typedef struct thread_start_tag { /* the real start routine and arg */ void *(*start_routine)(void *); void *arg; /* whether to create the threaded in detached state */ int detached; /* the other stuff we need to make sure this thread is inserted into ** the thread tree */ thread_type *thread; pthread_t sys_thread; } thread_start_t; static long _next_thread_id = 0; static int _initialized = 0; static avl_tree *_threadtree = NULL; #ifdef DEBUG_MUTEXES static mutex_t _threadtree_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; #else static mutex_t _threadtree_mutex = { PTHREAD_MUTEX_INITIALIZER }; #endif #ifdef DEBUG_MUTEXES static int _logid = -1; static long _next_mutex_id = 0; static avl_tree *_mutextree = NULL; static mutex_t _mutextree_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; #endif #ifdef DEBUG_MUTEXES static mutex_t _library_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1, PTHREAD_MUTEX_INITIALIZER}; #else static mutex_t _library_mutex = { PTHREAD_MUTEX_INITIALIZER }; #endif /* INTERNAL FUNCTIONS */ /* avl tree functions */ #ifdef DEBUG_MUTEXES static int _compare_mutexes(void *compare_arg, void *a, void *b); static int _free_mutex(void *key); #endif static int _compare_threads(void *compare_arg, void *a, void *b); static int _free_thread(void *key); static int _free_thread_if_detached(void *key); /* mutex fuctions */ static void _mutex_create(mutex_t *mutex); static void _mutex_lock(mutex_t *mutex); static void _mutex_unlock(mutex_t *mutex); /* misc thread stuff */ static void *_start_routine(void *arg); static void _catch_signals(void); static void _block_signals(void); /* LIBRARY INITIALIZATION */ void thread_initialize(void) { thread_type *thread; /* set up logging */ #ifdef THREAD_DEBUG log_initialize(); _logid = log_open("thread.log"); log_set_level(_logid, THREAD_DEBUG); #endif #ifdef DEBUG_MUTEXES /* create all the internal mutexes, and initialize the mutex tree */ _mutextree = avl_tree_new(_compare_mutexes, NULL); /* we have to create this one by hand, because there's no ** mutextree_mutex to lock yet! */ _mutex_create(&_mutextree_mutex); _mutextree_mutex.mutex_id = _next_mutex_id++; avl_insert(_mutextree, (void *)&_mutextree_mutex); #endif thread_mutex_create(&_threadtree_mutex); thread_mutex_create(&_library_mutex); /* initialize the thread tree and insert the main thread */ _threadtree = avl_tree_new(_compare_threads, NULL); thread = (thread_type *)malloc(sizeof(thread_type)); thread->thread_id = _next_thread_id++; thread->line = 0; thread->file = strdup("main.c"); thread->sys_thread = pthread_self(); thread->create_time = time(NULL); thread->name = strdup("Main Thread"); avl_insert(_threadtree, (void *)thread); _catch_signals(); _initialized = 1; } void thread_shutdown(void) { if (_initialized == 1) { thread_mutex_destroy(&_library_mutex); thread_mutex_destroy(&_threadtree_mutex); #ifdef THREAD_DEBUG thread_mutex_destroy(&_mutextree_mutex); avl_tree_free(_mutextree, _free_mutex); #endif avl_tree_free(_threadtree, _free_thread); } #ifdef THREAD_DEBUG log_close(_logid); log_shutdown(); #endif } /* * Signals should be handled by the main thread, nowhere else. * I'm using POSIX signal interface here, until someone tells me * that I should use signal/sigset instead * * This function only valid for non-Win32 */ static void _block_signals(void) { #ifndef _WIN32 sigset_t ss; sigfillset(&ss); /* These ones we want */ sigdelset(&ss, SIGKILL); sigdelset(&ss, SIGSTOP); sigdelset(&ss, SIGTERM); sigdelset(&ss, SIGSEGV); sigdelset(&ss, SIGBUS); if (pthread_sigmask(SIG_BLOCK, &ss, NULL) != 0) { #ifdef THREAD_DEBUG LOG_ERROR("Pthread_sigmask() failed for blocking signals"); #endif } #endif } /* * Let the calling thread catch all the relevant signals * * This function only valid for non-Win32 */ static void _catch_signals(void) { #ifndef _WIN32 sigset_t ss; sigemptyset(&ss); /* These ones should only be accepted by the signal handling thread (main thread) */ sigaddset(&ss, SIGHUP); sigaddset(&ss, SIGCHLD); sigaddset(&ss, SIGINT); sigaddset(&ss, SIGPIPE); if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL) != 0) { #ifdef THREAD_DEBUG LOG_ERROR("pthread_sigmask() failed for catching signals!"); #endif } #endif } thread_type *thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file) { int created; thread_type *thread; thread_start_t *start; thread = (thread_type *)malloc(sizeof(thread_type)); start = (thread_start_t *)malloc(sizeof(thread_start_t)); thread->line = line; thread->file = strdup(file); _mutex_lock(&_threadtree_mutex); thread->thread_id = _next_thread_id++; _mutex_unlock(&_threadtree_mutex); thread->name = strdup(name); thread->create_time = time(NULL); thread->detached = 0; start->start_routine = start_routine; start->arg = arg; start->thread = thread; start->detached = detached; created = 0; if (pthread_create(&thread->sys_thread, NULL, _start_routine, start) == 0) created = 1; #ifdef THREAD_DEBUG else LOG_ERROR("Could not create new thread"); #endif if (created == 0) { #ifdef THREAD_DEBUG LOG_ERROR("System won't let me create more threads, giving up"); #endif return NULL; } return thread; } /* _mutex_create ** ** creates a mutex */ static void _mutex_create(mutex_t *mutex) { #ifdef DEBUG_MUTEXES mutex->thread_id = MUTEX_STATE_NEVERLOCKED; mutex->line = -1; #endif pthread_mutex_init(&mutex->sys_mutex, NULL); } void thread_mutex_create_c(mutex_t *mutex, int line, char *file) { _mutex_create(mutex); #ifdef DEBUG_MUTEXES _mutex_lock(&_mutextree_mutex); mutex->mutex_id = _next_mutex_id++; avl_insert(_mutextree, (void *)mutex); _mutex_unlock(&_mutextree_mutex); #endif } void thread_mutex_destroy (mutex_t *mutex) { pthread_mutex_destroy(&mutex->sys_mutex); #ifdef DEBUG_MUTEXES _mutex_lock(&_mutextree_mutex); avl_delete(_mutextree, mutex, _free_mutex); _mutex_unlock(&_mutextree_mutex); #endif } void thread_mutex_lock_c(mutex_t *mutex, int line, char *file) { #ifdef DEBUG_MUTEXES thread_type *th = thread_self(); if (!th) LOG_WARN("No mt record for %u in lock [%s:%d]", thread_self(), file, line); LOG_DEBUG5("Locking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); # ifdef CHECK_MUTEXES /* Just a little sanity checking to make sure that we're locking ** mutexes correctly */ if (th) { int locks = 0; avl_node *node; mutex_t *tmutex; _mutex_lock(&_mutextree_mutex); node = avl_get_first (_mutextree); while (node) { tmutex = (mutex_t *)node->key; if (tmutex->mutex_id == mutex->mutex_id) { if (tmutex->thread_id == th->thread_id) { /* Deadlock, same thread can't lock the same mutex twice */ LOG_ERROR7("DEADLOCK AVOIDED (%d == %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); _mutex_unlock(&_mutextree_mutex); return; } } else if (tmutex->thread_id == th->thread_id) { /* Mutex locked by this thread (not this mutex) */ locks++; } node = avl_get_next(node); } if (locks > 0) { /* Has already got a mutex locked */ if (_multi_mutex.thread_id != th->thread_id) { /* Tries to lock two mutexes, but has not got the double mutex, norty boy! */ LOG_WARN("(%d != %d) Thread %d [%s] tries to lock a second mutex [%s] in file %s line %d, without locking double mutex!", _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); } } _mutex_unlock(&_mutextree_mutex); } # endif /* CHECK_MUTEXES */ _mutex_lock(mutex); _mutex_lock(&_mutextree_mutex); LOG_DEBUG2("Locked %p by thread %d", mutex, th ? th->thread_id : -1); mutex->line = line; if (th) { mutex->thread_id = th->thread_id; } _mutex_unlock(&_mutextree_mutex); #else _mutex_lock(mutex); #endif /* DEBUG_MUTEXES */ } void thread_mutex_unlock_c(mutex_t *mutex, int line, char *file) { #ifdef DEBUG_MUTEXES thread_type *th = thread_self(); if (!th) { LOG_ERROR3("No record for %u in unlock [%s:%d]", thread_self(), file, line); } LOG_DEBUG5("Unlocking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); mutex->line = line; # ifdef CHECK_MUTEXES if (th) { int locks = 0; avl_node *node; mutex_t *tmutex; _mutex_lock(&_mutextree_mutex); while (node) { tmutex = (mutex_t *)node->key; if (tmutex->mutex_id == mutex->mutex_id) { if (tmutex->thread_id != th->thread_id) { LOG_ERROR7("ILLEGAL UNLOCK (%d != %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); _mutex_unlock(&_mutextree_mutex); return; } } else if (tmutex->thread_id == th->thread_id) { locks++; } node = avl_get_next (node); } if ((locks > 0) && (_multi_mutex.thread_id != th->thread_id)) { /* Don't have double mutex, has more than this mutex left */ LOG_WARN("(%d != %d) Thread %d [%s] tries to unlock a mutex [%s] in file %s line %d, without owning double mutex!", _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); } _mutex_unlock(&_mutextree_mutex); } # endif /* CHECK_MUTEXES */ _mutex_unlock(mutex); _mutex_lock(&_mutextree_mutex); LOG_DEBUG2("Unlocked %p by thread %d", mutex, th ? th->thread_id : -1); mutex->line = -1; if (mutex->thread_id == th->thread_id) { mutex->thread_id = MUTEX_STATE_NOTLOCKED; } _mutex_unlock(&_mutextree_mutex); #else _mutex_unlock(mutex); #endif /* DEBUG_MUTEXES */ } void thread_cond_create_c(cond_t *cond, int line, char *file) { pthread_cond_init(&cond->sys_cond, NULL); pthread_mutex_init(&cond->cond_mutex, NULL); } void thread_cond_destroy(cond_t *cond) { pthread_mutex_destroy(&cond->cond_mutex); pthread_cond_destroy(&cond->sys_cond); } void thread_cond_signal_c(cond_t *cond, int line, char *file) { pthread_cond_signal(&cond->sys_cond); } void thread_cond_broadcast_c(cond_t *cond, int line, char *file) { pthread_cond_broadcast(&cond->sys_cond); } void thread_cond_timedwait_c(cond_t *cond, int millis, int line, char *file) { struct timespec time; time.tv_sec = millis/1000; time.tv_nsec = (millis - time.tv_sec*1000)*1000000; pthread_mutex_lock(&cond->cond_mutex); pthread_cond_timedwait(&cond->sys_cond, &cond->cond_mutex, &time); pthread_mutex_unlock(&cond->cond_mutex); } void thread_cond_wait_c(cond_t *cond, int line, char *file) { pthread_mutex_lock(&cond->cond_mutex); pthread_cond_wait(&cond->sys_cond, &cond->cond_mutex); pthread_mutex_unlock(&cond->cond_mutex); } void thread_rwlock_create_c(rwlock_t *rwlock, int line, char *file) { pthread_rwlock_init(&rwlock->sys_rwlock, NULL); } void thread_rwlock_destroy(rwlock_t *rwlock) { pthread_rwlock_destroy(&rwlock->sys_rwlock); } void thread_rwlock_rlock_c(rwlock_t *rwlock, int line, char *file) { pthread_rwlock_rdlock(&rwlock->sys_rwlock); } void thread_rwlock_wlock_c(rwlock_t *rwlock, int line, char *file) { pthread_rwlock_wrlock(&rwlock->sys_rwlock); } void thread_rwlock_unlock_c(rwlock_t *rwlock, int line, char *file) { pthread_rwlock_unlock(&rwlock->sys_rwlock); } void thread_exit_c(int val, int line, char *file) { thread_type *th = thread_self(); #if defined(DEBUG_MUTEXES) && defined(CHECK_MUTEXES) if (th) { avl_node *node; mutex_t *tmutex; char name[40]; _mutex_lock(&_mutextree_mutex); while (node) { tmutex = (mutex_t *)node->key; if (tmutex->thread_id == th->thread_id) { LOG_WARN("Thread %d [%s] exiting in file %s line %d, without unlocking mutex [%s]", th->thread_id, th->name, file, line, mutex_to_string(tmutex, name)); } node = avl_get_next (node); } _mutex_unlock(&_mutextree_mutex); } #endif if (th) { #ifdef THREAD_DEBUG LOG_INFO4("Removing thread %d [%s] started at [%s:%d], reason: 'Thread Exited'", th->thread_id, th->name, th->file, th->line); #endif _mutex_lock(&_threadtree_mutex); avl_delete(_threadtree, th, _free_thread_if_detached); _mutex_unlock(&_threadtree_mutex); } pthread_exit((void *)val); } /* sleep for a number of microseconds */ void thread_sleep(unsigned long len) { #ifdef _WIN32 Sleep(len / 1000); #else # ifdef HAVE_NANOSLEEP struct timespec time_sleep; struct timespec time_remaining; int ret; time_sleep.tv_sec = len / 1000000; time_sleep.tv_nsec = (len % 1000000) * 1000; ret = nanosleep(&time_sleep, &time_remaining); while (ret != 0 && errno == EINTR) { time_sleep.tv_sec = time_remaining.tv_sec; time_sleep.tv_nsec = time_remaining.tv_nsec; ret = nanosleep(&time_sleep, &time_remaining); } # else struct timeval tv; tv.tv_sec = len / 1000000; tv.tv_usec = (len % 1000000); select(0, NULL, NULL, NULL, &tv); # endif #endif } static void *_start_routine(void *arg) { thread_start_t *start = (thread_start_t *)arg; void *(*start_routine)(void *) = start->start_routine; void *real_arg = start->arg; thread_type *thread = start->thread; int detach = start->detached; _block_signals(); free(start); /* insert thread into thread tree here */ _mutex_lock(&_threadtree_mutex); thread->sys_thread = pthread_self(); avl_insert(_threadtree, (void *)thread); _mutex_unlock(&_threadtree_mutex); #ifdef THREAD_DEBUG LOG_INFO4("Added thread %d [%s] started at [%s:%d]", thread->thread_id, thread->name, thread->file, thread->line); #endif if (detach) { pthread_detach(thread->sys_thread); thread->detached = 1; } pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); /* call the real start_routine and start the thread ** this should never exit! */ (start_routine)(real_arg); #ifdef THREAD_DEBUG LOG_WARN("Thread x should never exit from here!!!"); #endif return NULL; } thread_type *thread_self(void) { avl_node *node; thread_type *th; pthread_t sys_thread = pthread_self(); _mutex_lock(&_threadtree_mutex); if (_threadtree == NULL) { #ifdef THREAD_DEBUG LOG_WARN("Thread tree is empty, this must be wrong!"); #endif _mutex_unlock(&_threadtree_mutex); return NULL; } node = avl_get_first(_threadtree); while (node) { th = (thread_type *)node->key; if (th && pthread_equal(sys_thread, th->sys_thread)) { _mutex_unlock(&_threadtree_mutex); return th; } node = avl_get_next(node); } _mutex_unlock(&_threadtree_mutex); #ifdef THREAD_DEBUG LOG_ERROR("Nonexistant thread alive..."); #endif return NULL; } void thread_rename(const char *name) { thread_type *th; th = thread_self(); if (th->name) free(th->name); th->name = strdup(name); } static void _mutex_lock(mutex_t *mutex) { pthread_mutex_lock(&mutex->sys_mutex); } static void _mutex_unlock(mutex_t *mutex) { pthread_mutex_unlock(&mutex->sys_mutex); } void thread_library_lock(void) { _mutex_lock(&_library_mutex); } void thread_library_unlock(void) { _mutex_unlock(&_library_mutex); } void thread_join(thread_type *thread) { void *ret; int i; i = pthread_join(thread->sys_thread, &ret); _mutex_lock(&_threadtree_mutex); avl_delete(_threadtree, thread, _free_thread); _mutex_unlock(&_threadtree_mutex); } /* AVL tree functions */ #ifdef DEBUG_MUTEXES static int _compare_mutexes(void *compare_arg, void *a, void *b) { mutex_t *m1, *m2; m1 = (mutex_t *)a; m2 = (mutex_t *)b; if (m1->mutex_id > m2->mutex_id) return 1; if (m1->mutex_id < m2->mutex_id) return -1; return 0; } #endif static int _compare_threads(void *compare_arg, void *a, void *b) { thread_type *t1, *t2; t1 = (thread_type *)a; t2 = (thread_type *)b; if (t1->thread_id > t2->thread_id) return 1; if (t1->thread_id < t2->thread_id) return -1; return 0; } #ifdef DEBUG_MUTEXES static int _free_mutex(void *key) { mutex_t *m; m = (mutex_t *)key; if (m && m->file) { free(m->file); m->file = NULL; } /* all mutexes are static. don't need to free them */ return 1; } #endif static int _free_thread(void *key) { thread_type *t; t = (thread_type *)key; if (t->file) free(t->file); if (t->name) free(t->name); free(t); return 1; } static int _free_thread_if_detached(void *key) { thread_type *t = key; if(t->detached) return _free_thread(key); return 1; } @ 1.24 log @Brendan was getting pissed off about inconsistent indentation styles. Convert all tabs to 4 spaces. All code must now use 4 space indents. @ text @d1 18 a18 19 /* threads.c ** - Thread Abstraction Functions ** ** Copyright (c) 1999, 2000 the icecast team ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License ** as published by the Free Software Foundation; either version 2 ** of the License, or (at your option) any latfer version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @ 1.23 log @avoid freeing a thread structure a second time. @ text @d77 12 a88 12 /* the real start routine and arg */ void *(*start_routine)(void *); void *arg; /* whether to create the threaded in detached state */ int detached; /* the other stuff we need to make sure this thread is inserted into ** the thread tree */ thread_type *thread; pthread_t sys_thread; d146 1 a146 1 thread_type *thread; d148 1 a148 1 /* set up logging */ d151 3 a153 3 log_initialize(); _logid = log_open("thread.log"); log_set_level(_logid, THREAD_DEBUG); d157 1 a157 1 /* create all the internal mutexes, and initialize the mutex tree */ d159 1 a159 1 _mutextree = avl_tree_new(_compare_mutexes, NULL); d161 4 a164 4 /* we have to create this one by hand, because there's no ** mutextree_mutex to lock yet! */ _mutex_create(&_mutextree_mutex); d166 2 a167 2 _mutextree_mutex.mutex_id = _next_mutex_id++; avl_insert(_mutextree, (void *)&_mutextree_mutex); d170 2 a171 2 thread_mutex_create(&_threadtree_mutex); thread_mutex_create(&_library_mutex); d173 1 a173 1 /* initialize the thread tree and insert the main thread */ d175 1 a175 1 _threadtree = avl_tree_new(_compare_threads, NULL); d177 1 a177 1 thread = (thread_type *)malloc(sizeof(thread_type)); d179 6 a184 6 thread->thread_id = _next_thread_id++; thread->line = 0; thread->file = strdup("main.c"); thread->sys_thread = pthread_self(); thread->create_time = time(NULL); thread->name = strdup("Main Thread"); d186 1 a186 1 avl_insert(_threadtree, (void *)thread); d188 1 a188 1 _catch_signals(); d190 1 a190 1 _initialized = 1; d195 3 a197 3 if (_initialized == 1) { thread_mutex_destroy(&_library_mutex); thread_mutex_destroy(&_threadtree_mutex); d199 3 a201 3 thread_mutex_destroy(&_mutextree_mutex); avl_tree_free(_mutextree, _free_mutex); d203 2 a204 2 avl_tree_free(_threadtree, _free_thread); } d207 2 a208 2 log_close(_logid); log_shutdown(); d271 8 a278 12 int created; thread_type *thread; thread_start_t *start; thread = (thread_type *)malloc(sizeof(thread_type)); start = (thread_start_t *)malloc(sizeof(thread_start_t)); thread->line = line; thread->file = strdup(file); _mutex_lock(&_threadtree_mutex); thread->thread_id = _next_thread_id++; _mutex_unlock(&_threadtree_mutex); d280 6 a285 2 thread->name = strdup(name); thread->create_time = time(NULL); d288 4 a291 4 start->start_routine = start_routine; start->arg = arg; start->thread = thread; start->detached = detached; d293 3 a295 3 created = 0; if (pthread_create(&thread->sys_thread, NULL, _start_routine, start) == 0) created = 1; d297 2 a298 2 else LOG_ERROR("Could not create new thread"); d301 1 a301 1 if (created == 0) { d303 1 a303 1 LOG_ERROR("System won't let me create more threads, giving up"); d305 2 a306 2 return NULL; } d308 1 a308 1 return thread; d318 2 a319 2 mutex->thread_id = MUTEX_STATE_NEVERLOCKED; mutex->line = -1; d322 1 a322 1 pthread_mutex_init(&mutex->sys_mutex, NULL); d327 1 a327 1 _mutex_create(mutex); d330 4 a333 4 _mutex_lock(&_mutextree_mutex); mutex->mutex_id = _next_mutex_id++; avl_insert(_mutextree, (void *)mutex); _mutex_unlock(&_mutextree_mutex); d339 1 a339 1 pthread_mutex_destroy(&mutex->sys_mutex); d342 3 a344 3 _mutex_lock(&_mutextree_mutex); avl_delete(_mutextree, mutex, _free_mutex); _mutex_unlock(&_mutextree_mutex); d351 1 a351 1 thread_type *th = thread_self(); d353 1 a353 1 if (!th) LOG_WARN("No mt record for %u in lock [%s:%d]", thread_self(), file, line); d355 1 a355 1 LOG_DEBUG5("Locking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); d358 44 a401 44 /* Just a little sanity checking to make sure that we're locking ** mutexes correctly */ if (th) { int locks = 0; avl_node *node; mutex_t *tmutex; _mutex_lock(&_mutextree_mutex); node = avl_get_first (_mutextree); while (node) { tmutex = (mutex_t *)node->key; if (tmutex->mutex_id == mutex->mutex_id) { if (tmutex->thread_id == th->thread_id) { /* Deadlock, same thread can't lock the same mutex twice */ LOG_ERROR7("DEADLOCK AVOIDED (%d == %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); _mutex_unlock(&_mutextree_mutex); return; } } else if (tmutex->thread_id == th->thread_id) { /* Mutex locked by this thread (not this mutex) */ locks++; } node = avl_get_next(node); } if (locks > 0) { /* Has already got a mutex locked */ if (_multi_mutex.thread_id != th->thread_id) { /* Tries to lock two mutexes, but has not got the double mutex, norty boy! */ LOG_WARN("(%d != %d) Thread %d [%s] tries to lock a second mutex [%s] in file %s line %d, without locking double mutex!", _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); } } _mutex_unlock(&_mutextree_mutex); } d403 10 a412 10 _mutex_lock(mutex); _mutex_lock(&_mutextree_mutex); LOG_DEBUG2("Locked %p by thread %d", mutex, th ? th->thread_id : -1); mutex->line = line; if (th) { mutex->thread_id = th->thread_id; } d414 1 a414 1 _mutex_unlock(&_mutextree_mutex); d416 1 a416 1 _mutex_lock(mutex); d423 1 a423 1 thread_type *th = thread_self(); d425 3 a427 3 if (!th) { LOG_ERROR3("No record for %u in unlock [%s:%d]", thread_self(), file, line); } d429 1 a429 1 LOG_DEBUG5("Unlocking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); d431 1 a431 1 mutex->line = line; d434 20 a453 30 if (th) { int locks = 0; avl_node *node; mutex_t *tmutex; _mutex_lock(&_mutextree_mutex); while (node) { tmutex = (mutex_t *)node->key; if (tmutex->mutex_id == mutex->mutex_id) { if (tmutex->thread_id != th->thread_id) { LOG_ERROR7("ILLEGAL UNLOCK (%d != %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, mutex->name ? mutex->name : "undefined", file, line, th->thread_id, th->name); _mutex_unlock(&_mutextree_mutex); return; } } else if (tmutex->thread_id == th->thread_id) { locks++; } node = avl_get_next (node); } if ((locks > 0) && (_multi_mutex.thread_id != th->thread_id)) { /* Don't have double mutex, has more than this mutex left */ LOG_WARN("(%d != %d) Thread %d [%s] tries to unlock a mutex [%s] in file %s line %d, without owning double mutex!", _multi_mutex.thread_id, th->thread_id, th->thread_id, th->name, mutex->name ? mutex->name : "undefined", file, line); } d455 12 a466 2 _mutex_unlock(&_mutextree_mutex); } d469 1 a469 1 _mutex_unlock(mutex); d471 1 a471 1 _mutex_lock(&_mutextree_mutex); d473 5 a477 5 LOG_DEBUG2("Unlocked %p by thread %d", mutex, th ? th->thread_id : -1); mutex->line = -1; if (mutex->thread_id == th->thread_id) { mutex->thread_id = MUTEX_STATE_NOTLOCKED; } d479 1 a479 1 _mutex_unlock(&_mutextree_mutex); d481 1 a481 1 _mutex_unlock(mutex); d487 2 a488 2 pthread_cond_init(&cond->sys_cond, NULL); pthread_mutex_init(&cond->cond_mutex, NULL); d493 2 a494 2 pthread_mutex_destroy(&cond->cond_mutex); pthread_cond_destroy(&cond->sys_cond); d499 1 a499 1 pthread_cond_signal(&cond->sys_cond); d504 1 a504 1 pthread_cond_broadcast(&cond->sys_cond); d521 3 a523 3 pthread_mutex_lock(&cond->cond_mutex); pthread_cond_wait(&cond->sys_cond, &cond->cond_mutex); pthread_mutex_unlock(&cond->cond_mutex); d528 1 a528 1 pthread_rwlock_init(&rwlock->sys_rwlock, NULL); d533 1 a533 1 pthread_rwlock_destroy(&rwlock->sys_rwlock); d538 1 a538 1 pthread_rwlock_rdlock(&rwlock->sys_rwlock); d543 1 a543 1 pthread_rwlock_wrlock(&rwlock->sys_rwlock); d548 1 a548 1 pthread_rwlock_unlock(&rwlock->sys_rwlock); d553 1 a553 1 thread_type *th = thread_self(); d556 14 a569 4 if (th) { avl_node *node; mutex_t *tmutex; char name[40]; d571 2 a572 4 _mutex_lock(&_mutextree_mutex); while (node) { tmutex = (mutex_t *)node->key; d574 2 a575 10 if (tmutex->thread_id == th->thread_id) { LOG_WARN("Thread %d [%s] exiting in file %s line %d, without unlocking mutex [%s]", th->thread_id, th->name, file, line, mutex_to_string(tmutex, name)); } node = avl_get_next (node); } _mutex_unlock(&_mutextree_mutex); } d577 2 a578 2 if (th) { d580 1 a580 1 LOG_INFO4("Removing thread %d [%s] started at [%s:%d], reason: 'Thread Exited'", th->thread_id, th->name, th->file, th->line); d583 6 a588 6 _mutex_lock(&_threadtree_mutex); avl_delete(_threadtree, th, _free_thread_if_detached); _mutex_unlock(&_threadtree_mutex); } pthread_exit((void *)val); d595 1 a595 1 Sleep(len / 1000); d598 14 a611 14 struct timespec time_sleep; struct timespec time_remaining; int ret; time_sleep.tv_sec = len / 1000000; time_sleep.tv_nsec = (len % 1000000) * 1000; ret = nanosleep(&time_sleep, &time_remaining); while (ret != 0 && errno == EINTR) { time_sleep.tv_sec = time_remaining.tv_sec; time_sleep.tv_nsec = time_remaining.tv_nsec; ret = nanosleep(&time_sleep, &time_remaining); } d613 1 a613 1 struct timeval tv; d615 2 a616 2 tv.tv_sec = len / 1000000; tv.tv_usec = (len % 1000000); d618 1 a618 1 select(0, NULL, NULL, NULL, &tv); d625 4 a628 4 thread_start_t *start = (thread_start_t *)arg; void *(*start_routine)(void *) = start->start_routine; void *real_arg = start->arg; thread_type *thread = start->thread; d631 1 a631 1 _block_signals(); d633 1 a633 1 free(start); d635 5 a639 5 /* insert thread into thread tree here */ _mutex_lock(&_threadtree_mutex); thread->sys_thread = pthread_self(); avl_insert(_threadtree, (void *)thread); _mutex_unlock(&_threadtree_mutex); d642 1 a642 1 LOG_INFO4("Added thread %d [%s] started at [%s:%d]", thread->thread_id, thread->name, thread->file, thread->line); d645 2 a646 2 if (detach) { pthread_detach(thread->sys_thread); d648 2 a649 2 } pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); d651 4 a654 4 /* call the real start_routine and start the thread ** this should never exit! */ (start_routine)(real_arg); d657 1 a657 1 LOG_WARN("Thread x should never exit from here!!!"); d660 1 a660 1 return NULL; d665 3 a667 3 avl_node *node; thread_type *th; pthread_t sys_thread = pthread_self(); d669 1 a669 1 _mutex_lock(&_threadtree_mutex); d671 1 a671 1 if (_threadtree == NULL) { d673 1 a673 1 LOG_WARN("Thread tree is empty, this must be wrong!"); d675 17 a691 17 _mutex_unlock(&_threadtree_mutex); return NULL; } node = avl_get_first(_threadtree); while (node) { th = (thread_type *)node->key; if (th && pthread_equal(sys_thread, th->sys_thread)) { _mutex_unlock(&_threadtree_mutex); return th; } node = avl_get_next(node); } _mutex_unlock(&_threadtree_mutex); d695 1 a695 1 LOG_ERROR("Nonexistant thread alive..."); d697 2 a698 2 return NULL; d703 1 a703 1 thread_type *th; d705 2 a706 2 th = thread_self(); if (th->name) free(th->name); d708 1 a708 1 th->name = strdup(name); d713 1 a713 1 pthread_mutex_lock(&mutex->sys_mutex); d718 1 a718 1 pthread_mutex_unlock(&mutex->sys_mutex); d724 1 a724 1 _mutex_lock(&_library_mutex); d729 1 a729 1 _mutex_unlock(&_library_mutex); d734 2 a735 2 void *ret; int i; d737 1 a737 1 i = pthread_join(thread->sys_thread, &ret); d748 1 a748 1 mutex_t *m1, *m2; d750 2 a751 2 m1 = (mutex_t *)a; m2 = (mutex_t *)b; d753 5 a757 5 if (m1->mutex_id > m2->mutex_id) return 1; if (m1->mutex_id < m2->mutex_id) return -1; return 0; d763 1 a763 1 thread_type *t1, *t2; d765 2 a766 2 t1 = (thread_type *)a; t2 = (thread_type *)b; d768 5 a772 5 if (t1->thread_id > t2->thread_id) return 1; if (t1->thread_id < t2->thread_id) return -1; return 0; d778 1 a778 1 mutex_t *m; d780 1 a780 1 m = (mutex_t *)key; d782 4 a785 4 if (m && m->file) { free(m->file); m->file = NULL; } d787 1 a787 1 /* all mutexes are static. don't need to free them */ d789 1 a789 1 return 1; d795 1 a795 1 thread_type *t; d797 1 a797 1 t = (thread_type *)key; d799 4 a802 4 if (t->file) free(t->file); if (t->name) free(t->name); d804 1 a804 1 free(t); d806 1 a806 1 return 1; @ 1.22 log @reduce include file namespace clutter for libshout and the associated smaller libs. @ text @a740 1 _free_thread(thread); @ 1.21 log @include the automake config.h file if the application defines one @ text @d45 2 a46 2 #include "thread.h" #include "avl.h" d48 1 a48 1 #include "log.h" @ 1.20 log @Make various thread structures omit the bits only used in debug mode. Some of these are pretty heavily used, so saving 10-20 bytes each can be quite significant. No functional differences. @ text @d21 4 @ 1.19 log @Fix some warnings, fix cflags. @ text @a86 1 static int _logid = -1; d90 2 d94 4 d99 3 d103 1 d107 3 d112 3 d119 1 d121 3 a124 1 static int _free_mutex(void *key); d152 1 a161 1 #ifdef DEBUG_MUTEXES d194 1 d198 1 d313 1 d316 1 a521 2 static int rwlocknum = 0; d742 1 d756 1 d772 1 d788 1 @ 1.18 log @Rename thread_t to avoid problems on OS X @ text @d91 2 a92 1 static mutex_t _threadtree_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1 }; d96 4 a99 2 static mutex_t _mutextree_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1 }; static mutex_t _library_mutex = { -1, NULL, MUTEX_STATE_UNINIT, NULL, -1 }; @ 1.17 log @Lots of bugfixes contributed by Karl Heyes. @ text @d83 1 a83 1 thread_t *thread; d121 1 a121 1 thread_t *thread; d152 1 a152 1 thread = (thread_t *)malloc(sizeof(thread_t)); d241 2 a242 1 thread_t *thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file) d245 1 a245 1 thread_t *thread; d248 1 a248 1 thread = (thread_t *)malloc(sizeof(thread_t)); d322 1 a322 1 thread_t *th = thread_self(); d394 1 a394 1 thread_t *th = thread_self(); d526 1 a526 1 thread_t *th = thread_self(); d601 1 a601 1 thread_t *thread = start->thread; d636 1 a636 1 thread_t *thread_self(void) d639 1 a639 1 thread_t *th; d655 1 a655 1 th = (thread_t *)node->key; d676 1 a676 1 thread_t *th; d705 1 a705 1 void thread_join(thread_t *thread) d735 1 a735 1 thread_t *t1, *t2; d737 2 a738 2 t1 = (thread_t *)a; t2 = (thread_t *)b; d765 1 a765 1 thread_t *t; d767 1 a767 1 t = (thread_t *)key; d781 1 a781 1 thread_t *t = key; @ 1.16 log @Bugfix: thread_join is often called after a thread has already exited, which it does using thread_exit(). thread_exit() was freeing the thread structure, so thread_join was using freed memory. Rearrange things so that if the thread is detached, the freeing happens in thread_join instead. @ text @d710 3 @ 1.15 log @Liberally sprinkle #ifdef THREAD_DEBUG around so libshout doesn't need to link with it. @ text @d105 1 d258 1 d556 1 a556 1 avl_delete(_threadtree, th, _free_thread); d619 1 d710 1 d775 7 @ 1.14 log @Timing fixes @ text @a40 1 #include "log.h" d43 3 d51 1 d69 1 d124 1 a125 2 #ifdef THREAD_DEBUG d180 1 a182 1 log_shutdown(); d205 2 a206 1 if (pthread_sigmask(SIG_BLOCK, &ss, NULL) != 0) d209 2 d231 2 a232 1 if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL) != 0) d235 2 d266 1 d269 1 d272 1 d274 1 d549 1 d551 1 d611 1 d613 1 d625 1 d627 1 d641 1 d643 1 d663 1 d665 1 @ 1.13 log @Various cleanups @ text @d571 1 a571 1 tv.tv_usec = (len % 1000000) / 1000; @ 1.12 log @oddsock's xslt stats support, slightly cleaned up @ text @d231 1 a231 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file) d262 1 a262 1 return -1; d265 1 a265 2 // return thread->thread_id; return thread->sys_thread; d678 1 a678 1 void thread_join(long thread) d683 1 a683 1 i = pthread_join(thread, &ret); @ 1.11 log @Cleaned up version of Ciaran Anscomb's relaying patch. @ text @a117 5 /* this must be called to init pthreads-win32 */ #ifdef _WIN32 ptw32_processInitialize(); #endif d127 1 a127 1 /* create all the interal mutexes, and initialize the mutex tree */ @ 1.10 log @Lots of patches committable now that my sound card works properly again. logging API changed slightly (I got sick of gcc warnings about deprecated features). resampling (for live input, not yet for reencoding) is in there. several patches from Karl Heyes have been incorporated. @ text @d468 12 d486 2 @ 1.9 log @Don't use start after freeing it in thread startup code. @ text @d50 16 a65 16 #define LOG_ERROR(y) log_write(_logid, 1, CATMODULE "/" __FUNCTION__, y) #define LOG_ERROR3(y, z1, z2, z3) log_write(_logid, 1, CATMODULE "/" __FUNCTION__, y, z1, z2, z3) #define LOG_ERROR7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 1, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) #define LOG_WARN(y) log_write(_logid, 2, CATMODULE "/" __FUNCTION__, y) #define LOG_WARN3(y, z1, z2, z3) log_write(_logid, 2, CATMODULE "/" __FUNCTION__, y, z1, z2, z3) #define LOG_WARN5(y, z1, z2, z3, z4, z5) log_write(_logid, 2, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4, z5) #define LOG_WARN7(y, z1, z2, z3, z4, z5, z6, z7) log_write(_logid, 2, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4, z5, z6, z7) #define LOG_INFO(y) log_write(_logid, 3, CATMODULE "/" __FUNCTION__, y) #define LOG_INFO4(y, z1, z2, z3, z4) log_write(_logid, 3, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4) #define LOG_INFO5(y, z1, z2, z3, z4, z5) log_write(_logid, 3, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4, z5) #define LOG_DEBUG(y) log_write(_logid, 4, CATMODULE "/" __FUNCTION__, y) #define LOG_DEBUG2(y, z1, z2) log_write(_logid, 4, CATMODULE "/" __FUNCTION__, y, z1, z2) #define LOG_DEBUG5(y, z1, z2, z3, z4, z5) log_write(_logid, 4, CATMODULE "/" __FUNCTION__, y, z1, z2, z3, z4, z5) a257 1 @ 1.8 log @win32 patches from Ed @ text @d577 1 d591 1 a591 1 if (start->detached) { @ 1.7 log @More win32 fixes. @ text @d28 2 d36 1 a38 2 #include d46 1 a46 1 #define __FUNCTION__ __FILE__ ":" __LINE__ @ 1.6 log @minor build fixes for win32 courtesy of Oddsock @ text @d117 5 @ 1.5 log @Revert the stacksize work. It's stupid. The original patch from Ben Laurie some years ago was needed because FreeBSD's default stack size was < 8k and this wasn't acceptable. Both Linux and Solaris had reasonable defaults for stacksize, or grew the stack as needed to a reasonable size. Testing today and consulting documentation shows that the default stack sizes on FreeBSD, Linux, and Solaris are all acceptable. Linux can grow to 2MB, 32bit Solaris defaults to 1MB, 64bit Solaris defaults to 2MB, and FreeBSD defaults to 64k. In my opinion FreeBSD needs to get with the program and provide a reasonable default. 64k is enough for us, but might not be for others. @ text @d31 3 d43 4 @ 1.4 log @Stack size per thread needs to be configurable. Setting it on a global bases is not enough. ices and icecast need this to be different, and if one is interested in tuning memory usage, one will want to alter this per thread. @ text @d223 1 a223 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int stacksize, int detached, int line, char *file) a224 1 pthread_attr_t attr; a245 2 pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, stacksize); d248 1 a248 1 if (pthread_create(&thread->sys_thread, &attr, _start_routine, start) == 0) a251 2 pthread_attr_destroy(&attr); @ 1.3 log @Win32 fixes. Specifically a header change and not using the gcc extensions for vararg macros. It's not as pretty, but it works. @ text @a58 3 /* INTERNAL DATA */ #define STACKSIZE 8192 d223 1 a223 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file) d248 1 a248 1 pthread_attr_setstacksize(&attr, STACKSIZE); @ 1.2 log @Oddsock found this bug when working with icecast2 on freebsd. Nanoseconds were off by a few orders of magnitude. @ text @a26 1 #include d30 1 d42 16 a57 4 #define LOG_ERROR(y, z...) log_write(_logid, 1, CATMODULE "/" __FUNCTION__, y, ##z) #define LOG_WARN(y, z...) log_write(_logid, 2, CATMODULE "/" __FUNCTION__, y, ##z) #define LOG_INFO(y, z...) log_write(_logid, 3, CATMODULE "/" __FUNCTION__, y, ##z) #define LOG_DEBUG(y, z...) log_write(_logid, 4, CATMODULE "/" __FUNCTION__, y, ##z) d312 1 a312 1 LOG_DEBUG("Locking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); d334 1 a334 1 LOG_ERROR("DEADLOCK AVOIDED (%d == %d) on mutex [%s] in file %s line %d by thread %d [%s]", d365 1 a365 1 LOG_DEBUG("Locked %p by thread %d", mutex, th ? th->thread_id : -1); d383 1 a383 1 LOG_ERROR("No record for %u in unlock [%s:%d]", thread_self(), file, line); d386 1 a386 1 LOG_DEBUG("Unlocking %p (%s) on line %d in file %s by thread %d", mutex, mutex->name, line, file, th ? th->thread_id : -1); d403 1 a403 1 LOG_ERROR("ILLEGAL UNLOCK (%d != %d) on mutex [%s] in file %s line %d by thread %d [%s]", tmutex->thread_id, th->thread_id, d430 1 a430 1 LOG_DEBUG("Unlocked %p by thread %d", mutex, th ? th->thread_id : -1); d524 1 a524 1 LOG_INFO("Removing thread %d [%s] started at [%s:%d], reason: 'Thread Exited'", th->thread_id, th->name, th->file, th->line); d583 1 a583 1 LOG_INFO("Added thread %d [%s] started at [%s:%d]", thread->thread_id, thread->name, thread->file, thread->line); @ 1.1 log @Initial revision @ text @d534 1 a534 1 time_sleep.tv_nsec = len % 1000000; @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/thread/thread.h,v0000664000175100017510000003167212203665125026421 0ustar mhaggermhagger00000000000000head 1.13; access; symbols libshout-2_0:1.12 libshout-2_0b3:1.12 libshout-2_0b2:1.11 libshout_2_0b1:1.11 libogg2-zerocopy:1.7.0.2 branch-beta2-rewrite:1.4.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @ * @; 1.13 date 2003.07.14.02.17.52; author brendan; state Exp; branches; next 1.12; 1.12 date 2003.07.07.20.38.34; author brendan; state Exp; branches; next 1.11; 1.11 date 2003.03.15.02.10.18; author msmith; state Exp; branches; next 1.10; 1.10 date 2003.03.05.19.52.10; author brendan; state Exp; branches; next 1.9; 1.9 date 2003.03.04.15.31.34; author msmith; state Exp; branches; next 1.8; 1.8 date 2002.12.29.09.55.50; author msmith; state Exp; branches; next 1.7; 1.7 date 2002.09.24.07.09.08; author msmith; state Exp; branches; next 1.6; 1.6 date 2002.08.10.03.22.44; author msmith; state Exp; branches; next 1.5; 1.5 date 2002.08.05.14.48.04; author msmith; state Exp; branches; next 1.4; 1.4 date 2001.10.21.02.04.27; author jack; state Exp; branches; next 1.3; 1.3 date 2001.10.20.22.40.28; author jack; state Exp; branches; next 1.2; 1.2 date 2001.10.20.22.27.52; author jack; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.26.33; author jack; state Exp; branches; next ; desc @@ 1.13 log @Assign LGP to thread module @ text @/* thread.h * - Thread Abstraction Function Headers * * Copyright (c) 1999, 2000 the icecast team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __THREAD_H__ #define __THREAD_H__ #include /* renamed from thread_t due to conflict on OS X */ typedef struct { /* the local id for the thread, and it's name */ long thread_id; char *name; /* the time the thread was created */ time_t create_time; /* the file and line which created this thread */ char *file; int line; /* is the thread running detached? */ int detached; /* the system specific thread */ pthread_t sys_thread; } thread_type; typedef struct { #ifdef DEBUG_MUTEXES /* the local id and name of the mutex */ long mutex_id; char *name; /* the thread which is currently locking this mutex */ long thread_id; /* the file and line where the mutex was locked */ char *file; int line; #endif /* the system specific mutex */ pthread_mutex_t sys_mutex; } mutex_t; typedef struct { #ifdef THREAD_DEBUG long cond_id; char *name; #endif pthread_mutex_t cond_mutex; pthread_cond_t sys_cond; } cond_t; typedef struct { #ifdef THREAD_DEBUG long rwlock_id; char *name; /* information on which thread and where in the code ** this rwlock was write locked */ long thread_id; char *file; int line; #endif pthread_rwlock_t sys_rwlock; } rwlock_t; #define thread_create(n,x,y,z) thread_create_c(n,x,y,z,__LINE__,__FILE__) #define thread_mutex_create(x) thread_mutex_create_c(x,__LINE__,__FILE__) #define thread_mutex_lock(x) thread_mutex_lock_c(x,__LINE__,__FILE__) #define thread_mutex_unlock(x) thread_mutex_unlock_c(x,__LINE__,__FILE__) #define thread_cond_create(x) thread_cond_create_c(x,__LINE__,__FILE__) #define thread_cond_signal(x) thread_cond_signal_c(x,__LINE__,__FILE__) #define thread_cond_broadcast(x) thread_cond_broadcast_c(x,__LINE__,__FILE__) #define thread_cond_wait(x) thread_cond_wait_c(x,__LINE__,__FILE__) #define thread_cond_timedwait(x,t) thread_cond_wait_c(x,t,__LINE__,__FILE__) #define thread_rwlock_create(x) thread_rwlock_create_c(x,__LINE__,__FILE__) #define thread_rwlock_rlock(x) thread_rwlock_rlock_c(x,__LINE__,__FILE__) #define thread_rwlock_wlock(x) thread_rwlock_wlock_c(x,__LINE__,__FILE__) #define thread_rwlock_unlock(x) thread_rwlock_unlock_c(x,__LINE__,__FILE__) #define thread_exit(x) thread_exit_c(x,__LINE__,__FILE__) #define MUTEX_STATE_NOTLOCKED -1 #define MUTEX_STATE_NEVERLOCKED -2 #define MUTEX_STATE_UNINIT -3 #define THREAD_DETACHED 1 #define THREAD_ATTACHED 0 #ifdef _mangle # define thread_initialize _mangle(thread_initialize) # define thread_initialize_with_log_id _mangle(thread_initialize_with_log_id) # define thread_shutdown _mangle(thread_shutdown) # define thread_create_c _mangle(thread_create_c) # define thread_mutex_create_c _mangle(thread_mutex_create) # define thread_mutex_lock_c _mangle(thread_mutex_lock_c) # define thread_mutex_unlock_c _mangle(thread_mutex_unlock_c) # define thread_mutex_destroy _mangle(thread_mutex_destroy) # define thread_cond_create_c _mangle(thread_cond_create_c) # define thread_cond_signal_c _mangle(thread_cond_signal_c) # define thread_cond_broadcast_c _mangle(thread_cond_broadcast_c) # define thread_cond_wait_c _mangle(thread_cond_wait_c) # define thread_cond_timedwait_c _mangle(thread_cond_timedwait_c) # define thread_cond_destroy _mangle(thread_cond_destroy) # define thread_rwlock_create_c _mangle(thread_rwlock_create_c) # define thread_rwlock_rlock_c _mangle(thread_rwlock_rlock_c) # define thread_rwlock_wlock_c _mangle(thread_rwlock_wlock_c) # define thread_rwlock_unlock_c _mangle(thread_rwlock_unlock_c) # define thread_rwlock_destroy _mangle(thread_rwlock_destroy) # define thread_exit_c _mangle(thread_exit_c) # define thread_sleep _mangle(thread_sleep) # define thread_library_lock _mangle(thread_library_lock) # define thread_library_unlock _mangle(thread_library_unlock) # define thread_self _mangle(thread_self) # define thread_rename _mangle(thread_rename) # define thread_join _mangle(thread_join) #endif /* init/shutdown of the library */ void thread_initialize(void); void thread_initialize_with_log_id(int log_id); void thread_shutdown(void); /* creation, destruction, locking, unlocking, signalling and waiting */ thread_type *thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file); void thread_mutex_create_c(mutex_t *mutex, int line, char *file); void thread_mutex_lock_c(mutex_t *mutex, int line, char *file); void thread_mutex_unlock_c(mutex_t *mutex, int line, char *file); void thread_mutex_destroy(mutex_t *mutex); void thread_cond_create_c(cond_t *cond, int line, char *file); void thread_cond_signal_c(cond_t *cond, int line, char *file); void thread_cond_broadcast_c(cond_t *cond, int line, char *file); void thread_cond_wait_c(cond_t *cond, int line, char *file); void thread_cond_timedwait_c(cond_t *cond, int millis, int line, char *file); void thread_cond_destroy(cond_t *cond); void thread_rwlock_create_c(rwlock_t *rwlock, int line, char *file); void thread_rwlock_rlock_c(rwlock_t *rwlock, int line, char *file); void thread_rwlock_wlock_c(rwlock_t *rwlock, int line, char *file); void thread_rwlock_unlock_c(rwlock_t *rwlock, int line, char *file); void thread_rwlock_destroy(rwlock_t *rwlock); void thread_exit_c(int val, int line, char *file); /* sleeping */ void thread_sleep(unsigned long len); /* for using library functions which aren't threadsafe */ void thread_library_lock(void); void thread_library_unlock(void); #define PROTECT_CODE(code) { thread_library_lock(); code; thread_library_unlock(); } /* thread information functions */ thread_type *thread_self(void); /* renames current thread */ void thread_rename(const char *name); /* waits until thread_exit is called for another thread */ void thread_join(thread_type *thread); #endif /* __THREAD_H__ */ @ 1.12 log @The last of the convenience lib cleanups. A little forethought in designing a keyboard macro made this one a lot easier. @ text @d4 1 a4 1 * Copyright (c) 1999, 2000 the icecast team d6 4 a9 13 * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. d11 8 @ 1.11 log @Brendan was getting pissed off about inconsistent indentation styles. Convert all tabs to 4 spaces. All code must now use 4 space indents. @ text @d114 29 a185 3 @ 1.10 log @Remove some namespace pollution @ text @d30 10 a39 10 /* the local id for the thread, and it's name */ long thread_id; char *name; /* the time the thread was created */ time_t create_time; /* the file and line which created this thread */ char *file; int line; d41 2 a42 2 /* is the thread running detached? */ int detached; d44 2 a45 2 /* the system specific thread */ pthread_t sys_thread; d50 10 a59 10 /* the local id and name of the mutex */ long mutex_id; char *name; /* the thread which is currently locking this mutex */ long thread_id; /* the file and line where the mutex was locked */ char *file; int line; d63 2 a64 2 /* the system specific mutex */ pthread_mutex_t sys_mutex; d69 2 a70 2 long cond_id; char *name; d73 2 a74 2 pthread_mutex_t cond_mutex; pthread_cond_t sys_cond; d79 2 a80 2 long rwlock_id; char *name; d82 6 a87 6 /* information on which thread and where in the code ** this rwlock was write locked */ long thread_id; char *file; int line; d90 1 a90 1 pthread_rwlock_t sys_rwlock; @ 1.9 log @Make various thread structures omit the bits only used in debug mode. Some of these are pretty heavily used, so saving 10-20 bytes each can be quite significant. No functional differences. @ text @d29 1 a29 1 typedef struct thread_tag { d41 2 a42 2 /* is the thread running detached? */ int detached; d48 1 a48 1 typedef struct mutex_tag { d67 1 a67 1 typedef struct cond_tag { d77 1 a77 1 typedef struct rwlock_tag { @ 1.8 log @Rename thread_t to avoid problems on OS X @ text @d49 1 d61 2 d68 1 d71 1 d78 1 d88 1 @ 1.7 log @Bugfix: thread_join is often called after a thread has already exited, which it does using thread_exit(). thread_exit() was freeing the thread structure, so thread_join was using freed memory. Rearrange things so that if the thread is detached, the freeing happens in thread_join instead. @ text @d27 2 d46 1 a46 1 } thread_t; d113 2 a114 1 thread_t *thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file); d141 1 a141 1 thread_t *thread_self(void); d147 1 a147 1 void thread_join(thread_t *thread); @ 1.6 log @Various cleanups @ text @d39 3 @ 1.5 log @Cleaned up version of Ciaran Anscomb's relaying patch. @ text @d108 1 a108 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file); d141 1 a141 1 void thread_join(long thread); @ 1.4 log @Revert the stacksize work. It's stupid. The original patch from Ben Laurie some years ago was needed because FreeBSD's default stack size was < 8k and this wasn't acceptable. Both Linux and Solaris had reasonable defaults for stacksize, or grew the stack as needed to a reasonable size. Testing today and consulting documentation shows that the default stack sizes on FreeBSD, Linux, and Solaris are all acceptable. Linux can grow to 2MB, 32bit Solaris defaults to 1MB, 64bit Solaris defaults to 2MB, and FreeBSD defaults to 64k. In my opinion FreeBSD needs to get with the program and provide a reasonable default. 64k is enough for us, but might not be for others. @ text @d89 1 d117 1 @ 1.3 log @Fix header definition. @ text @a26 2 #define THREAD_DEFAULT_STACKSIZE 8192 d81 1 a81 1 #define thread_create(n,w,x,y,z) thread_create_c(n,w,x,y,z,__LINE__,__FILE__) d107 1 a107 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int stacksize, int detached, int line, char *file); @ 1.2 log @Stack size per thread needs to be configurable. Setting it on a global bases is not enough. ices and icecast need this to be different, and if one is interested in tuning memory usage, one will want to alter this per thread. @ text @d109 1 a109 1 long thread_create_c(char *name, void *(*start_routine)(void *), void *arg, int detached, int line, char *file); @ 1.1 log @Initial revision @ text @d27 2 d83 1 a83 1 #define thread_create(n,x,y,z) thread_create_c(n,x,y,z,__LINE__,__FILE__) @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/0000775000175100017510000000000013206450463024417 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/httpp.h,v0000664000175100017510000001110412203665125026165 0ustar mhaggermhagger00000000000000head 1.10; access; symbols libshout-2_0:1.10 libshout-2_0b3:1.10 libshout-2_0b2:1.9 libshout_2_0b1:1.9 libogg2-zerocopy:1.4.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @ * @; 1.10 date 2003.07.07.01.49.27; author brendan; state Exp; branches; next 1.9; 1.9 date 2003.03.15.02.10.18; author msmith; state Exp; branches; next 1.8; 1.8 date 2003.03.09.22.56.46; author karl; state Exp; branches; next 1.7; 1.7 date 2003.03.08.04.57.02; author msmith; state Exp; branches; next 1.6; 1.6 date 2003.01.16.05.48.31; author brendan; state Exp; branches; next 1.5; 1.5 date 2002.12.31.06.28.39; author msmith; state Exp; branches; next 1.4; 1.4 date 2002.08.16.14.22.44; author msmith; state Exp; branches; next 1.3; 1.3 date 2002.08.05.14.48.03; author msmith; state Exp; branches; next 1.2; 1.2 date 2002.05.03.15.04.56; author msmith; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches; next ; desc @@ 1.10 log @httpp goes through the rinse cycle @ text @/* httpp.h ** ** http parsing library */ #ifndef __HTTPP_H #define __HTTPP_H #include #define HTTPP_VAR_PROTOCOL "__protocol" #define HTTPP_VAR_VERSION "__version" #define HTTPP_VAR_URI "__uri" #define HTTPP_VAR_REQ_TYPE "__req_type" #define HTTPP_VAR_ERROR_MESSAGE "__errormessage" #define HTTPP_VAR_ERROR_CODE "__errorcode" #define HTTPP_VAR_ICYPASSWORD "__icy_password" typedef enum httpp_request_type_tag { httpp_req_none, httpp_req_get, httpp_req_post, httpp_req_head, httpp_req_source, httpp_req_play, httpp_req_stats, httpp_req_unknown } httpp_request_type_e; typedef struct http_var_tag { char *name; char *value; } http_var_t; typedef struct http_varlist_tag { http_var_t var; struct http_varlist_tag *next; } http_varlist_t; typedef struct http_parser_tag { httpp_request_type_e req_type; char *uri; avl_tree *vars; avl_tree *queryvars; } http_parser_t; #ifdef _mangle # define httpp_create_parser _mangle(httpp_create_parser) # define httpp_initialize _mangle(httpp_initialize) # define httpp_parse _mangle(httpp_parse) # define httpp_parse_icy _mangle(httpp_parse_icy) # define httpp_parse_response _mangle(httpp_parse_response) # define httpp_setvar _mangle(httpp_setvar) # define httpp_getvar _mangle(httpp_getvar) # define httpp_set_query_param _mangle(httpp_set_query_param) # define httpp_get_query_param _mangle(httpp_get_query_param) # define httpp_destroy _mangle(httpp_destroy) # define httpp_clear _mangle(httpp_clear) #endif http_parser_t *httpp_create_parser(void); void httpp_initialize(http_parser_t *parser, http_varlist_t *defaults); int httpp_parse(http_parser_t *parser, char *http_data, unsigned long len); int httpp_parse_icy(http_parser_t *parser, char *http_data, unsigned long len); int httpp_parse_response(http_parser_t *parser, char *http_data, unsigned long len, char *uri); void httpp_setvar(http_parser_t *parser, char *name, char *value); char *httpp_getvar(http_parser_t *parser, char *name); void httpp_set_query_param(http_parser_t *parser, char *name, char *value); char *httpp_get_query_param(http_parser_t *parser, char *name); void httpp_destroy(http_parser_t *parser); void httpp_clear(http_parser_t *parser); #endif @ 1.9 log @Brendan was getting pissed off about inconsistent indentation styles. Convert all tabs to 4 spaces. All code must now use 4 space indents. @ text @d41 14 @ 1.8 log @reduce include file namespace clutter for libshout and the associated smaller libs. @ text @d20 2 a21 2 httpp_req_none, httpp_req_get, httpp_req_post, httpp_req_head, httpp_req_source, httpp_req_play, httpp_req_stats, httpp_req_unknown d25 2 a26 2 char *name; char *value; d30 2 a31 2 http_var_t var; struct http_varlist_tag *next; d35 4 a38 4 httpp_request_type_e req_type; char *uri; avl_tree *vars; avl_tree *queryvars; @ 1.7 log @Added support for shoutcast login protocol (ewww...) @ text @d9 1 a9 1 #include "avl.h" @ 1.6 log @Indentation again, don't mind me @ text @d17 1 d44 1 @ 1.5 log @mp3 metadata complete. Still untested. @ text @d19 2 a20 1 httpp_req_none, httpp_req_get, httpp_req_post, httpp_req_head, httpp_req_source, httpp_req_play, httpp_req_stats, httpp_req_unknown d37 1 a37 1 avl_tree *queryvars; a51 3 @ 1.4 log @bugfixes for httpp_parse_response @ text @d36 1 d45 2 @ 1.3 log @Cleaned up version of Ciaran Anscomb's relaying patch. @ text @d16 1 @ 1.2 log @Memory leaks. Lots of little ones. @ text @d15 1 a33 1 d40 1 @ 1.1 log @Initial revision @ text @d43 1 @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/BUILDING,v0000664000175100017510000000102712203665125026000 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @defines that affect compilation none library dependencies uses avl @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/COPYING,v0000664000175100017510000006221412203665125025720 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @ GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/TODO,v0000664000175100017510000000075212203665125025354 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @- nothing i can think of @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/README,v0000664000175100017510000000106512203665125025542 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols libshout-2_0:1.1.1.1 libshout-2_0b3:1.1.1.1 libshout-2_0b2:1.1.1.1 libshout_2_0b1:1.1.1.1 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @httpp is a simple http parser licensed under the lgpl created by jack moffitt @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/Makefile.am,v0000664000175100017510000000261112203665125026714 0ustar mhaggermhagger00000000000000head 1.3; access; symbols libshout-2_0:1.3 libshout-2_0b3:1.3 libshout-2_0b2:1.3 libshout_2_0b1:1.3 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @# @; 1.3 date 2003.03.09.22.56.46; author karl; state Exp; branches; next 1.2; 1.2 date 2003.03.08.00.46.58; author karl; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches; next ; desc @@ 1.3 log @reduce include file namespace clutter for libshout and the associated smaller libs. @ text @## Process this with automake to create Makefile.in AUTOMAKE_OPTIONS = foreign noinst_LTLIBRARIES = libicehttpp.la noinst_HEADERS = httpp.h libicehttpp_la_SOURCES = httpp.c libicehttpp_la_CFLAGS = @@XIPH_CFLAGS@@ INCLUDES = -I$(srcdir)/.. # SCCS stuff (for BitKeeper) GET = true debug: $(MAKE) all CFLAGS="@@DEBUG@@" profile: $(MAKE) all CFLAGS="@@PROFILE@@" @ 1.2 log @more on the XIPH_CFLAGS. For the smaller libs like thread etc put any passed flags into the compiling rules. Also configure in libshout now sets up the XIPH_CFLAGS @ text @d11 1 a11 1 INCLUDES = -I$(srcdir)/../avl -I$(srcdir)/../thread @ 1.1 log @Initial revision @ text @d9 1 d17 1 a17 1 $(MAKE) all CFLAGS="@@DEBUG@@" d20 1 a20 1 $(MAKE) all CFLAGS="@@PROFILE@@" @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/httpp.c,v0000664000175100017510000010430412203665125026165 0ustar mhaggermhagger00000000000000head 1.23; access; symbols libshout-2_0:1.23 libshout-2_0b3:1.23 libshout-2_0b2:1.22 libshout_2_0b1:1.22 libogg2-zerocopy:1.8.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @ * @; 1.23 date 2003.07.07.01.49.27; author brendan; state Exp; branches; next 1.22; 1.22 date 2003.06.18.15.52.25; author karl; state Exp; branches; next 1.21; 1.21 date 2003.06.18.11.13.11; author karl; state Exp; branches; next 1.20; 1.20 date 2003.06.09.22.30.09; author brendan; state Exp; branches; next 1.19; 1.19 date 2003.06.05.17.09.12; author brendan; state Exp; branches; next 1.18; 1.18 date 2003.03.15.02.10.18; author msmith; state Exp; branches; next 1.17; 1.17 date 2003.03.09.22.56.46; author karl; state Exp; branches; next 1.16; 1.16 date 2003.03.08.16.05.38; author karl; state Exp; branches; next 1.15; 1.15 date 2003.03.08.05.27.17; author msmith; state Exp; branches; next 1.14; 1.14 date 2003.03.08.04.57.02; author msmith; state Exp; branches; next 1.13; 1.13 date 2003.03.06.01.55.20; author brendan; state Exp; branches; next 1.12; 1.12 date 2003.01.17.09.01.04; author msmith; state Exp; branches; next 1.11; 1.11 date 2003.01.16.05.48.31; author brendan; state Exp; branches; next 1.10; 1.10 date 2003.01.15.23.46.56; author brendan; state Exp; branches; next 1.9; 1.9 date 2002.12.31.06.28.39; author msmith; state Exp; branches; next 1.8; 1.8 date 2002.08.16.14.22.44; author msmith; state Exp; branches; next 1.7; 1.7 date 2002.08.05.14.48.03; author msmith; state Exp; branches; next 1.6; 1.6 date 2002.05.03.15.04.56; author msmith; state Exp; branches; next 1.5; 1.5 date 2002.04.05.09.28.25; author msmith; state Exp; branches; next 1.4; 1.4 date 2002.02.11.09.11.18; author msmith; state Exp; branches; next 1.3; 1.3 date 2001.10.20.07.40.09; author jack; state Exp; branches; next 1.2; 1.2 date 2001.10.20.04.41.54; author jack; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.47; author jack; state Exp; branches; next ; desc @@ 1.23 log @httpp goes through the rinse cycle @ text @/* Httpp.c ** ** http parsing engine */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include #include "httpp.h" #ifdef _WIN32 #define strcasecmp stricmp #endif #define MAX_HEADERS 32 /* internal functions */ /* misc */ static char *_lowercase(char *str); /* for avl trees */ static int _compare_vars(void *compare_arg, void *a, void *b); static int _free_vars(void *key); http_parser_t *httpp_create_parser(void) { return (http_parser_t *)malloc(sizeof(http_parser_t)); } void httpp_initialize(http_parser_t *parser, http_varlist_t *defaults) { http_varlist_t *list; parser->req_type = httpp_req_none; parser->uri = NULL; parser->vars = avl_tree_new(_compare_vars, NULL); parser->queryvars = avl_tree_new(_compare_vars, NULL); /* now insert the default variables */ list = defaults; while (list != NULL) { httpp_setvar(parser, list->var.name, list->var.value); list = list->next; } } static int split_headers(char *data, unsigned long len, char **line) { /* first we count how many lines there are ** and set up the line[] array */ int lines = 0; unsigned long i; line[lines] = data; for (i = 0; i < len && lines < MAX_HEADERS; i++) { if (data[i] == '\r') data[i] = '\0'; if (data[i] == '\n') { lines++; data[i] = '\0'; if (i + 1 < len) { if (data[i + 1] == '\n' || data[i + 1] == '\r') break; line[lines] = &data[i + 1]; } } } i++; while (data[i] == '\n') i++; return lines; } static void parse_headers(http_parser_t *parser, char **line, int lines) { int i,l; int whitespace, where, slen; char *name = NULL; char *value = NULL; /* parse the name: value lines. */ for (l = 1; l < lines; l++) { where = 0; whitespace = 0; name = line[l]; value = NULL; slen = strlen(line[l]); for (i = 0; i < slen; i++) { if (line[l][i] == ':') { whitespace = 1; line[l][i] = '\0'; } else { if (whitespace) { whitespace = 0; while (i < slen && line[l][i] == ' ') i++; if (i < slen) value = &line[l][i]; break; } } } if (name != NULL && value != NULL) { httpp_setvar(parser, _lowercase(name), value); name = NULL; value = NULL; } } } int httpp_parse_response(http_parser_t *parser, char *http_data, unsigned long len, char *uri) { char *data; char *line[MAX_HEADERS]; int lines, slen,i, whitespace=0, where=0,code; char *version=NULL, *resp_code=NULL, *message=NULL; if(http_data == NULL) return 0; /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; lines = split_headers(data, len, line); /* In this case, the first line contains: * VERSION RESPONSE_CODE MESSAGE, such as HTTP/1.0 200 OK */ slen = strlen(line[0]); version = line[0]; for(i=0; i < slen; i++) { if(line[0][i] == ' ') { line[0][i] = 0; whitespace = 1; } else if(whitespace) { whitespace = 0; where++; if(where == 1) resp_code = &line[0][i]; else { message = &line[0][i]; break; } } } if(version == NULL || resp_code == NULL || message == NULL) { free(data); return 0; } httpp_setvar(parser, HTTPP_VAR_ERROR_CODE, resp_code); code = atoi(resp_code); if(code < 200 || code >= 300) { httpp_setvar(parser, HTTPP_VAR_ERROR_MESSAGE, message); } httpp_setvar(parser, HTTPP_VAR_URI, uri); httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "NONE"); parse_headers(parser, line, lines); free(data); return 1; } static int hex(char c) { if(c >= '0' && c <= '9') return c - '0'; else if(c >= 'A' && c <= 'F') return c - 'A' + 10; else if(c >= 'a' && c <= 'f') return c - 'a' + 10; else return -1; } static char *url_escape(char *src) { int len = strlen(src); unsigned char *decoded; int i; char *dst; int done = 0; decoded = calloc(1, len + 1); dst = decoded; for(i=0; i < len; i++) { switch(src[i]) { case '%': if(i+2 >= len) { free(decoded); return NULL; } if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { free(decoded); return NULL; } *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); i+= 2; break; case '#': done = 1; break; case 0: free(decoded); return NULL; break; default: *dst++ = src[i]; break; } if(done) break; } *dst = 0; /* null terminator */ return decoded; } /** TODO: This is almost certainly buggy in some cases */ static void parse_query(http_parser_t *parser, char *query) { int len; int i=0; char *key = query; char *val=NULL; if(!query || !*query) return; len = strlen(query); while(ireq_type = httpp_req_source; httpp_setvar(parser, HTTPP_VAR_URI, "/"); httpp_setvar(parser, HTTPP_VAR_ICYPASSWORD, line[0]); httpp_setvar(parser, HTTPP_VAR_PROTOCOL, "ICY"); httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "SOURCE"); /* This protocol is evil */ httpp_setvar(parser, HTTPP_VAR_VERSION, "666"); parse_headers(parser, line, lines); free(data); return 1; } int httpp_parse(http_parser_t *parser, char *http_data, unsigned long len) { char *data, *tmp; char *line[MAX_HEADERS]; /* limited to 32 lines, should be more than enough */ int i; int lines; char *req_type = NULL; char *uri = NULL; char *version = NULL; int whitespace, where, slen; if (http_data == NULL) return 0; /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; lines = split_headers(data, len, line); /* parse the first line special ** the format is: ** REQ_TYPE URI VERSION ** eg: ** GET /index.html HTTP/1.0 */ where = 0; whitespace = 0; slen = strlen(line[0]); req_type = line[0]; for (i = 0; i < slen; i++) { if (line[0][i] == ' ') { whitespace = 1; line[0][i] = '\0'; } else { /* we're just past the whitespace boundry */ if (whitespace) { whitespace = 0; where++; switch (where) { case 1: uri = &line[0][i]; break; case 2: version = &line[0][i]; break; } } } } if (strcasecmp("GET", req_type) == 0) { parser->req_type = httpp_req_get; } else if (strcasecmp("POST", req_type) == 0) { parser->req_type = httpp_req_post; } else if (strcasecmp("HEAD", req_type) == 0) { parser->req_type = httpp_req_head; } else if (strcasecmp("SOURCE", req_type) == 0) { parser->req_type = httpp_req_source; } else if (strcasecmp("PLAY", req_type) == 0) { parser->req_type = httpp_req_play; } else if (strcasecmp("STATS", req_type) == 0) { parser->req_type = httpp_req_stats; } else { parser->req_type = httpp_req_unknown; } if (uri != NULL && strlen(uri) > 0) { char *query; if((query = strchr(uri, '?')) != NULL) { *query = 0; query++; parse_query(parser, query); } parser->uri = strdup(uri); } else { free(data); return 0; } if ((version != NULL) && ((tmp = strchr(version, '/')) != NULL)) { tmp[0] = '\0'; if ((strlen(version) > 0) && (strlen(&tmp[1]) > 0)) { httpp_setvar(parser, HTTPP_VAR_PROTOCOL, version); httpp_setvar(parser, HTTPP_VAR_VERSION, &tmp[1]); } else { free(data); return 0; } } else { free(data); return 0; } if (parser->req_type != httpp_req_none && parser->req_type != httpp_req_unknown) { switch (parser->req_type) { case httpp_req_get: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "GET"); break; case httpp_req_post: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "POST"); break; case httpp_req_head: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "HEAD"); break; case httpp_req_source: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "SOURCE"); break; case httpp_req_play: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "PLAY"); break; case httpp_req_stats: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "STATS"); break; default: break; } } else { free(data); return 0; } if (parser->uri != NULL) { httpp_setvar(parser, HTTPP_VAR_URI, parser->uri); } else { free(data); return 0; } parse_headers(parser, line, lines); free(data); return 1; } void httpp_setvar(http_parser_t *parser, char *name, char *value) { http_var_t *var; if (name == NULL || value == NULL) return; var = (http_var_t *)malloc(sizeof(http_var_t)); if (var == NULL) return; var->name = strdup(name); var->value = strdup(value); if (httpp_getvar(parser, name) == NULL) { avl_insert(parser->vars, (void *)var); } else { avl_delete(parser->vars, (void *)var, _free_vars); avl_insert(parser->vars, (void *)var); } } char *httpp_getvar(http_parser_t *parser, char *name) { http_var_t var; http_var_t *found; void *fp; fp = &found; var.name = name; var.value = NULL; if (avl_get_by_key(parser->vars, &var, fp) == 0) return found->value; else return NULL; } void httpp_set_query_param(http_parser_t *parser, char *name, char *value) { http_var_t *var; if (name == NULL || value == NULL) return; var = (http_var_t *)malloc(sizeof(http_var_t)); if (var == NULL) return; var->name = strdup(name); var->value = url_escape(value); if (httpp_get_query_param(parser, name) == NULL) { avl_insert(parser->queryvars, (void *)var); } else { avl_delete(parser->queryvars, (void *)var, _free_vars); avl_insert(parser->queryvars, (void *)var); } } char *httpp_get_query_param(http_parser_t *parser, char *name) { http_var_t var; http_var_t *found; void *fp; fp = &found; var.name = name; var.value = NULL; if (avl_get_by_key(parser->queryvars, (void *)&var, fp) == 0) return found->value; else return NULL; } void httpp_clear(http_parser_t *parser) { parser->req_type = httpp_req_none; if (parser->uri) free(parser->uri); parser->uri = NULL; avl_tree_free(parser->vars, _free_vars); avl_tree_free(parser->queryvars, _free_vars); parser->vars = NULL; } void httpp_destroy(http_parser_t *parser) { httpp_clear(parser); free(parser); } static char *_lowercase(char *str) { char *p = str; for (; *p != '\0'; p++) *p = tolower(*p); return str; } static int _compare_vars(void *compare_arg, void *a, void *b) { http_var_t *vara, *varb; vara = (http_var_t *)a; varb = (http_var_t *)b; return strcmp(vara->name, varb->name); } static int _free_vars(void *key) { http_var_t *var; var = (http_var_t *)key; if (var->name) free(var->name); if (var->value) free(var->value); free(var); return 1; } @ 1.22 log @ermmm, let's use the right operator. @ text @d34 2 a35 2 int _compare_vars(void *compare_arg, void *a, void *b); int _free_vars(void *key); d556 1 a556 1 int _compare_vars(void *compare_arg, void *a, void *b) d566 1 a566 1 int _free_vars(void *key) @ 1.21 log @minor cleanup, removes compiler warning, makes it static, and doesn't re-evaluate string length each time. @ text @d550 1 a550 1 for (; *p |= '\0'; p++) @ 1.20 log @gcc 3.3 warns: dereferencing type-punned pointer will break strict-aliasing rules @ text @d31 1 a31 1 char *_lowercase(char *str); d547 1 a547 1 char *_lowercase(char *str) d549 3 a551 3 long i; for (i = 0; i < strlen(str); i++) str[i] = tolower(str[i]); @ 1.19 log @Karl's patch for freebsd, minus the sys/select.h test which breaks on OS X. Also enables IPV6 in libshout! @ text @d481 1 d483 1 d487 1 a487 1 if (avl_get_by_key(parser->vars, (void *)&var, (void **)&found) == 0) d518 1 d520 1 d524 1 a524 1 if (avl_get_by_key(parser->queryvars, (void *)&var, (void **)&found) == 0) @ 1.18 log @Brendan was getting pissed off about inconsistent indentation styles. Convert all tabs to 4 spaces. All code must now use 4 space indents. @ text @d15 3 @ 1.17 log @reduce include file namespace clutter for libshout and the associated smaller libs. @ text @d36 1 a36 1 return (http_parser_t *)malloc(sizeof(http_parser_t)); d41 1 a41 1 http_varlist_t *list; d43 11 a53 11 parser->req_type = httpp_req_none; parser->uri = NULL; parser->vars = avl_tree_new(_compare_vars, NULL); parser->queryvars = avl_tree_new(_compare_vars, NULL); /* now insert the default variables */ list = defaults; while (list != NULL) { httpp_setvar(parser, list->var.name, list->var.value); list = list->next; } d58 4 a61 4 /* first we count how many lines there are ** and set up the line[] array */ int lines = 0; d63 11 a73 11 line[lines] = data; for (i = 0; i < len && lines < MAX_HEADERS; i++) { if (data[i] == '\r') data[i] = '\0'; if (data[i] == '\n') { lines++; data[i] = '\0'; if (i + 1 < len) { if (data[i + 1] == '\n' || data[i + 1] == '\r') break; line[lines] = &data[i + 1]; d75 2 a76 2 } } d78 2 a79 2 i++; while (data[i] == '\n') i++; d87 35 a121 35 int whitespace, where, slen; char *name = NULL; char *value = NULL; /* parse the name: value lines. */ for (l = 1; l < lines; l++) { where = 0; whitespace = 0; name = line[l]; value = NULL; slen = strlen(line[l]); for (i = 0; i < slen; i++) { if (line[l][i] == ':') { whitespace = 1; line[l][i] = '\0'; } else { if (whitespace) { whitespace = 0; while (i < slen && line[l][i] == ' ') i++; if (i < slen) value = &line[l][i]; break; } } } if (name != NULL && value != NULL) { httpp_setvar(parser, _lowercase(name), value); name = NULL; value = NULL; } } d126 4 a129 4 char *data; char *line[MAX_HEADERS]; int lines, slen,i, whitespace=0, where=0,code; char *version=NULL, *resp_code=NULL, *message=NULL; d131 2 a132 2 if(http_data == NULL) return 0; d134 5 a138 39 /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; lines = split_headers(data, len, line); /* In this case, the first line contains: * VERSION RESPONSE_CODE MESSAGE, such as HTTP/1.0 200 OK */ slen = strlen(line[0]); version = line[0]; for(i=0; i < slen; i++) { if(line[0][i] == ' ') { line[0][i] = 0; whitespace = 1; } else if(whitespace) { whitespace = 0; where++; if(where == 1) resp_code = &line[0][i]; else { message = &line[0][i]; break; } } } if(version == NULL || resp_code == NULL || message == NULL) { free(data); return 0; } httpp_setvar(parser, HTTPP_VAR_ERROR_CODE, resp_code); code = atoi(resp_code); if(code < 200 || code >= 300) { httpp_setvar(parser, HTTPP_VAR_ERROR_MESSAGE, message); } d140 1 a140 2 httpp_setvar(parser, HTTPP_VAR_URI, uri); httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "NONE"); d142 20 a161 1 parse_headers(parser, line, lines); d163 4 a166 1 free(data); d168 14 a181 1 return 1; d186 8 a193 8 if(c >= '0' && c <= '9') return c - '0'; else if(c >= 'A' && c <= 'F') return c - 'A' + 10; else if(c >= 'a' && c <= 'f') return c - 'a' + 10; else return -1; d198 39 a236 39 int len = strlen(src); unsigned char *decoded; int i; char *dst; int done = 0; decoded = calloc(1, len + 1); dst = decoded; for(i=0; i < len; i++) { switch(src[i]) { case '%': if(i+2 >= len) { free(decoded); return NULL; } if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { free(decoded); return NULL; } *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); i+= 2; break; case '#': done = 1; break; case 0: free(decoded); return NULL; break; default: *dst++ = src[i]; break; } if(done) break; } d238 1 a238 1 *dst = 0; /* null terminator */ d240 1 a240 1 return decoded; d246 29 a274 29 int len; int i=0; char *key = query; char *val=NULL; if(!query || !*query) return; len = strlen(query); while(ireq_type = httpp_req_get; } else if (strcasecmp("POST", req_type) == 0) { parser->req_type = httpp_req_post; } else if (strcasecmp("HEAD", req_type) == 0) { parser->req_type = httpp_req_head; } else if (strcasecmp("SOURCE", req_type) == 0) { parser->req_type = httpp_req_source; } else if (strcasecmp("PLAY", req_type) == 0) { parser->req_type = httpp_req_play; } else if (strcasecmp("STATS", req_type) == 0) { parser->req_type = httpp_req_stats; } else { parser->req_type = httpp_req_unknown; } if (uri != NULL && strlen(uri) > 0) { char *query; if((query = strchr(uri, '?')) != NULL) { *query = 0; query++; parse_query(parser, query); } d397 10 a406 2 parser->uri = strdup(uri); } else { d411 27 a437 48 if ((version != NULL) && ((tmp = strchr(version, '/')) != NULL)) { tmp[0] = '\0'; if ((strlen(version) > 0) && (strlen(&tmp[1]) > 0)) { httpp_setvar(parser, HTTPP_VAR_PROTOCOL, version); httpp_setvar(parser, HTTPP_VAR_VERSION, &tmp[1]); } else { free(data); return 0; } } else { free(data); return 0; } if (parser->req_type != httpp_req_none && parser->req_type != httpp_req_unknown) { switch (parser->req_type) { case httpp_req_get: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "GET"); break; case httpp_req_post: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "POST"); break; case httpp_req_head: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "HEAD"); break; case httpp_req_source: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "SOURCE"); break; case httpp_req_play: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "PLAY"); break; case httpp_req_stats: httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "STATS"); break; default: break; } } else { free(data); return 0; } if (parser->uri != NULL) { httpp_setvar(parser, HTTPP_VAR_URI, parser->uri); } else { free(data); return 0; } d439 6 a444 1 parse_headers(parser, line, lines); d446 3 a448 1 free(data); d450 1 a450 1 return 1; d455 1 a455 1 http_var_t *var; d457 2 a458 2 if (name == NULL || value == NULL) return; d460 2 a461 2 var = (http_var_t *)malloc(sizeof(http_var_t)); if (var == NULL) return; d463 9 a471 9 var->name = strdup(name); var->value = strdup(value); if (httpp_getvar(parser, name) == NULL) { avl_insert(parser->vars, (void *)var); } else { avl_delete(parser->vars, (void *)var, _free_vars); avl_insert(parser->vars, (void *)var); } d476 2 a477 2 http_var_t var; http_var_t *found; d479 2 a480 2 var.name = name; var.value = NULL; d482 4 a485 4 if (avl_get_by_key(parser->vars, (void *)&var, (void **)&found) == 0) return found->value; else return NULL; d490 1 a490 1 http_var_t *var; d492 2 a493 2 if (name == NULL || value == NULL) return; d495 2 a496 2 var = (http_var_t *)malloc(sizeof(http_var_t)); if (var == NULL) return; d498 9 a506 9 var->name = strdup(name); var->value = url_escape(value); if (httpp_get_query_param(parser, name) == NULL) { avl_insert(parser->queryvars, (void *)var); } else { avl_delete(parser->queryvars, (void *)var, _free_vars); avl_insert(parser->queryvars, (void *)var); } d511 2 a512 2 http_var_t var; http_var_t *found; d514 2 a515 2 var.name = name; var.value = NULL; d517 4 a520 4 if (avl_get_by_key(parser->queryvars, (void *)&var, (void **)&found) == 0) return found->value; else return NULL; d525 7 a531 7 parser->req_type = httpp_req_none; if (parser->uri) free(parser->uri); parser->uri = NULL; avl_tree_free(parser->vars, _free_vars); avl_tree_free(parser->queryvars, _free_vars); parser->vars = NULL; d536 2 a537 2 httpp_clear(parser); free(parser); d542 3 a544 3 long i; for (i = 0; i < strlen(str); i++) str[i] = tolower(str[i]); d546 1 a546 1 return str; d551 1 a551 1 http_var_t *vara, *varb; d553 2 a554 2 vara = (http_var_t *)a; varb = (http_var_t *)b; d556 1 a556 1 return strcmp(vara->name, varb->name); d561 1 a561 1 http_var_t *var; d563 1 a563 1 var = (http_var_t *)key; d565 5 a569 5 if (var->name) free(var->name); if (var->value) free(var->value); free(var); d571 1 a571 1 return 1; @ 1.16 log @include the automake config.h file if the application defines one @ text @d16 1 a16 1 #include "avl.h" @ 1.15 log @Set another parameter in the icy protocol parse that logging expects @ text @d6 4 @ 1.14 log @Added support for shoutcast login protocol (ewww...) @ text @d299 1 @ 1.13 log @Use gnu archive ACX_PTHREAD macro to figure out how to compile thread support. Also make it possible to build libshout without threads, albeit without locking in the resolver or avl trees. New option --disable-pthread too. @ text @d273 36 d387 4 a390 2 } else parser->uri = NULL; @ 1.12 log @Fix some warnings, fix cflags. @ text @a11 1 #include "thread.h" @ 1.11 log @Indentation again, don't mind me @ text @d58 2 a59 1 int i, lines = 0; @ 1.10 log @Make indentation consistent before doing other work @ text @d472 1 a472 1 var.value = NULL; @ 1.9 log @mp3 metadata complete. Still untested. @ text @d43 1 a43 1 parser->queryvars = avl_tree_new(_compare_vars, NULL); d122 4 a125 4 char *data; char *line[MAX_HEADERS]; int lines, slen,i, whitespace=0, where=0,code; char *version=NULL, *resp_code=NULL, *message=NULL; d127 2 a128 2 if(http_data == NULL) return 0; d134 1 a134 1 data[len] = 0; d136 1 a136 1 lines = split_headers(data, len, line); d138 20 a157 22 /* In this case, the first line contains: * VERSION RESPONSE_CODE MESSAGE, such as * HTTP/1.0 200 OK */ slen = strlen(line[0]); version = line[0]; for(i=0; i < slen; i++) { if(line[0][i] == ' ') { line[0][i] = 0; whitespace = 1; } else if(whitespace) { whitespace = 0; where++; if(where == 1) resp_code = &line[0][i]; else { message = &line[0][i]; break; } } } d159 4 a162 10 if(version == NULL || resp_code == NULL || message == NULL) { free(data); return 0; } httpp_setvar(parser, HTTPP_VAR_ERROR_CODE, resp_code); code = atoi(resp_code); if(code < 200 || code >= 300) { httpp_setvar(parser, HTTPP_VAR_ERROR_MESSAGE, message); } d164 7 a170 1 httpp_setvar(parser, HTTPP_VAR_URI, uri); d173 1 a173 1 parse_headers(parser, line, lines); d182 8 a189 8 if(c >= '0' && c <= '9') return c - '0'; else if(c >= 'A' && c <= 'F') return c - 'A' + 10; else if(c >= 'a' && c <= 'f') return c - 'a' + 10; else return -1; d194 39 a232 39 int len = strlen(src); unsigned char *decoded; int i; char *dst; int done = 0; decoded = calloc(1, len + 1); dst = decoded; for(i=0; i < len; i++) { switch(src[i]) { case '%': if(i+2 >= len) { free(decoded); return NULL; } if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { free(decoded); return NULL; } *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); i+= 2; break; case '#': done = 1; break; case 0: free(decoded); return NULL; break; default: *dst++ = src[i]; break; } if(done) break; } d234 1 a234 1 *dst = 0; /* null terminator */ d236 1 a236 1 return decoded; d242 29 a270 30 int len; int i=0; char *key = query; char *val=NULL; if(!query || !*query) return; len = strlen(query); while(iuri != NULL) { d403 1 a403 1 parse_headers(parser, line, lines); d437 1 a437 1 var.value = NULL; d493 2 a494 2 httpp_clear(parser); free(parser); @ 1.8 log @bugfixes for httpp_parse_response @ text @d43 1 d182 94 d345 9 a353 1 if (uri != NULL && strlen(uri) > 0) d355 1 d442 1 a442 1 var.value = NULL; d450 35 d492 1 @ 1.7 log @Cleaned up version of Ciaran Anscomb's relaying patch. @ text @d165 1 a168 2 free(data); return 0; d172 1 a172 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "RELAY"); @ 1.6 log @Memory leaks. Lots of little ones. @ text @d52 1 a52 1 int httpp_parse(http_parser_t *parser, char *http_data, unsigned long len) a53 21 char *data, *tmp; char *line[MAX_HEADERS]; /* limited to 32 lines, should be more than enough */ int i, l, retlen; int lines; char *req_type = NULL; char *uri = NULL; char *version = NULL; char *name = NULL; char *value = NULL; int whitespace, where; int slen; if (http_data == NULL) return 0; /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; d57 1 a57 1 lines = 0; d75 128 a202 1 retlen = i; d298 1 a298 1 if (parser->uri != NULL) { d305 1 a305 31 /* parse the name: value lines. */ for (l = 1; l < lines; l++) { where = 0; whitespace = 0; name = line[l]; value = NULL; slen = strlen(line[l]); for (i = 0; i < slen; i++) { if (line[l][i] == ':') { whitespace = 1; line[l][i] = '\0'; } else { if (whitespace) { whitespace = 0; while (i < slen && line[l][i] == ' ') i++; if (i < slen) value = &line[l][i]; break; } } } if (name != NULL && value != NULL) { httpp_setvar(parser, _lowercase(name), value); name = NULL; value = NULL; } } d309 1 a309 1 return retlen; @ 1.5 log @Buffer overflows. Requires a change to the format plugin interface - jack: if you want this done differently, feel free to change it (or ask me to). @ text @d271 1 a271 1 void httpp_destroy(http_parser_t *parser) d279 6 @ 1.4 log @Bunch of fixes: - connections are now matched to format plugins based on content-type headers, and are rejected if there isn't a format handler for that content-type, or there is no content-type at all. - format_vorbis now handles pages with granulepos of -1 in the headers correctly (this happens if the headers are fairly large, because of many comments, for example). - various #include fixes. - buffer overflow in httpp.c fixed. @ text @d6 2 d20 2 d55 1 a55 1 char *line[32]; /* limited to 32 lines, should be more than enough */ d80 1 a80 1 for (i = 0; i < len; i++) { @ 1.3 log @Thanks to Akos Maroy for this. These variables need to be uppercase always in order to comply with the HTTP specification. While not a problem internal to icecast, they were slipping into the log files and breaking some less-than-robust parsers. @ text @d65 2 a66 2 /* make a local copy of the data */ data = (char *)malloc(len); d69 1 d81 3 a83 3 if (i + 1 < len) if (data[i + 1] == '\n' || data[i + 1] == '\r') { data[i] = '\0'; a84 3 } data[i] = '\0'; if (i < len - 1) d86 1 @ 1.2 log @Win32 compatibility courtesy of Oddsock. @ text @d150 1 a150 1 httpp_setvar(parser, HTTPP_VAR_PROTOCOL, _lowercase(version)); d164 1 a164 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "get"); d167 1 a167 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "post"); d170 1 a170 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "head"); d173 1 a173 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "source"); d176 1 a176 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "play"); d179 1 a179 1 httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "stats"); @ 1.1 log @Initial revision @ text @d14 4 d54 5 a58 4 char *req_type; char *uri; char *version; char *name, *value; @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/.cvsignore,v0000664000175100017510000000070212203665125026657 0ustar mhaggermhagger00000000000000head 1.2; access; symbols libshout-2_0:1.2 libshout-2_0b3:1.2 libshout-2_0b2:1.2 libshout_2_0b1:1.2 libogg2-zerocopy:1.2.0.2; locks; strict; comment @# @; 1.2 date 2001.09.10.03.04.10; author jack; state Exp; branches; next 1.1; 1.1 date 2001.09.10.03.00.40; author jack; state Exp; branches; next ; desc @@ 1.2 log @.cvsignore is fun! @ text @Makefile Makefile.in .deps .libs *.la *.lo @ 1.1 log @Still more .cvsignore @ text @d4 3 @ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/httpp/test.c,v0000664000175100017510000000572512203665125026014 0ustar mhaggermhagger00000000000000head 1.2; access; symbols libshout-2_0:1.2 libshout-2_0b3:1.2 libshout-2_0b2:1.2 libshout_2_0b1:1.2 libogg2-zerocopy:1.1.1.1.0.2 start:1.1.1.1 xiph:1.1.1; locks; strict; comment @ * @; 1.2 date 2003.03.15.02.10.18; author msmith; state Exp; branches; next 1.1; 1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.09.10.02.28.49; author jack; state Exp; branches; next ; desc @@ 1.2 log @Brendan was getting pissed off about inconsistent indentation styles. Convert all tabs to 4 spaces. All code must now use 4 space indents. @ text @#include #include #include "httpp.h" int main(int argc, char **argv) { char buff[8192]; int readed; http_parser_t parser; avl_node *node; http_var_t *var; httpp_initialize(&parser, NULL); readed = fread(buff, 1, 8192, stdin); if (httpp_parse(&parser, buff, readed)) { printf("Parse succeeded...\n\n"); printf("Request was "); switch (parser.req_type) { case httpp_req_none: printf(" none\n"); break; case httpp_req_unknown: printf(" unknown\n"); break; case httpp_req_get: printf(" get\n"); break; case httpp_req_post: printf(" post\n"); break; case httpp_req_head: printf(" head\n"); break; } printf("Version was 1.%d\n", parser.version); node = avl_get_first(parser.vars); while (node) { var = (http_var_t *)node->key; if (var) printf("Iterating variable(s): %s = %s\n", var->name, var->value); node = avl_get_next(node); } } else { printf("Parse failed...\n"); } printf("Destroying parser...\n"); httpp_destroy(&parser); return 0; } @ 1.1 log @Initial revision @ text @d9 5 a13 5 char buff[8192]; int readed; http_parser_t parser; avl_node *node; http_var_t *var; d15 1 a15 1 httpp_initialize(&parser, NULL); d17 35 a51 35 readed = fread(buff, 1, 8192, stdin); if (httpp_parse(&parser, buff, readed)) { printf("Parse succeeded...\n\n"); printf("Request was "); switch (parser.req_type) { case httpp_req_none: printf(" none\n"); break; case httpp_req_unknown: printf(" unknown\n"); break; case httpp_req_get: printf(" get\n"); break; case httpp_req_post: printf(" post\n"); break; case httpp_req_head: printf(" head\n"); break; } printf("Version was 1.%d\n", parser.version); node = avl_get_first(parser.vars); while (node) { var = (http_var_t *)node->key; if (var) printf("Iterating variable(s): %s = %s\n", var->name, var->value); node = avl_get_next(node); } } else { printf("Parse failed...\n"); } d53 2 a54 2 printf("Destroying parser...\n"); httpp_destroy(&parser); d56 1 a56 1 return 0; @ 1.1.1.1 log @move to cvs @ text @@ cvs2svn-2.5.0/test-data/resync-misgroups-cvsrepos/README0000664000175100017510000000224712203665125024144 0ustar mhaggermhagger00000000000000This data is for testing the resolution of: http://subversion.tigris.org/issues/show_bug.cgi?id=1427 "cvs2svn: fails on GtkRadiant repository" But the data here is not the GtkRadiant data. Instead, it comes from Jack Moffitt at xiph.org, who was able to narrow down the same bug to a much smaller repro set. It might be possible to narrow it down even further, I don't know -- too lazy to try right now. The important thing is that this data won't convert if revision 6567 is subtracted from cvs2svn.py. The error message can look either like this ----- pass 3 ----- ----- pass 4 ----- committing: Sun Sep 9 21:26:32 2001, over 3 seconds No origin records for branch 'xiph'. or like this File "./cvs2svn.py", line 960, in copy_path entries) File "./cvs2svn.py", line 661, in change_path for ent in new_val.keys(): AttributeError: 'None' object has no attribute 'keys' the former if no part of the 'xiph' vendor import branch has been created in the Subversion repository by the time we get to the problem file, the latter if /branches/xiph/ already exists. It could go either way, depending on how Python iterates over dictionary keys. cvs2svn-2.5.0/test-data/symbolic-name-overfill-cvsrepos/0000775000175100017510000000000013206450463024306 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symbolic-name-overfill-cvsrepos/proj/0000775000175100017510000000000013206450463025260 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symbolic-name-overfill-cvsrepos/proj/file.txt,v0000664000175100017510000000064412203665125027205 0ustar mhaggermhagger00000000000000head 1.1; access; symbols branch1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.07.23.19.16.28; author fitz; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2004.07.23.19.17.22; author fitz; state Exp; branches; next ; desc @@ 1.1 log @initial import @ text @This is a file. That's all there is to it.@ 1.1.2.1 log @Commit to file on branch. @ text @d1 1 a1 1 This is a file. It's now on a branch.@ cvs2svn-2.5.0/test-data/symbolic-name-overfill-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463026324 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/symbolic-name-overfill-cvsrepos/proj/Attic/file-added-on-branch.txt,v0000664000175100017510000000062112203665125033150 0ustar mhaggermhagger00000000000000head 1.1; access; symbols branch1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.07.23.19.28.17; author fitz; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2004.07.23.19.28.17; author fitz; state Exp; branches; next ; desc @@ 1.1 log @file branched-file.txt was initially added on branch branch1. @ text @@ 1.1.2.1 log @initial import @ text @a0 2 This file added on a branch. @ cvs2svn-2.5.0/test-data/delete-cvsignore-cvsrepos/0000775000175100017510000000000013206450463023166 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/delete-cvsignore-cvsrepos/proj/0000775000175100017510000000000013206450463024140 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/delete-cvsignore-cvsrepos/proj/file.txt,v0000664000175100017510000000030212203665124026053 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2006.10.15.13.12.43; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Add random file and .cvsignore @ text @@ cvs2svn-2.5.0/test-data/delete-cvsignore-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463025204 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/delete-cvsignore-cvsrepos/proj/Attic/.cvsignore,v0000664000175100017510000000047512203665124027452 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.15.13.13.20; author mhagger; state dead; branches; next 1.1; 1.1 date 2006.10.15.13.12.43; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Remove .cvsignore @ text @*.o @ 1.1 log @Add random file and .cvsignore @ text @@ cvs2svn-2.5.0/test-data/leftover-revs-cvsrepos/0000775000175100017510000000000013206450463022532 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/leftover-revs-cvsrepos/file.txt,v0000664000175100017510000000103112203665125024446 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH:1.2.0.2; locks; strict; comment @// @; 1.2 date 2003.07.04.16.36.26; author author4; state dead; branches 1.2.2.1; next 1.1; 1.1 date 2003.07.04.16.13.47; author author4; state Exp; branches; next ; 1.2.2.1 date 2003.07.13.07.12.30; author author15; state Exp; branches; next 1.2.2.2; 1.2.2.2 date 2004.04.09.01.57.02; author author15; state dead; branches; next ; desc @@ 1.2 log @log 1670@ text @@ 1.2.2.1 log @log 1950@ text @@ 1.2.2.2 log @log 11@ text @@ 1.1 log @log 1671@ text @@ cvs2svn-2.5.0/test-data/empty-trunk-cvsrepos/0000775000175100017510000000000013206450463022226 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-trunk-cvsrepos/root/0000775000175100017510000000000013206450463023211 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-trunk-cvsrepos/root/Attic/0000775000175100017510000000000013206450463024255 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/empty-trunk-cvsrepos/root/Attic/a_file,v0000664000175100017510000000055612203665124025665 0ustar mhaggermhagger00000000000000head 1.1; access; symbols mytag:1.1.2.1 mybranch:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.05.14.14.45; author max; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.05.14.14.45; author max; state Exp; branches; next ; desc @@ 1.1 log @file a_file was initially added on branch mybranch. @ text @@ 1.1.2.1 log @Add a_file @ text @@ cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/0000775000175100017510000000000013206450463024007 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/dir/0000775000175100017510000000000013206450463024565 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/dir/kv.txt,v0000664000175100017510000000040312203665124026203 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.09.13.14.34.25; author ossi; state Exp; branches; next ; commitid e7E4xRVK9dgJfAxs; desc @@ 1.1 log @add @ text @$Author$ $Date$ $RCSfile$ $Source$ $State$ $Revision$ $Id$ $Header$ @ cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/dir/kk.txt,v0000664000175100017510000000036512203665124026177 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; expand @k@; 1.1 date 2007.09.13.14.34.25; author ossi; state Exp; branches; next ; commitid e7E4xRVK9dgJfAxs; desc @@ 1.1 log @add @ text @some text $Id: literal blunder$ more text @ cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/dir/kv-deleted.txt,v0000664000175100017510000000101512203665124027607 0ustar mhaggermhagger00000000000000head 1.2; branch; access; symbols b:1.1.1; locks; strict; comment @# @; 1.2 date 2004.07.28.10.42.27; author kfogel; state dead; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.07.19.20.57.25; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Deleting this dreadful file @ text @This is a fine file. This file resides at $Source$ @ 1.1 log @Add a file. @ text @@ 1.1.1.1 log @Adding a line @ text @a1 1 branches are fun @ cvs2svn-2.5.0/test-data/internal-co-keywords-cvsrepos/dir/ko.txt,v0000664000175100017510000000036512203665124026203 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; expand @o@; 1.1 date 2007.09.13.14.34.25; author ossi; state Exp; branches; next ; commitid e7E4xRVK9dgJfAxs; desc @@ 1.1 log @add @ text @some text $Id: literal blunder$ more text @ cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/0000775000175100017510000000000013206450463023643 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/multi-branch/0000775000175100017510000000000013206450463026230 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/multi-branch/file2,v0000664000175100017510000000076012203665124027417 0ustar mhaggermhagger00000000000000head 1.2; access; symbols branch:1.1.0.2; locks; strict; comment @# @; 1.2 date 2005.12.27.10.46.17; author ossi; state tst; branches; next 1.1; 1.1 date 2005.12.27.10.44.37; author ossi; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2005.12.27.10.46.41; author ossi; state Exp; branches; next ; desc @@ 1.2 log @multi-branch-commit @ text @file2 rev2 head @ 1.1 log @adding @ text @d1 1 a1 1 file2 rev1 @ 1.1.2.1 log @multi-branch-commit @ text @d1 1 a1 1 file2 rev2 branch @ cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/multi-branch/file1,v0000664000175100017510000000076012203665124027416 0ustar mhaggermhagger00000000000000head 1.2; access; symbols branch:1.1.0.2; locks; strict; comment @# @; 1.2 date 2005.12.27.10.45.27; author ossi; state tst; branches; next 1.1; 1.1 date 2005.12.27.10.44.37; author ossi; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2005.12.27.10.45.53; author ossi; state Exp; branches; next ; desc @@ 1.2 log @multi-branch-commit @ text @file1 rev2 head @ 1.1 log @adding @ text @d1 1 a1 1 file1 rev1 @ 1.1.2.1 log @multi-branch-commit @ text @d1 1 a1 1 file1 rev2 branch @ cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/interleaved/0000775000175100017510000000000013206450463026145 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/interleaved/file2,v0000664000175100017510000000046312203665124027334 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2005.11.27.10.43.45; author ossi; state tst; branches; next 1.1; 1.1 date 2005.11.27.10.42.18; author ossi; state Exp; branches; next ; desc @@ 1.2 log @big commit @ text @file2 rev2 @ 1.1 log @adding @ text @d1 1 a1 1 file2 rev1 @ cvs2svn-2.5.0/test-data/commit-dependencies-cvsrepos/interleaved/file1,v0000664000175100017510000000067712203665124027342 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2005.11.27.10.43.34; author ossi; state tst; branches; next 1.2; 1.2 date 2005.11.27.10.43.12; author ossi; state Exp; branches; next 1.1; 1.1 date 2005.11.27.10.42.18; author ossi; state Exp; branches; next ; desc @@ 1.3 log @dependant small commit @ text @file1 rev3 @ 1.2 log @big commit @ text @d1 1 a1 1 file1 rev2 @ 1.1 log @adding @ text @d1 1 a1 1 file1 rev1 @ cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/0000775000175100017510000000000013206450463023163 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/0000775000175100017510000000000013206450463024135 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/dir2/0000775000175100017510000000000013206450463024775 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/dir2/file2.txt,v0000664000175100017510000000027212203665125027001 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH1:1.1.0.2; locks; strict; comment @// @; 1.1 date 2002.01.04.21.27.35; author author1; state Exp; branches; next ; desc @@ 1.1 log @log 2@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/dir2/file3.txt,v0000664000175100017510000000025112203665125026777 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @// @; 1.1 date 2001.02.16.00.44.44; author author1; state Exp; branches; next ; desc @@ 1.1 log @log 1@ text @@ cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/dir1/0000775000175100017510000000000013206450463024774 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/mirror-keyerror2-cvsrepos/proj/dir1/file1.txt,v0000664000175100017510000000052312203665125026776 0ustar mhaggermhagger00000000000000head 1.1; access; symbols TAG1:1.1.2.1 BRANCH1:1.1.2.1.0.2 BRANCH2:1.1.0.2; locks; strict; comment @ * @; 1.1 date 2002.06.24.05.47.35; author author2; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2002.07.03.17.27.18; author author3; state Exp; branches; next ; desc @@ 1.1 log @log 3@ text @@ 1.1.2.1 log @log 4@ text @@ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/0000775000175100017510000000000013206450463021433 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.UPCASE1,v0000664000175100017510000000062412203665124023443 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/auto-props0000664000175100017510000000070512203665124023467 0ustar mhaggermhagger00000000000000# auto-props file for auto_props() test. [auto-props] *.txt = myprop=txt *.xml = myprop=xml;svn:mime-type=text/xml;svn:eol-style=CRLF *.zip = myprop=zip;svn:mime-type=application/zip *.asc = myprop=asc;svn:mime-type=text/plain;!svn:eol-style *.bin = myprop=bin;svn:executable *.csv = myprop=csv;svn:mime-type=text/csv;svn:eol-style=CRLF *.dbf = myprop=dbf;svn:mime-type=application/what-is-dbf *.UPCASE1 = myprop=UPCASE1 *.upcase2 = myprop=UPCASE2 cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.txt,v0000664000175100017510000000062412203665124023221 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.csv,v0000664000175100017510000000064012203665124023173 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @b@; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.xml,v0000664000175100017510000000062412203665124023202 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.UPCASE2,v0000664000175100017510000000062412203665124023444 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.asc,v0000664000175100017510000000062412203665124023150 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.zip,v0000664000175100017510000000062412203665124023204 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/mime.types0000664000175100017510000000020412203665124023442 0ustar mhaggermhagger00000000000000text/xml xml application/zip zip text/csv csv application/what-is-dbf dbf cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.bin,v0000664000175100017510000000060312203665124023147 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @b@; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @#!/bin/sh echo 'Hello world!' echo 'Hello back at you!' @ 1.1 log @Add a file. @ text @d3 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/foo.dbf,v0000664000175100017510000000064012203665124023133 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; expand @b@; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/eol-mime-cvsrepos/README0000664000175100017510000000061012203665124022306 0ustar mhaggermhagger00000000000000This repository is for testing that setting of svn:eol-style works as expected, that the mime mapper also works, and that mime types and eol styles interact with each other correctly. Issue #39 has more about how these interactions work. The 'mime-mappings.txt' file for this repository is stored right here, in the repository itself. It won't be converted because it doesn't end with ,v. cvs2svn-2.5.0/test-data/file-directory-conflict-cvsrepos/0000775000175100017510000000000013206450463024447 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/file-directory-conflict-cvsrepos/proj/0000775000175100017510000000000013206450463025421 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/file-directory-conflict-cvsrepos/proj/name,v0000664000175100017510000000026712203665124026531 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.03.26.13.00.00; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding file "name". @ text @@ cvs2svn-2.5.0/test-data/file-directory-conflict-cvsrepos/proj/name/0000775000175100017510000000000013206450463026341 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/file-directory-conflict-cvsrepos/proj/name/name2,v0000664000175100017510000000027012203665124027525 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.03.26.12.00.00; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding file "name2". @ text @@ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/0000775000175100017510000000000013206450463023136 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/0000775000175100017510000000000013206450463024110 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/e.txt,v0000664000175100017510000000166312203665124025343 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols vtag-3:1.1.1.3; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of e.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of e.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of e.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of e.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/added-then-imported.txt,v0000664000175100017510000000076012203665124030732 0ustar mhaggermhagger00000000000000head 1.1; access; symbols vtag-4:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.15; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Add a file to the working copy. @ text @Adding this file, before importing it with different contents. @ 1.1.1.1 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of added-then-imported.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/d.txt,v0000664000175100017510000000174312203665124025341 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of d.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of d.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of d.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of d.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/a.txt,v0000664000175100017510000000227612203665124025340 0ustar mhaggermhagger00000000000000head 1.2; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.2 date 2004.02.09.15.43.14; author kfogel; state Exp; branches; next 1.1; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.2 log @First regular commit, to a.txt, on vtag-3. @ text @This is vtag-3 (on vbranchA) of a.txt. A regular change to a.txt. @ 1.1 log @Initial revision @ text @d1 2 a2 1 This is vtag-1 (on vbranchA) of a.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of a.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of a.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of a.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/c.txt,v0000664000175100017510000000176312203665124025342 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of c.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of c.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of c.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of c.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/b.txt,v0000664000175100017510000000176312203665124025341 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of b.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of b.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of b.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of b.txt. @ cvs2svn-2.5.0/test-data/default-branches-cvsrepos/proj/deleted-on-vendor-branch.txt,v0000664000175100017510000000212012203665124031652 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access; symbols vtag-4:1.1.1.4 vtag-3:1.1.1.3 vtag-2:1.1.1.2 vtag-1:1.1.1.1 vbranchA:1.1.1; locks; strict; comment @# @; 1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.2; 1.1.1.2 date 2004.02.09.15.43.13; author kfogel; state Exp; branches; next 1.1.1.3; 1.1.1.3 date 2004.02.09.15.43.13; author kfogel; state dead; branches; next 1.1.1.4; 1.1.1.4 date 2004.02.09.15.43.16; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @This is vtag-1 (on vbranchA) of deleted-on-vendor-branch.txt. @ 1.1.1.1 log @Import (vbranchA, vtag-1). @ text @@ 1.1.1.2 log @Import (vbranchA, vtag-2). @ text @d1 1 a1 1 This is vtag-2 (on vbranchA) of deleted-on-vendor-branch.txt. @ 1.1.1.3 log @Import (vbranchA, vtag-3). @ text @d1 1 a1 1 This is vtag-3 (on vbranchA) of deleted-on-vendor-branch.txt. @ 1.1.1.4 log @Import (vbranchA, vtag-4). @ text @d1 1 a1 1 This is vtag-4 (on vbranchA) of deleted-on-vendor-branch.txt. @ cvs2svn-2.5.0/test-data/overdead-cvsrepos/0000775000175100017510000000000013206450463021520 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/overdead-cvsrepos/overdead,v0000664000175100017510000000535712203665125023507 0ustar mhaggermhagger00000000000000head 1.16; access; symbols jujubean-2_1_0:1.16 jujubean-0_3_4:1.1.1.1 jujubean-1_2_4:1.1.1.1 jujubean-1_2_3:1.1.1.1 jujubean-1_2_2:1.1.1.1 jujubean-0_3_3:1.1.1.1 jujubean-1_2_1:1.1.1.1 jujubean-1_2_0:1.1.1.1.0.10 jujubean-1_1_0:1.1.1.1.0.6 jujubean-1_0_2:1.1.1.1 jujubean-1_0_1:1.1.1.1 jujubean-0_3_2:1.1.1.1 jujubean-1_0_0:1.1.1.1.0.4 jujubean-0_3_1:1.1.1.1 jujubean-0_3_0:1.1.1.1.0.2 jujubean-0_0_0:1.1.1.1 jujubean:1.1.1; locks; strict; comment @// @; 1.16 date 2002.05.24.23.49.01; author bloomy; state dead; branches; next 1.15; 1.15 date 2002.05.24.23.48.55; author bloomy; state dead; branches; next 1.14; 1.14 date 2002.05.24.23.48.52; author bloomy; state dead; branches; next 1.13; 1.13 date 2002.05.24.23.48.50; author bloomy; state dead; branches; next 1.12; 1.12 date 2002.05.24.23.48.47; author bloomy; state dead; branches; next 1.11; 1.11 date 2002.05.24.23.48.44; author bloomy; state dead; branches; next 1.10; 1.10 date 2002.05.24.23.48.41; author bloomy; state dead; branches; next 1.9; 1.9 date 2002.05.24.23.48.39; author bloomy; state dead; branches; next 1.8; 1.8 date 2002.05.24.23.48.36; author bloomy; state dead; branches; next 1.7; 1.7 date 2002.05.24.23.48.30; author bloomy; state dead; branches; next 1.6; 1.6 date 2002.05.24.23.48.27; author bloomy; state dead; branches; next 1.5; 1.5 date 2002.05.24.23.48.24; author bloomy; state dead; branches; next 1.4; 1.4 date 2002.05.24.23.48.21; author bloomy; state dead; branches; next 1.3; 1.3 date 2002.05.24.23.48.18; author bloomy; state dead; branches; next 1.2; 1.2 date 2002.05.24.23.48.16; author bloomy; state dead; branches; next 1.1; 1.1 date 2002.04.11.03.20.38; author bloomy; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2002.04.11.03.20.38; author bloomy; state Exp; branches; next ; desc @@ 1.16 log @Flipping the fragbottom busters. @ text @This is a file that's been marked as 'dead' in a crazy number of revisions. @ 1.15 log @Flipping the fragbottom busters. @ text @@ 1.14 log @Flipping the fragbottom busters. @ text @@ 1.13 log @Flipping the fragbottom busters. @ text @@ 1.12 log @Flipping the fragbottom busters. @ text @@ 1.11 log @Flipping the fragbottom busters. @ text @@ 1.10 log @Flipping the fragbottom busters. @ text @@ 1.9 log @Flipping the fragbottom busters. @ text @@ 1.8 log @Flipping the fragbottom busters. @ text @@ 1.7 log @Flipping the fragbottom busters. @ text @@ 1.6 log @Flipping the fragbottom busters. @ text @@ 1.5 log @Flipping the fragbottom busters. @ text @@ 1.4 log @Flipping the fragbottom busters. @ text @@ 1.3 log @Flipping the fragbottom busters. @ text @@ 1.2 log @Flipping the fragbottom busters. @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/double-add-cvsrepos/0000775000175100017510000000000013206450463021727 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-add-cvsrepos/file.txt,v0000775000175100017510000000143612203665124023656 0ustar mhaggermhagger00000000000000head 1.2; access; symbols my-branch:1.2.0.2 boom-branch:1.2.0.4; locks; strict; comment @# @; 1.2 date 2005.02.10.19.45.43; author harry; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2005.01.27.22.11.00; author sally; state dead; branches; next ; 1.2.2.1 date 2005.03.04.21.06.12; author harry; state Exp; branches; next 1.2.2.2; 1.2.2.2 date 2005.04.01.23.09.43; author harry; state Exp; branches; next ; 1.2.4.1 date 2005.03.04.21.02.34; author harry; state Exp; branches; next ; desc @@ 1.2 log @merged some-branch to trunk @ text @@ 1.2.2.1 log @merged trunk to my-branch @ text @@ 1.2.2.2 log @merged trunk to my-branch @ text @@ 1.2.4.1 log @merged trunk to another-branch @ text @@ 1.1 log @file file.txt was initially added on branch BRANCH-sally. @ text @@ cvs2svn-2.5.0/test-data/double-add-cvsrepos/Attic/0000775000175100017510000000000013206450463022773 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-add-cvsrepos/Attic/file2.txt,v0000775000175100017510000000127012203665124025000 0ustar mhaggermhagger00000000000000head 1.2; access; symbols boom-branch:1.2.0.2; locks; strict; comment @# @; 1.2 date 2005.03.04.19.57.41; author harry; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2005.03.04.18.57.55; author sally; state dead; branches; next ; 1.2.2.1 date 2005.03.04.19.57.41; author harry; state dead; branches; next 1.2.2.2; 1.2.2.2 date 2005.03.04.21.02.34; author harry; state Exp; branches; next ; desc @@ 1.2 log @merged some-branch to trunk @ text @@ 1.2.2.1 log @file file2.txt was added on branch boom-branch on 2005-03-04 21:02:34 +0000 @ text @@ 1.2.2.2 log @merged trunk to another-branch @ text @@ 1.1 log @file file2.txt was initially added on branch some-branch. @ text @@ cvs2svn-2.5.0/test-data/double-add-cvsrepos/seemingly-irrelevant-file.txt,v0000775000175100017510000000060212203665124030013 0ustar mhaggermhagger00000000000000head 1.2; access; symbols boom-branch:1.2.0.2; locks; strict; comment @# @; 1.2 date 2005.05.04.15.08.53; author harry; state Exp; branches; next 1.1; 1.1 date 2005.05.02.19.09.31; author sally; state dead; branches; next ; desc @@ 1.2 log @merged some-branch to trunk @ text @@ 1.1 log @file seemingly-irrelevant-file.txt was initially added on branch BRANCH-sally. @ text @@ cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/0000775000175100017510000000000013206450463024733 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/tagged-on-trunk-1.2-a,v0000664000175100017510000000114512203665124030641 0ustar mhaggermhagger00000000000000head 1.2; access; symbols T:1.2 b2:1.1.0.4 b1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2004.03.16.17.41.23; author max; state Exp; branches; next 1.1; 1.1 date 2004.03.11.01.46.11; author max; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2004.03.11.01.49.00; author max; state Exp; branches; next ; 1.1.4.1 date 2004.03.11.01.49.16; author max; state Exp; branches; next ; desc @@ 1.2 log @Commit again on trunk @ text @trunk-again @ 1.1 log @Add on trunk @ text @d1 1 @ 1.1.4.1 log @Commit on branch b2 @ text @a0 1 b2 @ 1.1.2.1 log @Commit on branch b1 @ text @a0 1 b1 @ cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/tagged-on-trunk-1.2-b,v0000664000175100017510000000114512203665124030642 0ustar mhaggermhagger00000000000000head 1.2; access; symbols T:1.2 b2:1.1.0.4 b1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2004.03.16.17.41.23; author max; state Exp; branches; next 1.1; 1.1 date 2004.03.11.01.46.11; author max; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2004.03.11.01.49.00; author max; state Exp; branches; next ; 1.1.4.1 date 2004.03.11.01.49.16; author max; state Exp; branches; next ; desc @@ 1.2 log @Commit again on trunk @ text @trunk-again @ 1.1 log @Add on trunk @ text @d1 1 @ 1.1.4.1 log @Commit on branch b2 @ text @a0 1 b2 @ 1.1.2.1 log @Commit on branch b1 @ text @a0 1 b1 @ cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/tagged-on-b1,v0000664000175100017510000000115112203665124027261 0ustar mhaggermhagger00000000000000head 1.2; access; symbols T:1.1.2.1 b2:1.1.0.4 b1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2004.03.16.17.41.23; author max; state Exp; branches; next 1.1; 1.1 date 2004.03.11.01.46.11; author max; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2004.03.11.01.49.00; author max; state Exp; branches; next ; 1.1.4.1 date 2004.03.11.01.49.16; author max; state Exp; branches; next ; desc @@ 1.2 log @Commit again on trunk @ text @trunk-again @ 1.1 log @Add on trunk @ text @d1 1 @ 1.1.4.1 log @Commit on branch b2 @ text @a0 1 b2 @ 1.1.2.1 log @Commit on branch b1 @ text @a0 1 b1 @ cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/tagged-on-trunk-1.1,v0000664000175100017510000000114512203665124030422 0ustar mhaggermhagger00000000000000head 1.2; access; symbols T:1.1 b2:1.1.0.4 b1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2004.03.16.17.41.23; author max; state Exp; branches; next 1.1; 1.1 date 2004.03.11.01.46.11; author max; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2004.03.11.01.49.00; author max; state Exp; branches; next ; 1.1.4.1 date 2004.03.11.01.49.16; author max; state Exp; branches; next ; desc @@ 1.2 log @Commit again on trunk @ text @trunk-again @ 1.1 log @Add on trunk @ text @d1 1 @ 1.1.4.1 log @Commit on branch b2 @ text @a0 1 b2 @ 1.1.2.1 log @Commit on branch b1 @ text @a0 1 b1 @ cvs2svn-2.5.0/test-data/compose-tag-three-sources-cvsrepos/tagged-on-b2,v0000664000175100017510000000115112203665124027262 0ustar mhaggermhagger00000000000000head 1.2; access; symbols T:1.1.4.1 b2:1.1.0.4 b1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2004.03.16.17.41.24; author max; state Exp; branches; next 1.1; 1.1 date 2004.03.11.01.46.11; author max; state Exp; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2004.03.11.01.49.00; author max; state Exp; branches; next ; 1.1.4.1 date 2004.03.11.01.49.16; author max; state Exp; branches; next ; desc @@ 1.2 log @Commit again on trunk @ text @trunk-again @ 1.1 log @Add on trunk @ text @d1 1 @ 1.1.4.1 log @Commit on branch b2 @ text @a0 1 b2 @ 1.1.2.1 log @Commit on branch b1 @ text @a0 1 b1 @ cvs2svn-2.5.0/test-data/non-ascii-cvsrepos/0000775000175100017510000000000013206450463021607 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/non-ascii-cvsrepos/single-files/0000775000175100017510000000000013206450463024170 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/non-ascii-cvsrepos/single-files/.cvsignore,v0000664000175100017510000000061012203665125026426 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Modify .cvsignore @ text @Spätzle CrèmeBrûlée JamónIbérico AmêijoasÀBulhãoPato @ 1.1 log @Add .cvsignore content with some latin1 characters. @ text @d3 2 @ cvs2svn-2.5.0/test-data/non-ascii-cvsrepos/single-files/twoquick,v0000664000175100017510000000056512203665125026230 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @ * @; 1.2 date 2002.09.29.00.00.01; author jrandom; state Exp; branches; next 1.1; 1.1 date 2002.09.29.00.00.00; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @Modify .cvsignore @ text @hello modified after checked in @ 1.1 log @Add .cvsignore content with some latin1 characters. @ text @d2 2 @ cvs2svn-2.5.0/test-data/move-parent-cvsrepos/0000775000175100017510000000000013206450463022164 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/move-parent-cvsrepos/file2,v0000664000175100017510000000035712203665125023356 0ustar mhaggermhagger00000000000000head 1.1; access; symbols b3:1.1.0.6 b1:1.1.0.4 b2:1.1.0.2; locks; strict; comment @# @; 1.1 date 2011.01.03.14.26.48; author pilegand; state Exp; branches; next ; commitid 657b4d21dca84567; desc @@ 1.1 log @first @ text @file2 @ cvs2svn-2.5.0/test-data/move-parent-cvsrepos/file1,v0000664000175100017510000000062712203665125023355 0ustar mhaggermhagger00000000000000head 1.1; access; symbols b3:1.1.2.1.0.4 b1:1.1.2.1.0.2 b2:1.1.0.2; locks; strict; comment @# @; 1.1 date 2011.01.03.14.26.48; author pilegand; state Exp; branches 1.1.2.1; next ; commitid 657b4d21dca84567; 1.1.2.1 date 2011.01.03.14.26.51; author pilegand; state Exp; branches; next ; commitid 657f4d21dcab4567; desc @@ 1.1 log @first @ text @file1 @ 1.1.2.1 log @second @ text @a1 1 On b2 @ cvs2svn-2.5.0/test-data/double-delete-cvsrepos/0000775000175100017510000000000013206450463022441 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-delete-cvsrepos/twice-removed,v0000664000175100017510000000144712203665124025404 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @; @; 1.3 date 95.12.30.18.37.22; author jrandom; state dead; branches; next 1.2; 1.2 date 95.12.11.00.27.53; author jrandom; state dead; branches; next 1.1; 1.1 date 93.06.18.05.46.07; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 93.06.18.05.46.08; author jrandom; state Exp; branches; next ; desc @@ 1.3 log @Remove this file for the second time, which should have no effect. @ text @The original text of this file was much longer, but we didn't need it for the regression test, so we removed it. It was src/gnu/usr.bin/cvs/contrib/pcl-cvs/Attic/cookie.el,v in the FreeBSD CVS repository. @ 1.2 log @Remove this file for the first time. @ text @@ 1.1 log @Initial revision @ text @@ 1.1.1.1 log @Updated CVS @ text @@ cvs2svn-2.5.0/test-data/multiply-defined-symbols-cvsrepos/0000775000175100017510000000000013206450463024670 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/multiply-defined-symbols-cvsrepos/cvs2svn-ignore.options0000664000175100017510000000151012203665125031166 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # Fix a problem with multiply-defined symbols by ignoring one copy of # each symbol. from cvs2svn_lib.symbol_transform import SymbolMapper execfile('cvs2svn-example.options') name = 'multiply-defined-symbols' ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/%s--options=cvs2svn-ignore.options-svnrepos' % (name,), ) run_options.clear_projects() filename = 'test-data/%s-cvsrepos/proj/default,v' % (name,) symbol_mapper = SymbolMapper([ (filename, 'BRANCH', '1.2.4', None), (filename, 'TAG', '1.2', None), ]) run_options.add_project( r'test-data/%s-cvsrepos' % (name,), trunk_path='trunk', branches_path='branches', tags_path='tags', symbol_transforms=[ symbol_mapper, ], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/multiply-defined-symbols-cvsrepos/cvs2svn-rename.options0000664000175100017510000000151712203665125031161 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # Fix a problem with multiply-defined symbols by renaming one copy of # each symbol. from cvs2svn_lib.symbol_transform import SymbolMapper execfile('cvs2svn-example.options') name = 'multiply-defined-symbols' ctx.output_option = NewRepositoryOutputOption( 'cvs2svn-tmp/%s--options=cvs2svn-rename.options-svnrepos' % (name,), ) run_options.clear_projects() filename = 'test-data/%s-cvsrepos/proj/default,v' % (name,) symbol_mapper = SymbolMapper([ (filename, 'BRANCH', '1.2.4', 'BRANCH2'), (filename, 'TAG', '1.2', 'TAG2'), ]) run_options.add_project( r'test-data/%s-cvsrepos' % (name,), trunk_path='trunk', branches_path='branches', tags_path='tags', symbol_transforms=[ symbol_mapper, ], symbol_strategy_rules=global_symbol_strategy_rules, ) cvs2svn-2.5.0/test-data/multiply-defined-symbols-cvsrepos/proj/0000775000175100017510000000000013206450463025642 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/multiply-defined-symbols-cvsrepos/proj/default,v0000664000175100017510000000105312203665125027451 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH:1.2.0.4 BRANCH:1.2.0.2 TAG:1.2 TAG:1.1; locks; strict; comment @# @; 1.2 date 2003.05.23.00.17.53; author jrandom; state Exp; branches 1.2.2.1 1.2.4.1; next 1.1; 1.1 date 2003.05.22.23.20.19; author jrandom; state Exp; branches; next ; 1.2.2.1 date 2003.05.23.00.31.36; author jrandom; state Exp; branches; next ; 1.2.4.1 date 2003.06.03.03.20.31; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @@ text @@ 1.2.4.1 log @@ text @@ 1.2.2.1 log @@ text @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/0000775000175100017510000000000013206450463024271 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/makerepo.sh0000775000175100017510000000471712203665125026443 0ustar mhaggermhagger00000000000000#! /bin/sh # This is the script used to create the preferred-parent-cycle CVS # repository. (The repository is checked into svn; this script is # only here for its documentation value.) # # The script should be started from the main cvs2svn directory. # # The branching structure of the three files in this repository is # constructed to create a loop in the preferred parent of each branch # A, B, and C. The branches are as follows ('*' marks revisions, # which are used to prevent trunk from being a possible parent of # branches A, B, or C): # # file1: # --*--+-------------------- trunk # | # +--*--+-------------- branch X # | # +-------------- branch A # | # +-------------- branch B # | # +-------------- branch C # # file2: # --*--+-------------------- trunk # | # +--*--+-------------- branch Y # | # +-------------- branch B # | # +-------------- branch C # | # +-------------- branch A # # file3: # --*--+-------------------- trunk # | # +--*--+-------------- branch Z # | # +-------------- branch C # | # +-------------- branch A # | # +-------------- branch B # # Note that the possible parents of A are (X, Y, Z, C*2, B*1), those # of B are (X, Y, Z, A*2, C*1), and those of C are (X, Y, Z, B*2, # A*1). Therefore the preferred parents form a cycle A -> C -> B -> # A. repo=`pwd`/test-data/preferred-parent-cycle-cvsrepos wc=`pwd`/cvs2svn-tmp/preferred-parent-cycle-wc [ -e $repo/CVSROOT ] && rm -rf $repo/CVSROOT [ -e $repo/dir ] && rm -rf $repo/dir [ -e $wc ] && rm -rf $wc cvs -d $repo init cvs -d $repo co -d $wc . cd $wc mkdir dir cvs add dir cd dir echo '1.1' >file1 echo '1.1' >file2 echo '1.1' >file3 cvs add file1 file2 file3 cvs commit -m 'Adding files on trunk' . cvs tag -b X file1 cvs up -r X file1 cvs tag -b Y file2 cvs up -r Y file2 cvs tag -b Z file3 cvs up -r Z file3 echo '1.1.2.1' >file1 echo '1.1.2.1' >file2 echo '1.1.2.1' >file3 cvs commit -m 'Adding revision on first-level branches' . cvs tag -b A file1 cvs up -r A file1 cvs tag -b B file1 cvs up -r B file1 cvs tag -b C file1 cvs up -r C file1 cvs tag -b B file2 cvs up -r B file2 cvs tag -b C file2 cvs up -r C file2 cvs tag -b A file2 cvs up -r A file2 cvs tag -b C file3 cvs up -r C file3 cvs tag -b A file3 cvs up -r A file3 cvs tag -b B file3 cvs up -r B file3 cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/dir/0000775000175100017510000000000013206450463025047 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/dir/file2,v0000664000175100017510000000064112203665125026235 0ustar mhaggermhagger00000000000000head 1.1; access; symbols A:1.1.2.1.0.6 C:1.1.2.1.0.4 B:1.1.2.1.0.2 Y:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.04.22.16.35.58; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.04.22.16.35.59; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @1.1 @ 1.1.2.1 log @Adding revision on first-level branches @ text @d1 1 a1 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/dir/file1,v0000664000175100017510000000064112203665125026234 0ustar mhaggermhagger00000000000000head 1.1; access; symbols C:1.1.2.1.0.6 B:1.1.2.1.0.4 A:1.1.2.1.0.2 X:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.04.22.16.35.58; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.04.22.16.35.59; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @1.1 @ 1.1.2.1 log @Adding revision on first-level branches @ text @d1 1 a1 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/preferred-parent-cycle-cvsrepos/dir/file3,v0000664000175100017510000000064112203665125026236 0ustar mhaggermhagger00000000000000head 1.1; access; symbols B:1.1.2.1.0.6 A:1.1.2.1.0.4 C:1.1.2.1.0.2 Z:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.04.22.16.35.58; author mhagger; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2007.04.22.16.35.59; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding files on trunk @ text @1.1 @ 1.1.2.1 log @Adding revision on first-level branches @ text @d1 1 a1 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/trunk-readd-cvsrepos/0000775000175100017510000000000013206450463022147 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/trunk-readd-cvsrepos/root/0000775000175100017510000000000013206450463023132 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/trunk-readd-cvsrepos/root/b_file,v0000664000175100017510000000077312203665125024545 0ustar mhaggermhagger00000000000000head 1.2; access; symbols mytag:1.1.2.1 mybranch:1.1.0.2; locks; strict; comment @# @; 1.2 date 2007.06.17.18.48.11; author mhagger; state Exp; branches; next 1.1; 1.1 date 2004.06.05.14.14.45; author max; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.05.14.14.45; author max; state Exp; branches; next ; desc @@ 1.2 log @Re-adding b_file on trunk @ text @b_file 1.2 @ 1.1 log @file b_file was initially added on branch mybranch. @ text @d1 1 @ 1.1.2.1 log @Add b_file @ text @@ cvs2svn-2.5.0/test-data/double-branch-delete-cvsrepos/0000775000175100017510000000000013206450463023674 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-branch-delete-cvsrepos/SoftSet.java,v0000664000175100017510000000027412203665124026371 0ustar mhaggermhagger00000000000000head 1.1; access; symbols Branch_4_0:1.1.0.2; locks; strict; comment @# @; 1.1 date 2006.05.11.02.23.22; author starksm; state Exp; branches; next ; desc @@ 1.1 log @log1 @ text @@ cvs2svn-2.5.0/test-data/double-branch-delete-cvsrepos/Streams.java,v0000664000175100017510000000076112203665124026421 0ustar mhaggermhagger00000000000000head 1.4; access; symbols Branch_4_0:1.4.0.18; locks; strict; comment @# @; 1.4 date 2002.05.31.03.09.03; author user57; state Exp; branches 1.4.18.1; next ; 1.4.18.1 date 2005.10.29.05.07.47; author starksm; state Exp; branches; next ; desc @@ 1.4 log @ o turned commented System.out's into gaurded log.trace's o flush output buffer on copyb, as if it was not a buffer already the last bit might be (and usually is) lost. @ text @@ 1.4.18.1 log @Update the LGPL header @ text @@ cvs2svn-2.5.0/test-data/double-branch-delete-cvsrepos/IMarshalledValue.java,v0000664000175100017510000000145012203665124030161 0ustar mhaggermhagger00000000000000head 1.1; access; symbols Branch_4_0:1.1.0.2; locks; strict; comment @# @; 1.1 date 2005.12.15.20.18.27; author csuconic; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2005.12.15.20.18.27; author csuconic; state dead; branches; next 1.1.2.3; 1.1.2.3 date 2005.12.15.20.33.41; author csuconic; state Exp; branches; next 1.1.2.4; 1.1.2.4 date 2006.03.29.04.16.05; author csuconic; state dead; branches; next ; desc @@ 1.1 log @JBAS-2436 - Adding IMarshalledValue to common - required piece for integrating Pluggable Serialization @ text @@ 1.1.2.1 log @file IMarshalledValue.java was added on branch Branch_4_0 on 2005-12-15 20:18:49 +0000 @ text @@ 1.1.2.3 log @JBAS-2436 - Adding LGPL Header2 @ text @@ 1.1.2.4 log @JBAS-3025 - Removing dependency on commons/IMarshalledValue @ text @@ cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/0000775000175100017510000000000013206450463024273 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/0000775000175100017510000000000013206450463025245 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/e.txt,v0000664000175100017510000000075712203665125026504 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vtag-2:1.1.1.1 vbranchB:1.1.1; locks ; strict; comment @# @; 1.1 date 2005.02.12.22.01.44; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2005.02.12.22.01.44; author kfogel; state Exp; branches ; next ; desc @@ 1.1 log @This log message is not the standard 'Initial revision\n' that indicates an import. @ text @Import vtag-2 on vbranchB. @ 1.1.1.1 log @First vendor branch revision. @ text @@ cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/d.txt,v0000664000175100017510000000036612203665125026477 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches; next ; desc @@ 1.1 log @Initial revision @ text @Added d.txt via 'cvs add', but with same timestamp as the imports. @ cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/a.txt,v0000664000175100017510000000065412203665125026474 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vtag-1:1.1.1.1 vbranchA:1.1.1; locks ; strict; comment @# @; 1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @Import vtag-1 on vbranchA. @ 1.1.1.1 log @First vendor branch revision. @ text @@ cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/c.txt,v0000664000175100017510000000065512203665125026477 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vtag-1:1.1.1.1 vbranchA:1.1.1; locks ; strict; comment @# @; 1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.12.22.01.44; author kfogel; state dead; branches ; next ; desc @@ 1.1 log @Initial revision @ text @Import vtag-1 on vbranchA. @ 1.1.1.1 log @First vendor branch revision. @ text @@ cvs2svn-2.5.0/test-data/vendor-branch-sameness-cvsrepos/proj/b.txt,v0000664000175100017510000000073512203665125026475 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vtag-1:1.1.1.1 vbranchA:1.1.1; locks ; strict; comment @# @; 1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2004.02.12.22.01.44; author kfogel; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @Import vtag-1 on vbranchA. @ 1.1.1.1 log @First vendor branch revision. @ text @a1 1 The text on the vendor branch is different. @ cvs2svn-2.5.0/test-data/timestamp-chaos-cvsrepos/0000775000175100017510000000000013206450463023025 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/timestamp-chaos-cvsrepos/proj/0000775000175100017510000000000013206450463023777 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/timestamp-chaos-cvsrepos/proj/file2.txt,v0000664000175100017510000000071412203665125026004 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2007.01.01.22.00.00; author mhagger; state Exp; branches; next 1.2; 1.2 date 2030.01.01.00.00.00; author mhagger; state Exp; branches; next 1.1; 1.1 date 2007.01.01.21.00.00; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @Revision 1.3 @ text @Revision 1.3 @ 1.2 log @Revision 1.2 @ text @d1 1 a1 1 Revision 1.2 @ 1.1 log @Revision 1.1 @ text @d1 1 a1 1 Revision 1.1 @ cvs2svn-2.5.0/test-data/timestamp-chaos-cvsrepos/proj/file1.txt,v0000664000175100017510000000071412203665125026003 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2007.01.01.22.00.00; author mhagger; state Exp; branches; next 1.2; 1.2 date 2000.01.01.00.00.00; author mhagger; state Exp; branches; next 1.1; 1.1 date 2007.01.01.21.00.00; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @Revision 1.3 @ text @Revision 1.3 @ 1.2 log @Revision 1.2 @ text @d1 1 a1 1 Revision 1.2 @ 1.1 log @Revision 1.1 @ text @d1 1 a1 1 Revision 1.1 @ cvs2svn-2.5.0/test-data/native-eol-cvsrepos/0000775000175100017510000000000013206450463021772 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/native-eol-cvsrepos/foo.txt,v0000664000175100017510000000111212203665125023552 0ustar mhaggermhagger00000000000000head 1.4; access; symbols; locks; strict; comment @# @; 1.4 date 2005.01.04.19.59.01; author tori; state Exp; branches; next 1.3; 1.3 date 2005.01.04.19.58.15; author tori; state Exp; branches; next 1.2; 1.2 date 2005.01.04.19.57.04; author tori; state Exp; branches; next 1.1; 1.1 date 2005.01.04.19.55.50; author tori; state Exp; branches; next ; desc @@ 1.4 log @Mixed EOLs @ text @And then mixed EOLs @ 1.3 log @CR EOLs @ text @d1 2 a2 1 And then CR EOLs @ 1.2 log @CRLF EOLs @ text @d1 1 a1 3 Then CRLF EOLs @ 1.1 log @LF EOLs @ text @d1 3 a3 3 First LF EOLs @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/0000775000175100017510000000000013206450463022347 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABCD-passthru-loop/0000775000175100017510000000000013206450463025656 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABCD-passthru-loop/d.txt,v0000664000175100017510000000071112203665125027102 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.39; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.38; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.37; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABCD-passthru-loop-B @ text @1.3 @ 1.2 log @ABCD-passthru-loop-A @ text @d1 1 a1 1 1.2 @ 1.1 log @ABCD-passthru-loop-D @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABCD-passthru-loop/a.txt,v0000664000175100017510000000071112203665125027077 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.30; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.29; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.28; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABCD-passthru-loop-C @ text @1.3 @ 1.2 log @ABCD-passthru-loop-B @ text @d1 1 a1 1 1.2 @ 1.1 log @ABCD-passthru-loop-A @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABCD-passthru-loop/c.txt,v0000664000175100017510000000071112203665125027101 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.36; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.35; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.34; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABCD-passthru-loop-A @ text @1.3 @ 1.2 log @ABCD-passthru-loop-D @ text @d1 1 a1 1 1.2 @ 1.1 log @ABCD-passthru-loop-C @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABCD-passthru-loop/b.txt,v0000664000175100017510000000071112203665125027100 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.33; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.32; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.31; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABCD-passthru-loop-D @ text @1.3 @ 1.2 log @ABCD-passthru-loop-C @ text @d1 1 a1 1 1.2 @ 1.1 log @ABCD-passthru-loop-B @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-loop/0000775000175100017510000000000013206450463023600 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-loop/a.txt,v0000664000175100017510000000045512203665125025026 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.30.22.11.10; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.09; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @AB-loop-B @ text @1.2 @ 1.1 log @AB-loop-A @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-loop/b.txt,v0000664000175100017510000000045512203665125025027 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.30.22.11.12; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.11; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @AB-loop-A @ text @1.2 @ 1.1 log @AB-loop-B @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-loop/0000775000175100017510000000000013206450463023703 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-loop/a.txt,v0000664000175100017510000000045712203665125025133 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.30.22.11.14; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.13; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @ABC-loop-B @ text @1.2 @ 1.1 log @ABC-loop-A @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-loop/c.txt,v0000664000175100017510000000045712203665125025135 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.30.22.11.18; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.17; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @ABC-loop-A @ text @1.2 @ 1.1 log @ABC-loop-C @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-loop/b.txt,v0000664000175100017510000000045712203665125025134 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2006.10.30.22.11.16; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.15; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @ABC-loop-C @ text @1.2 @ 1.1 log @ABC-loop-B @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-double-passthru-loop/0000775000175100017510000000000013206450463026717 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-double-passthru-loop/a.txt,v0000664000175100017510000000114312203665125030140 0ustar mhaggermhagger00000000000000head 1.4; access; symbols; locks; strict; comment @# @; 1.4 date 2006.10.30.22.11.43; author mhagger; state Exp; branches; next 1.3; 1.3 date 2006.10.30.22.11.42; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.41; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.40; author mhagger; state Exp; branches; next ; desc @@ 1.4 log @AB-double-passthru-loop-D @ text @1.4 @ 1.3 log @AB-double-passthru-loop-C @ text @d1 1 a1 1 1.3 @ 1.2 log @AB-double-passthru-loop-B @ text @d1 1 a1 1 1.2 @ 1.1 log @AB-double-passthru-loop-A @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/AB-double-passthru-loop/b.txt,v0000664000175100017510000000114312203665125030141 0ustar mhaggermhagger00000000000000head 1.4; access; symbols; locks; strict; comment @# @; 1.4 date 2006.10.30.22.11.47; author mhagger; state Exp; branches; next 1.3; 1.3 date 2006.10.30.22.11.46; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.45; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.44; author mhagger; state Exp; branches; next ; desc @@ 1.4 log @AB-double-passthru-loop-B @ text @1.4 @ 1.3 log @AB-double-passthru-loop-A @ text @d1 1 a1 1 1.3 @ 1.2 log @AB-double-passthru-loop-D @ text @d1 1 a1 1 1.2 @ 1.1 log @AB-double-passthru-loop-C @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-passthru-loop/0000775000175100017510000000000013206450463025552 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-passthru-loop/a.txt,v0000664000175100017510000000070612203665125026777 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.21; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.20; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.19; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABC-passthru-loop-C @ text @1.3 @ 1.2 log @ABC-passthru-loop-B @ text @d1 1 a1 1 1.2 @ 1.1 log @ABC-passthru-loop-A @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-passthru-loop/c.txt,v0000664000175100017510000000070612203665125027001 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.27; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.26; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.25; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABC-passthru-loop-B @ text @1.3 @ 1.2 log @ABC-passthru-loop-A @ text @d1 1 a1 1 1.2 @ 1.1 log @ABC-passthru-loop-C @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/ABC-passthru-loop/b.txt,v0000664000175100017510000000070612203665125027000 0ustar mhaggermhagger00000000000000head 1.3; access; symbols; locks; strict; comment @# @; 1.3 date 2006.10.30.22.11.24; author mhagger; state Exp; branches; next 1.2; 1.2 date 2006.10.30.22.11.23; author mhagger; state Exp; branches; next 1.1; 1.1 date 2006.10.30.22.11.22; author mhagger; state Exp; branches; next ; desc @@ 1.3 log @ABC-passthru-loop-A @ text @1.3 @ 1.2 log @ABC-passthru-loop-C @ text @d1 1 a1 1 1.2 @ 1.1 log @ABC-passthru-loop-B @ text @d1 1 a1 1 1.1 @ cvs2svn-2.5.0/test-data/nasty-graphs-cvsrepos/make-nasty-graphs.sh0000775000175100017510000000734712203665125026253 0ustar mhaggermhagger00000000000000#! /bin/sh # This script can be moved to the test-data directory and executed # there to recreate nasty-graphs-cvsrepos. (Well, approximately. It # doesn't clean up CVSROOT or add CVSROOT/README.) CVSROOT=`pwd`/nasty-graphs-cvsrepos export CVSROOT rm -rf $CVSROOT WC=`pwd`/cvs2svn-tmp rm -rf $WC cvs init cvs co -d $WC . # +-> A -> B --+ # | | # +------------+ # # A: a.txt<1.1> b.txt<1.2> # B: a.txt<1.2> b.txt<1.1> TEST=AB-loop D=$WC/$TEST mkdir $D cvs add $D echo "1.1" >$D/a.txt cvs add $D/a.txt cvs commit -m "$TEST-A" $D/a.txt echo "1.2" >$D/a.txt cvs commit -m "$TEST-B" $D/a.txt echo "1.1" >$D/b.txt cvs add $D/b.txt cvs commit -m "$TEST-B" $D/b.txt echo "1.2" >$D/b.txt cvs commit -m "$TEST-A" $D/b.txt # +-> A -> B -> C --+ # | | # +-----------------+ # # A: a.txt<1.1> c.txt<1.2> # B: a.txt<1.2> b.txt<1.1> # C: b.txt<1.2> c.txt<1.1> TEST=ABC-loop D=$WC/$TEST mkdir $D cvs add $D echo "1.1" >$D/a.txt cvs add $D/a.txt cvs commit -m "$TEST-A" $D/a.txt echo "1.2" >$D/a.txt cvs commit -m "$TEST-B" $D/a.txt echo "1.1" >$D/b.txt cvs add $D/b.txt cvs commit -m "$TEST-B" $D/b.txt echo "1.2" >$D/b.txt cvs commit -m "$TEST-C" $D/b.txt echo "1.1" >$D/c.txt cvs add $D/c.txt cvs commit -m "$TEST-C" $D/c.txt echo "1.2" >$D/c.txt cvs commit -m "$TEST-A" $D/c.txt # A: a.txt<1.1> b.txt<1.3> c.txt<1.2> # B: a.txt<1.2> b.txt<1.1> c.txt<1.3> # C: a.txt<1.3> b.txt<1.2> c.txt<1.1> TEST=ABC-passthru-loop D=$WC/$TEST mkdir $D cvs add $D echo "1.1" >$D/a.txt cvs add $D/a.txt cvs commit -m "$TEST-A" $D/a.txt echo "1.2" >$D/a.txt cvs commit -m "$TEST-B" $D/a.txt echo "1.3" >$D/a.txt cvs commit -m "$TEST-C" $D/a.txt echo "1.1" >$D/b.txt cvs add $D/b.txt cvs commit -m "$TEST-B" $D/b.txt echo "1.2" >$D/b.txt cvs commit -m "$TEST-C" $D/b.txt echo "1.3" >$D/b.txt cvs commit -m "$TEST-A" $D/b.txt echo "1.1" >$D/c.txt cvs add $D/c.txt cvs commit -m "$TEST-C" $D/c.txt echo "1.2" >$D/c.txt cvs commit -m "$TEST-A" $D/c.txt echo "1.3" >$D/c.txt cvs commit -m "$TEST-B" $D/c.txt # A: a.txt<1.1> c.txt<1.3> d.txt<1.2> # B: a.txt<1.2> b.txt<1.1> d.txt<1.3> # C: a.txt<1.3> b.txt<1.2> c.txt<1.1> # D: b.txt<1.3> c.txt<1.2> d.txt<1.1> TEST=ABCD-passthru-loop D=$WC/$TEST mkdir $D cvs add $D echo "1.1" >$D/a.txt cvs add $D/a.txt cvs commit -m "$TEST-A" $D/a.txt echo "1.2" >$D/a.txt cvs commit -m "$TEST-B" $D/a.txt echo "1.3" >$D/a.txt cvs commit -m "$TEST-C" $D/a.txt echo "1.1" >$D/b.txt cvs add $D/b.txt cvs commit -m "$TEST-B" $D/b.txt echo "1.2" >$D/b.txt cvs commit -m "$TEST-C" $D/b.txt echo "1.3" >$D/b.txt cvs commit -m "$TEST-D" $D/b.txt echo "1.1" >$D/c.txt cvs add $D/c.txt cvs commit -m "$TEST-C" $D/c.txt echo "1.2" >$D/c.txt cvs commit -m "$TEST-D" $D/c.txt echo "1.3" >$D/c.txt cvs commit -m "$TEST-A" $D/c.txt echo "1.1" >$D/d.txt cvs add $D/d.txt cvs commit -m "$TEST-D" $D/d.txt echo "1.2" >$D/d.txt cvs commit -m "$TEST-A" $D/d.txt echo "1.3" >$D/d.txt cvs commit -m "$TEST-B" $D/d.txt # The following test has the nasty property that each changeset has # either one LINK_PREV or LINK_SUCC and also one LINK_PASSTHRU. # # A: a.txt<1.1> b.txt<1.3> # B: a.txt<1.2> b.txt<1.4> # C: a.txt<1.3> b.txt<1.1> # D: a.txt<1.4> b.txt<1.2> TEST=AB-double-passthru-loop D=$WC/$TEST mkdir $D cvs add $D echo "1.1" >$D/a.txt cvs add $D/a.txt cvs commit -m "$TEST-A" $D/a.txt echo "1.2" >$D/a.txt cvs commit -m "$TEST-B" $D/a.txt echo "1.3" >$D/a.txt cvs commit -m "$TEST-C" $D/a.txt echo "1.4" >$D/a.txt cvs commit -m "$TEST-D" $D/a.txt echo "1.1" >$D/b.txt cvs add $D/b.txt cvs commit -m "$TEST-C" $D/b.txt echo "1.2" >$D/b.txt cvs commit -m "$TEST-D" $D/b.txt echo "1.3" >$D/b.txt cvs commit -m "$TEST-A" $D/b.txt echo "1.4" >$D/b.txt cvs commit -m "$TEST-B" $D/b.txt cvs2svn-2.5.0/test-data/double-fill-cvsrepos/0000775000175100017510000000000013206450463022125 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-fill-cvsrepos/file.txt,v0000775000175100017510000000075412203665124024056 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH-boom:1.2.0.2; locks; strict; comment @ * @; 1.2 date 2005.04.22.14.13.03; author harry; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2005.04.21.19.38.46; author sally; state dead; branches; next ; 1.2.2.1 date 2005.04.22.14.13.03; author harry; state dead; branches; next ; desc @@ 1.2 log @log message @ text @@ 1.2.2.1 log @file file.txt was added on branch BRANCH-boom on 2005-04-22 17:58:42 +0000 @ text @@ 1.1 log @log message @ text @@ cvs2svn-2.5.0/test-data/double-fill-cvsrepos/Attic/0000775000175100017510000000000013206450463023171 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/double-fill-cvsrepos/Attic/oldfile.txt,v0000775000175100017510000000060312203665124025612 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH-boom:1.1.0.2; locks; strict; comment @# @; 1.1 date 2005.04.04.16.13.48; author sally; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2005.04.04.16.13.48; author boom; state dead; branches; next ; desc @@ 1.1 log @log message @ text @@ 1.1.2.1 log @file oldfile.txt was added on branch BRANCH-boom on 2005-04-06 20:15:29 +0000 @ text @@ cvs2svn-2.5.0/test-data/double-fill-cvsrepos/otherfile.txt,v0000775000175100017510000000116512203665124025115 0ustar mhaggermhagger00000000000000head 1.2; access; symbols BRANCH-boom:1.2.0.2; locks; strict; comment @# @; 1.2 date 2005.03.29.22.31.23; author harry; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2005.03.29.13.21.35; author sally; state dead; branches; next ; 1.2.2.1 date 2005.03.29.22.31.23; author harry; state dead; branches; next 1.2.2.2; 1.2.2.2 date 2005.03.29.23.36.06; author harry; state Exp; branches; next ; desc @@ 1.2 log @log message @ text @@ 1.2.2.1 log @file otherfile.txt was added on branch BRANCH-boom on 2005-03-29 23:36:06 +0000 @ text @@ 1.2.2.2 log @merged trunk to boom @ text @@ 1.1 log @log message @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/0000775000175100017510000000000013206450463023274 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/one.txt,v0000664000175100017510000000161312203665125025060 0ustar mhaggermhagger00000000000000head 1.3; access; symbols BRANCH:1.2.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.2.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.2.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.2.2.1). @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/foo/0000775000175100017510000000000013206450463024057 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/foo/three.txt,v0000664000175100017510000000161312203665125026171 0ustar mhaggermhagger00000000000000head 1.3; access; symbols BRANCH:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/foo/four.txt,v0000664000175100017510000000161312203665125026035 0ustar mhaggermhagger00000000000000head 1.3; access; symbols BRANCH:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/two.txt,v0000664000175100017510000000161312203665125025110 0ustar mhaggermhagger00000000000000head 1.3; access; symbols BRANCH:1.2.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches 1.2.2.1; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.2.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.2.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.2.2.1). @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/bar/0000775000175100017510000000000013206450463024040 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/bar/six.txt,v0000664000175100017510000000125012203665125025643 0ustar mhaggermhagger00000000000000head 1.3; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/bar/five.txt,v0000664000175100017510000000125012203665125025771 0ustar mhaggermhagger00000000000000head 1.3; access; symbols vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/peer-path-pruning-cvsrepos/README0000664000175100017510000002517312203665125024163 0ustar mhaggermhagger00000000000000This repository is for testing cvs2svn's pruning choices when filling symbolic names. The layout is: /one.txt two.txt /foo/three.txt four.txt /bar/five.txt six.txt Every file was imported in a standard way, then revisions 1.2 and 1.3 were committed on every file. Then this branch was made on four files (/one.txt, /two.txt, /foo/three.txt, /foo/four.txt): BRANCH: sprouts from 1.3 (so branch number 1.3.0.2) Then a revision was committed on that branch, creating revision 1.3.2.1 on those files. No branch was made on /bar/five.txt nor /bar/six.txt. Thus, when BRANCH is created, subdir /bar should be deleted entirely. But if you revert r1125 of cvs2svn.py, /bar won't be deleted. Search below for the word "Suppose" for more details. (Note that we still don't have a test for the proposed 'if not prune_ok:' conditional, though.) --------------------8-<-------cut-here---------8-<----------------------- From nobody Wed Jun 2 18:24:01 2004 Sender: kfogel@newton.ch.collab.net To: fitz@tigris.org Cc: commits@cvs2svn.tigris.org Subject: Re: cvs2svn commit: r1035 - branches/may-04-redesign References: <200405290539.i4T5dmM10064@morbius.ch.collab.net> From: kfogel@collab.net Reply-To: kfogel@collab.net X-Windows: putting new limits on productivity. Date: 02 Jun 2004 18:24:00 -0500 In-Reply-To: <200405290539.i4T5dmM10064@morbius.ch.collab.net> Message-ID: <85vfi9czn3.fsf@newton.ch.collab.net> Lines: 200 User-Agent: Gnus/5.09 (Gnus v5.9.0) Emacs/21.1.50 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii fitz@tigris.org writes: > Modified: branches/may-04-redesign/cvs2svn.py > ============================================================================== > --- branches/may-04-redesign/cvs2svn.py (original) > +++ branches/may-04-redesign/cvs2svn.py Sat May 29 00:39:43 2004 > @@ -4802,15 +4802,16 @@ > return dest > > def _fill(self, symbol_fill, key, name, > - parent_path_so_far=None, preferred_revnum=None): > + parent_path_so_far=None, preferred_revnum=None, prune_ok=None): > """Descends through all nodes in SYMBOL_FILL.node_tree that are > rooted at KEY, which is a string key into SYMBOL_FILL.node_tree. > Generates copy (and delete) commands for all destination nodes > that don't exist in NAME. > > - PARENT_PATH_SO_FAR is the parent directory of the path(s) that may > - be copied in this invocation of the method. If None, that means > - that our source path starts from the root of the repository. > + PARENT_PATH_SO_FAR is the parent directory of the source path(s) > + that may be copied in this invocation of the method. If None, > + that means that our source path starts from the root of the > + repository. > > PREFERRED_REVNUM is an int which is the source revision number > that the caller (who may have copied KEY's parent) used to > @@ -4818,9 +4819,15 @@ > is preferable to any other (which probably means that no copies > have happened yet). > > - PARENT_PATH_SO_FAR and PREFERRED_REVNUM should only be passed in > - by recursive calls.""" > + PRUNE_OK means that a copy has been made in this recursion, and > + it's safe to prune directories that are not in SYMBOL_FILL.node_tree. This documentation of PRUNE_OK might want to be a little more detailed, and make clear the cause-effect relationship between the first and second clauses. I.e., PRUNE_OK doesn't mean two unrelated things, it means one thing that happens to have an implication. Also, the pruning behavior applies to files as well as directories, no? (Maybe you were tempted to say "directories" because that's what the unrelated --prune flag to cvs2svn affects... Which is one reason not to call this new param 'prune_ok'; see later for another reason.) Anyway, I'm thinking something like this for the doc: PRUNE_OK means that a copy has already been made higher in this recursion, and that therefore it's safe to prune directories and files that do not appear in the part of SYMBOL_FILL.node_tree() governing this recursion. If that were the doc string, would it be accurate? > @@ -4839,7 +4846,7 @@ > src_revnum = None > # if our destination path doesn't already exist, then we may > # have to make a copy. > - if (dest_path is not None and not self.path_exists(dest_path)): > + if not self.path_exists(dest_path): > src_revnum = symbol_fill.get_best_revnum(key, preferred_revnum) > > # If the revnum of our parent's copy (src_revnum) is the same > @@ -4849,6 +4856,7 @@ > if src_revnum != preferred_revnum: > # Do the copy > new_entries = self.copy_path(src_path_so_far, dest_path, src_revnum) > + prune_ok = peer_path_unsafe_for_pruning = 1 > # Delete invalid entries that got swept in by the copy. > valid_entries = symbol_fill.node_tree[key] > bad_entries = self._get_invalid_entries(valid_entries, new_entries) > @@ -4857,8 +4865,25 @@ > del_path = dest_path + '/' + entry > self.delete_path(del_path) > > - self._fill(symbol_fill, key, name, src_path_so_far, src_revnum) > + self._fill(symbol_fill, key, name, src_path_so_far, src_revnum, prune_ok) > + > + if peer_path_unsafe_for_pruning: > + return > + # Any entries still present in the dest directory that we've just > + # created by copying, but not in > + # symbol_fill.node_tree[parent_key], don't belong and should be > + # deleted as well. If we haven't actually made a copy, do > + # nothing. > + if parent_path_so_far and prune_ok: > + this_path = self._dest_path_for_source_path(name, parent_path_so_far) > + ign, this_contents = self._node_for_path(this_path, self.youngest) > + expected_contents = symbol_fill.node_tree[parent_key] > + bad_entries = self._get_invalid_entries(expected_contents, this_contents) > + for entry in bad_entries: > + del_path = this_path + '/' + entry > > + print "FITZ: deleting path", del_path > + self.delete_path(del_path) Hmmm. Okay, I get the general idea here, and like it. There's something weird about 'peer_path_unsafe_for_pruning', though... Let me see if I can put my finger on it. Suppose we first make this copy: cp /trunk/foo/ @ r4 --> /branches/MYBRANCH/foo/ That sets 'peer_path_unsafe_for_pruning' to 1 in the stack frame for "/" (that is, the frame in which "/foo" is a child entry). Great. Suppose that foo@4 had two subdirs, "/foo/bar/" and "/foo/baz/", only the first of which is on this branch at all, albeit from r6 not r4. So, now we recurse down and copy "bar/" from r6: del /branches/MYBRANCH/foo/bar/ cp /trunk/foo/bar/ @ r6 --> /branches/MYBRANCH/foo/bar/ That sets 'peer_path_unsafe_for_pruning' to 1 in the stack frame for "/foo", of course, since "/foo/bar" is a child entry of that. But remember, earlier when we copied "/foo", we accidentally got "/foo/baz/" as well, which needs to be pruned out. We used to have code to prune that, but it's commented out now, see the comment that begins "###TODO OPTIMIZE: If we keep a list COPIED_PATHS of...". So the question is, who *will* prune "/foo/baz/"? All of the stack frames we've created in this fill have 'peer_path_unsafe_for_pruning' set to 1, so all of them will return early, without entering the blob of code at the end that is supposed to clean up these bad entries. Thus, I don't think anyone can prune "/foo/baz/", even though it clearly must be pruned. Another way of saying it is, that commented out loop that's supposed to be just an optimization is actually not an optimization -- it's correctness code. But I'm not saying the answer here is just to uncomment that loop. I think also 'peer_path_unsafe_for_pruning' should not get set *if* 'prune_ok' is already set! In other words prune_ok = peer_path_unsafe_for_pruning = 1 should be changed to if not prune_ok: prune_ok = peer_path_unsafe_for_pruning = 1 Because if 'prune_ok' is already set, then we're already in a recursive call after a copy, so peer paths are, in fact, safe for pruning, even though they are peer paths! (One way to think of it is that the name 'prune_ok' should be 'we_are_now_under_a_copy', or 'copy_happened', or something like that. What the param really indicates is that all dest paths being generated in this leg of the recursion are underneath a copy -- the fact that pruning is okay is merely one consequence of that situation.) Is this making any sense, or am I off my rocker? The comment right before the final blob of code confused me for a minute... # Any entries still present in the dest directory that we've just # created by copying, but not in # symbol_fill.node_tree[parent_key], don't belong and should be # deleted as well. If we haven't actually made a copy, do # nothing. ... because it talks about "any entries still present in the dest directory that we've just copied", yet 'parent_path_so_far' is *not* a directory we just copied, rather it's the parent of things we may or may not have copied. IOW, this code (the last bit of code in the function, right below the above comment)... if parent_path_so_far and prune_ok: this_path = self._dest_path_for_source_path(name, parent_path_so_far) ign, this_contents = self._node_for_path(this_path, self.youngest) expected_contents = symbol_fill.node_tree[parent_key] bad_entries = self._get_invalid_entries(expected_contents, this_contents) for entry in bad_entries: del_path = this_path + '/' + entry self.delete_path(del_path) ... cannot be talking about a directory we've just copied in this stack frame, but rather about a directory copied in some previous frame, for which we're now in a recursive call (that is, an old parent_path_so_far got extended by one or more entry components, following a copy). Perhaps this is what is meant by "the dest directory that we've just copied", and all we need to do is clarify things by saying "the dest directory at or under a copy made in an call higher up in this recursion". Well that's clumsy, but you know what I mean. Anyway, those are my thoughts for now. Are they anything like yours, or have I fanned off into my own private Oort cloud of insanity? Btw, can't say I share the feeling that _fill() has gotten overly complex. I mean, apparently it has a bug or two right now, but in general it seems to be about as complex as the problem it solves -- I wouldn't want to break it up any further. -K cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/0000775000175100017510000000000013206450463022270 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/one.txt,v0000664000175100017510000000235212203665124024054 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3.12.1 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.2.1.0.4 BRANCH_7:1.3.0.10 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1 1.3.12.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; 1.3.12.1 date 2004.06.28.22.03.12; author tori; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.12.1 log @First change on branch BRANCH_B1 @ text @d1 1 a1 1 Yet another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/three.txt,v0000664000175100017510000000205712203665124024404 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.2.1.0.4 BRANCH_7:1.3.0.10 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/two.txt,v0000664000175100017510000000205712203665124024106 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.2.1.0.4 BRANCH_7:1.3.0.10 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/0000775000175100017510000000000013206450463023142 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/six.txt,v0000664000175100017510000000205712203665124024752 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.0.10 BRANCH_7:1.3.2.1.0.4 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/four.txt,v0000664000175100017510000000205712203665124025122 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.2.1.0.4 BRANCH_7:1.3.0.10 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/sub2/0000775000175100017510000000000013206450463024015 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/sub2/nine.txt,v0000664000175100017510000000211412203665124025745 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.8 BRANCH_8:1.3.2.1.0.10 BRANCH_7:1.3.0.6 BRANCH_6:1.3.2.1.0.8 BRANCH_5:1.3.2.1.0.6 BRANCH_4:1.3.0.4 BRANCH_3:1.3.2.1.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/sub2/ten.txt,v0000664000175100017510000000212112203665124025600 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.6 BRANCH_8:1.3.2.1.0.12 BRANCH_7:1.3.0.4 BRANCH_6:1.3.2.1.0.10 BRANCH_5:1.3.2.1.0.8 BRANCH_4:1.3.2.1.0.6 BRANCH_3:1.3.2.1.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/sub2/seven.txt,v0000664000175100017510000000210512203665124026134 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.2.1.0.6 BRANCH_7:1.3.0.10 BRANCH_6:1.3.0.8 BRANCH_5:1.3.0.6 BRANCH_4:1.3.0.4 BRANCH_3:1.3.2.1.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/sub2/eight.txt,v0000664000175100017510000000211012203665124026110 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.10 BRANCH_8:1.3.2.1.0.8 BRANCH_7:1.3.0.8 BRANCH_6:1.3.2.1.0.6 BRANCH_5:1.3.0.6 BRANCH_4:1.3.0.4 BRANCH_3:1.3.2.1.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/sub1/five.txt,v0000664000175100017510000000205712203665124025100 0ustar mhaggermhagger00000000000000head 1.3; access; symbols TAG_B1:1.3 BRANCH_B1:1.3.0.12 BRANCH_8:1.3.0.10 BRANCH_7:1.3.2.1.0.4 BRANCH_5:1.3.0.8 BRANCH_4:1.3.0.6 BRANCH_3:1.3.0.4 BRANCH_2:1.3.2.1.0.2 BRANCH_1:1.3.0.2 vendortag:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.3 date 2004.06.07.18.42.06; author kfogel; state Exp; branches 1.3.2.1; next 1.2; 1.2 date 2004.06.07.18.38.34; author kfogel; state Exp; branches; next 1.1; 1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.05.24.01.02.01; author kfogel; state Exp; branches; next ; 1.3.2.1 date 2004.06.07.20.29.58; author kfogel; state Exp; branches; next ; desc @@ 1.3 log @Commit 1.3 of every file. @ text @Another small change. @ 1.3.2.1 log @Commit change to everyone on branch BRANCH_1. @ text @d1 1 a1 1 First change on branch BRANCH_1 (1.3.2.1 for everyone). @ 1.2 log @Commit 1.2 of every file. @ text @d1 1 a1 1 A small change. @ 1.1 log @Initial revision @ text @d1 1 a1 1 This is the first revision. @ 1.1.1.1 log @Initial import. @ text @@ cvs2svn-2.5.0/test-data/fill-choices-cvsrepos/README0000664000175100017510000000660112203665124023151 0ustar mhaggermhagger00000000000000This repository is for testing cvs2svn's choices of which directories to copy when filling symbolic names. The layout is: /one.txt two.txt three.txt /sub1/four.txt five.txt six.txt sub2/seven.txt eight.txt nine.txt ten.txt Every file was imported in a standard way, then revisions 1.2 and 1.3 were committed on every file. Then a branch was made: BRANCH_1: sprouts from every file's 1.3 (so branch number 1.3.0.2) Then a revision was committed on that branch, creating revision 1.3.2.1 on every file. Next a branch was made from that revision, on every file: BRANCH_2: sprouts from every file's 1.3.2.1 (so branch number 1.3.2.1.0.2) BRANCH_3 to BRANCH_8 all sprout from either trunk, or from the first revision on BRANCH_1 (that is, from 1.3.2.1), in various combinations. Every branch below exists on every file, the only question is where the branch is rooted for each file. BRANCH_3: Sprouts from trunk everywhere except sub1/sub2/*, where it sprouts from BRANCH_1 for all four files. BRANCH_4: Sprouts from trunk everywhere except for sub1/sub2/ten.txt, where it sprouts from BRANCH_1. Note that this is a clear minority in sub1/sub2/, since it still sprouts from trunk on the other three files there ('seven.txt', 'eight.txt', and 'nine.txt'). BRANCH_5: Sprouts from trunk everywhere except for sub1/sub2/nine.txt and sub1/sub2/ten.txt, where it sprouts from BRANCH_1. This is an even division in sub1/sub2/, since it sprouts from trunk on two files ('seven.txt' and 'eight.txt') and from BRANCH_1 on the other two ('nine.txt' and 'ten.txt'). BRANCH_6: Sprouts from trunk everywhere except for sub1/sub2/eight.txt, sub1/sub2/nine.txt, and sub1/sub2/ten.txt, where it sprouts from BRANCH_1. This is a clear majority in favor of BRANCH_1, since BRANCH_6 sprouts from trunk on only one file ('seven.txt') and from BRANCH_1 on the other three ('eight.txt', 'nine.txt' and 'ten.txt'). BRANCH_7: Sprouts from trunk everywhere except sub1/five.txt and sub1/six.txt, where it sprouts from BRANCH_1. This is a majority in favor of BRANCH_1 there, as the only other file in that directory is 'four.txt', but note that both the parent directory and the sole subdirectory are majority from trunk. BRANCH_8: The reverse of BRANCH_7. Sprouts from BRANCH_1 everywhere except sub1/five.txt and sub1/six.txt, where it sprouts from trunk. This is a majority in favor of trunk there, as the only other file in that directory is 'four.txt', but note that both the parent directory and the sole subdirectory are majority from BRANCH_1. To test the filling of a tag set on a branch, a new branch is created. BRANCH_B1: sprouts from every file's 1.3 (so branch number 1.3.0.12) A single change to one.txt in BRANCH_B1 is committed so that 1.3.12.1 is created on that file, and TAG_B1 is set on the tip of that branch. TAG_B1: set on 1.3.12.1 on one.txt, and 1.3 on the rest TAG_B1 should be created as a single copy from BRANCH_B1. cvs2svn-2.5.0/test-data/questionable-symbols-cvsrepos/0000775000175100017510000000000013206450463024110 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/questionable-symbols-cvsrepos/foo.txt,v0000664000175100017510000001171212203665125025677 0ustar mhaggermhagger00000000000000head 1.2; access; symbols TagWith/Slash_Z:1.2 TagWith\Backslash_E:1.2 TagWith///ThreeSlashes_D:1.2 Tag_A:1.2 BranchWith/Slash_Z:1.1.0.10 /BranchStartsWithSlash_Y:1.1.0.8 #BranchStartsWithHash_X:1.1.0.6 BranchWith.Dot_W:1.1.0.4 3BranchStartsWithNumber_V:1.1.0.2 BranchWith\Backslash_E:1.2.0.10 BranchWith///ThreeSlashes_D:1.2.0.8 BranchWith.Various/Prohibited\Symbols_C:1.2.0.6 \BranchStartsWithBackslash_B:1.2.0.4 Branch_A:1.2.0.2; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author kfogel; state Exp; branches 1.2.2.1 1.2.4.1 1.2.6.1 1.2.8.1 1.2.10.1; next 1.1; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches 1.1.2.1 1.1.4.1 1.1.6.1 1.1.8.1 1.1.10.1; next ; 1.1.2.1 date 2004.08.11.18.27.06; author kfogel; state Exp; branches; next 1.1.2.2; 1.1.2.2 date 2004.08.11.18.28.12; author kfogel; state Exp; branches; next ; 1.1.4.1 date 2004.08.11.18.27.09; author kfogel; state Exp; branches; next 1.1.4.2; 1.1.4.2 date 2004.08.11.18.28.15; author kfogel; state Exp; branches; next ; 1.1.6.1 date 2004.08.11.18.27.12; author kfogel; state Exp; branches; next 1.1.6.2; 1.1.6.2 date 2004.08.11.18.28.18; author kfogel; state Exp; branches; next ; 1.1.8.1 date 2004.08.11.18.27.15; author kfogel; state Exp; branches; next 1.1.8.2; 1.1.8.2 date 2004.08.11.18.28.21; author kfogel; state Exp; branches; next ; 1.1.10.1 date 2004.08.11.18.27.18; author kfogel; state Exp; branches; next 1.1.10.2; 1.1.10.2 date 2004.08.11.18.28.24; author kfogel; state Exp; branches; next ; 1.2.2.1 date 2004.08.11.18.26.55; author kfogel; state Exp; branches; next 1.2.2.2; 1.2.2.2 date 2004.08.11.18.27.57; author kfogel; state Exp; branches; next ; 1.2.4.1 date 2004.08.11.18.26.57; author kfogel; state Exp; branches; next 1.2.4.2; 1.2.4.2 date 2004.08.11.18.28.00; author kfogel; state Exp; branches; next ; 1.2.6.1 date 2004.08.11.18.26.59; author kfogel; state Exp; branches; next 1.2.6.2; 1.2.6.2 date 2004.08.11.18.28.03; author kfogel; state Exp; branches; next ; 1.2.8.1 date 2004.08.11.18.27.01; author kfogel; state Exp; branches; next 1.2.8.2; 1.2.8.2 date 2004.08.11.18.28.06; author kfogel; state Exp; branches; next ; 1.2.10.1 date 2004.08.11.18.27.03; author kfogel; state Exp; branches; next 1.2.10.2; 1.2.10.2 date 2004.08.11.18.28.09; author kfogel; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.2.10.1 log @Committing branch change (unique code 'E'). @ text @a2 1 First commit on this branch (unique code 'E'). @ 1.2.10.2 log @Committing another branch change (unique code 'E'). @ text @a3 1 Second commit on this branch (unique code 'E'). @ 1.2.8.1 log @Committing branch change (unique code 'D'). @ text @a2 1 First commit on this branch (unique code 'D'). @ 1.2.8.2 log @Committing another branch change (unique code 'D'). @ text @a3 1 Second commit on this branch (unique code 'D'). @ 1.2.6.1 log @Committing branch change (unique code 'C'). @ text @a2 1 First commit on this branch (unique code 'C'). @ 1.2.6.2 log @Committing another branch change (unique code 'C'). @ text @a3 1 Second commit on this branch (unique code 'C'). @ 1.2.4.1 log @Committing branch change (unique code 'B'). @ text @a2 1 First commit on this branch (unique code 'B'). @ 1.2.4.2 log @Committing another branch change (unique code 'B'). @ text @a3 1 Second commit on this branch (unique code 'B'). @ 1.2.2.1 log @Committing branch change (unique code 'A'). @ text @a2 1 First commit on this branch (unique code 'A'). @ 1.2.2.2 log @Committing another branch change (unique code 'A'). @ text @a3 1 Second commit on this branch (unique code 'A'). @ 1.1 log @Add a file. @ text @d2 1 @ 1.1.10.1 log @Committing branch change (unique code 'Z'). @ text @a1 1 First commit on this branch (unique code 'Z'). @ 1.1.10.2 log @Committing another branch change (unique code 'Z'). @ text @a2 1 Second commit on this branch (unique code 'Z'). @ 1.1.8.1 log @Committing branch change (unique code 'Y'). @ text @a1 1 First commit on this branch (unique code 'Y'). @ 1.1.8.2 log @Committing another branch change (unique code 'Y'). @ text @a2 1 Second commit on this branch (unique code 'Y'). @ 1.1.6.1 log @Committing branch change (unique code 'X'). @ text @a1 1 First commit on this branch (unique code 'X'). @ 1.1.6.2 log @Committing another branch change (unique code 'X'). @ text @a2 1 Second commit on this branch (unique code 'X'). @ 1.1.4.1 log @Committing branch change (unique code 'W'). @ text @a1 1 First commit on this branch (unique code 'W'). @ 1.1.4.2 log @Committing another branch change (unique code 'W'). @ text @a2 1 Second commit on this branch (unique code 'W'). @ 1.1.2.1 log @Committing branch change (unique code 'V'). @ text @a1 1 First commit on this branch (unique code 'V'). @ 1.1.2.2 log @Committing another branch change (unique code 'V'). @ text @a2 1 Second commit on this branch (unique code 'V'). @ cvs2svn-2.5.0/test-data/phoenix-cvsrepos/0000775000175100017510000000000013206450463021401 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/phoenix-cvsrepos/phoenix,v0000664000175100017510000001650112203665125023242 0ustar mhaggermhagger00000000000000head 1.4; access; symbols libogg2-zerocopy:1.4.0.4 vorbis1_0_public_release:1.4 volsung_flush:1.4.0.2 release_0_8_2:1.4 vorbis1_0_public_release_candidate_2:1.4 volsung_20010721:1.2.0.2 start:1.1.1.1 xiphophorus:1.1.1; locks; strict; comment @# @; 1.4 date 2001.08.05.02.35.30; author volsung; state Exp; branches; next 1.3; 1.3 date 2001.08.04.02.56.08; author volsung; state Exp; branches; next 1.2; 1.2 date 2000.10.31.07.08.41; author jack; state dead; branches 1.2.2.1; next 1.1; 1.1 date 2000.09.03.09.56.05; author jack; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2000.09.03.09.56.05; author jack; state Exp; branches; next ; 1.2.2.1 date 2001.07.22.03.35.41; author volsung; state Exp; branches; next 1.2.2.2; 1.2.2.2 date 2001.07.24.17.51.09; author volsung; state Exp; branches; next ; desc @@ 1.4 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "Documentation update." @ text @ libao - Documentation

libao documentation

libao version 0.8.0 - 20010804

libao Documentation

Libao is a cross-platform library that allows programs to output PCM audio data to the native audio devices on a wide variety of platforms. It currently supports:

  • OSS (Open Sound System)
  • ESD (ESounD)
  • ALSA (Advanced Linux Sound Architecture)
  • Sun audio system (used in Solaris, OpenBSD, and NetBSD)
  • aRts (Analog Realtime Synthesizer)

libao api overview
drivers
example code
configuration files
libao api reference
plugin writer's overview
plugin api reference



copyright © 2001 Stan Seibert

xiph.org
indigo@@aztec.asu.edu

libao documentation

libao version 0.8.0 - 20010804

@ 1.3 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "Merger of new API branch (volsung_20010721) with head." @ text @d12 1 a12 1

libao version 0.90 - 20010528

d35 1 a35 1 plugin overview
d46 1 a46 1

libao version 0.90 - 20010528

@ 1.2 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "documetation???? WHAT!?!" jack. @ text @d1 52 a52 1 There is no documentation yet. @ 1.2.2.1 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "Initial branch of libao to new API. Great fear and trembling shall sweep the land..." @ text @d1 1 a1 52 libao - Documentation

libao documentation

libao version 0.90 - 20010528

libao Documentation

Libao is a cross-platform library that allows programs to output PCM audio data to the native audio devices on a wide variety of platforms. It currently supports:

  • OSS (Open Sound System)
  • ESD (ESounD)
  • ALSA (Advanced Linux Sound Architecture)
  • Sun audio system (used in Solaris, OpenBSD, and NetBSD)
  • aRts (Analog Realtime Synthesizer)

libao api overview
driver list
example code
configuration files
libao api reference
plugin overview
plugin api reference



copyright © 2001 Stan Seibert

xiph.org
indigo@@aztec.asu.edu

libao documentation

libao version 0.90 - 20010528

@ 1.2.2.2 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "We now use a ranking system to select a defaut driver from the range of possible drivers. Note that the null device is no longer a possible default device to prevent user confusion that we have had in the past." @ text @d31 1 a31 1 drivers
@ 1.1 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "Initial revision" @ text @@ 1.1.1.1 log @This file was supplied by Jack Moffitt to help us reproduce a bug in which cvs2svn.py would throw an exception when it tried to create a file branch rooted in a 'dead' RCS revision. See http://subversion.tigris.org/issues/show_bug.cgi?id=1417 for more details. The original file was ao/doc/index.html,v, and the log message for this revision was: "The first sample..." @ text @@ cvs2svn-2.5.0/test-data/phoenix-cvsrepos/file.txt,v0000664000175100017510000000052212203665125023321 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2000.09.01.09.56.05; author fitz; state Exp; branches; next ; desc @@ 1.1 log @initial import @ text @This is a file on trunk that has a few revisions. It's here only to make sure that it doesn't accidentally get copied over to any branches when they get created. @ cvs2svn-2.5.0/test-data/phoenix-cvsrepos/Attic/0000775000175100017510000000000013206450463022445 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/phoenix-cvsrepos/Attic/added-on-branch2.txt,v0000664000175100017510000000124612203665125026442 0ustar mhaggermhagger00000000000000head 1.1; access; symbols xiphophorus:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.15.15.39.58; author fitz; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.15.15.39.58; author fitz; state Exp; branches; next ; desc @@ 1.1 log @file added-on-branch2.txt was initially added on branch xiphophorus, and this log message was tweaked so that it's not the standard log message generated by CVS for the 'dead' trunk revision of a file added on a branch. @ text @@ 1.1.2.1 log @This file was also added on branch xiphophorus, but slightly after the other file was added on this branch. @ text @a0 1 This file was also added on the branch xiphophorus. @ cvs2svn-2.5.0/test-data/phoenix-cvsrepos/Attic/added-on-branch.txt,v0000664000175100017510000000067512203665125026365 0ustar mhaggermhagger00000000000000head 1.1; access; symbols xiphophorus:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.15.15.33.01; author fitz; state dead; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.15.15.33.01; author fitz; state Exp; branches; next ; desc @@ 1.1 log @file added-on-branch.txt was initially added on branch xiphophorus. @ text @@ 1.1.2.1 log @File added on branch xiphophorus @ text @a0 1 This file was added on the branch xiphophorus.@ cvs2svn-2.5.0/test-data/eol-variants-cvsrepos/0000775000175100017510000000000013206450463022333 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/eol-variants-cvsrepos/proj/0000775000175100017510000000000013206450463023305 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/eol-variants-cvsrepos/proj/file.txt,v0000444000175100017510000000033012203665124025215 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.10.17.21.00.40; author mhagger; state Exp; branches; next ; commitid nkHx4xZOOGvuiZBs; desc @@ 1.1 log @Adding file @ text @line 1 line 2 @ cvs2svn-2.5.0/test-data/ctrl-char-in-log-cvsrepos/0000775000175100017510000000000013206450463022771 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/ctrl-char-in-log-cvsrepos/ctrl-char-in-log,v0000664000175100017510000000104312203665124026214 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vendorbranch:1.1.1 vendortag:1.1.1.1; locks ; strict; comment @# @; 1.1 date 2003.07.28.15.00.00; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2003.07.28.15.00.00; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @The content of this revision is unimportant, what matters is that this log message contains a Ctrl-D right here, "", and cvs2svn.py should handle this. @ text @Nothing to see here. @ 1.1.1.1 log @imported @ text @@ cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/0000775000175100017510000000000013206450463024025 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/makerepo.sh0000775000175100017510000000305112203665124026164 0ustar mhaggermhagger00000000000000#! /bin/sh # This is the script used to create the branch-from-deleted-1-1 CVS # repository. (The repository is checked into svn; this script is # only here for its documentation value.) # # The script should be started from the main cvs2svn directory. name=branch-from-deleted-1-1 repo=`pwd`/test-data/$name-cvsrepos wc=`pwd`/cvs2svn-tmp/$name-wc [ -e $repo/CVSROOT ] && rm -rf $repo/CVSROOT [ -e $repo/proj ] && rm -rf $repo/proj [ -e $wc ] && rm -rf $wc cvs -d $repo init cvs -d $repo co -d $wc . cd $wc mkdir proj cvs add proj cd proj echo "Create a file a.txt on trunk:" echo '1.1' >a.txt cvs add a.txt cvs commit -m 'Adding a.txt:1.1' . echo "Create two branches on file a.txt:" cvs tag -b BRANCH1 cvs tag -b BRANCH2 echo "Add file b.txt on BRANCH1:" cvs up -r BRANCH1 echo '1.1.2.1' >b.txt cvs add b.txt cvs commit -m 'Adding b.txt:1.1.2.1' echo "Add file b.txt on BRANCH2:" cvs up -r BRANCH2 echo '1.1.4.1' >b.txt cvs add b.txt cvs commit -m 'Adding b.txt:1.1.4.1' echo "Add file b.txt on trunk:" cvs up -A echo '1.2' >b.txt cvs add b.txt cvs commit -m 'Adding b.txt:1.2' echo "Add file c.txt on BRANCH1:" cvs up -r BRANCH1 echo '1.1.2.1' >c.txt cvs add c.txt cvs commit -m 'Adding c.txt:1.1.2.1' echo "Add file c.txt on BRANCH2:" cvs up -r BRANCH2 echo '1.1.4.1' >c.txt cvs add c.txt cvs commit -m 'Adding c.txt:1.1.4.1' echo "Create branch BRANCH3 from 1.1 versions of b.txt and c.txt:" cvs rtag -r 1.1 -b BRANCH3 proj/b.txt proj/c.txt echo "Create tag TAG1 from 1.1 versions of b.txt and c.txt:" cvs rtag -r 1.1 TAG1 proj/b.txt proj/c.txt cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/proj/0000775000175100017510000000000013206450463024777 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/proj/Attic/0000775000175100017510000000000013206450463026043 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/proj/Attic/c.txt,v0000664000175100017510000000107212203665124027266 0ustar mhaggermhagger00000000000000head 1.1; access; symbols TAG1:1.1 BRANCH3:1.1.0.6 BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.06.25.22.20.19; author mhagger; state dead; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2007.06.25.22.20.19; author mhagger; state Exp; branches; next ; 1.1.4.1 date 2007.06.25.22.20.21; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @file c.txt was initially added on branch BRANCH1. @ text @@ 1.1.4.1 log @Adding c.txt:1.1.4.1 @ text @a0 1 1.1.4.1 @ 1.1.2.1 log @Adding c.txt:1.1.2.1 @ text @a0 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/proj/a.txt,v0000664000175100017510000000033212203665124026216 0ustar mhaggermhagger00000000000000head 1.1; access; symbols BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.1 date 2007.06.25.22.20.14; author mhagger; state Exp; branches; next ; desc @@ 1.1 log @Adding a.txt:1.1 @ text @1.1 @ cvs2svn-2.5.0/test-data/branch-from-deleted-1-1-cvsrepos/proj/b.txt,v0000664000175100017510000000126712203665124026227 0ustar mhaggermhagger00000000000000head 1.2; access; symbols TAG1:1.1 BRANCH3:1.1.0.6 BRANCH2:1.1.0.4 BRANCH1:1.1.0.2; locks; strict; comment @# @; 1.2 date 2007.06.25.22.20.17; author mhagger; state Exp; branches; next 1.1; 1.1 date 2007.06.25.22.20.15; author mhagger; state dead; branches 1.1.2.1 1.1.4.1; next ; 1.1.2.1 date 2007.06.25.22.20.15; author mhagger; state Exp; branches; next ; 1.1.4.1 date 2007.06.25.22.20.16; author mhagger; state Exp; branches; next ; desc @@ 1.2 log @Adding b.txt:1.2 @ text @1.2 @ 1.1 log @file b.txt was initially added on branch BRANCH1. @ text @d1 1 @ 1.1.4.1 log @Adding b.txt:1.1.4.1 @ text @a0 1 1.1.4.1 @ 1.1.2.1 log @Adding b.txt:1.1.2.1 @ text @a0 1 1.1.2.1 @ cvs2svn-2.5.0/test-data/internal-co-cvsrepos/0000775000175100017510000000000013206450463022142 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/internal-co-cvsrepos/branched/0000775000175100017510000000000013206450463023710 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/internal-co-cvsrepos/branched/Attic/0000775000175100017510000000000013206450463024754 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/internal-co-cvsrepos/branched/Attic/somefile.txt,v0000664000175100017510000000443712203665124027570 0ustar mhaggermhagger00000000000000head 1.5; access; symbols BRANCH_FROM_DEAD:1.5.0.2 BRANCH:1.1.0.2; locks; strict; comment @# @; 1.5 date 2007.04.05.15.32.44; author ossi; state dead; branches 1.5.2.1; next 1.4; commitid ksTEPgcwRGzBKTcs; 1.4 date 2007.04.05.15.32.23; author ossi; state Exp; branches; next 1.3; commitid g00K7nhwfWduKTcs; 1.3 date 2007.04.05.15.13.23; author ossi; state dead; branches; next 1.2; commitid 6vDsezBCNsbYDTcs; 1.2 date 2007.04.05.15.13.08; author ossi; state Exp; branches; next 1.1; commitid XPB93k3c4jJSDTcs; 1.1 date 2007.04.05.15.07.41; author ossi; state Exp; branches 1.1.2.1; next ; commitid jgg4E7IfvqX0CTcs; 1.5.2.1 date 2007.04.05.15.32.44; author ossi; state dead; branches; next 1.5.2.2; commitid bGYbKyNPiicdLTcs; 1.5.2.2 date 2007.04.05.15.34.30; author ossi; state Exp; branches; next ; commitid bGYbKyNPiicdLTcs; 1.1.2.1 date 2007.04.05.15.30.02; author ossi; state Exp; branches; next 1.1.2.2; commitid 6MkfqH2xRPLDJTcs; 1.1.2.2 date 2007.04.05.15.30.44; author ossi; state Exp; branches; next 1.1.2.3; commitid BSF6Cvx3cHWUJTcs; 1.1.2.3 date 2007.04.05.15.30.55; author ossi; state dead; branches; next ; commitid UTPOIUtFBh3ZJTcs; desc @@ 1.5 log @file re-deleted on trunk @ text @resurrected file content. @ 1.5.2.1 log @file somefile.txt was added on branch BRANCH_FROM_DEAD on 2007-04-05 15:34:30 +0000 @ text @d1 1 @ 1.5.2.2 log @file revived on branch @ text @a0 1 text on branch spawning from dead revision.@ 1.4 log @file resurrected on trunk @ text @@ 1.3 log @file deleted @ text @d1 1 a1 2 keyword: $Id: somefile.txt,v 1.2 2007-04-05 15:13:08 ossi Exp $ now done this is modified file content. @ 1.2 log @file modified @ text @d1 1 a1 1 keyword: $Id: somefile.txt,v 1.1 2007-04-05 15:07:41 ossi Exp $ now done @ 1.1 log @file added @ text @d1 2 a2 2 this is file content. keyword: $Id: fake expaded keyword$ now done @ 1.1.2.1 log @modified on branch @ text @d2 1 a2 2 text added on branch. keyword: $Id: somefile.txt,v 1.1 2007-04-05 15:07:41 ossi Exp $ now done @ 1.1.2.2 log @file modified on branch, take 2 @ text @d3 1 a3 2 keyword: $Id: somefile.txt,v 1.1.2.1 2007-04-05 15:30:02 ossi Exp $ now done more text added on branch. @ 1.1.2.3 log @file deleted on branch @ text @d3 1 a3 1 keyword: $Id: somefile.txt,v 1.1.2.2 2007-04-05 15:30:44 ossi Exp $ now done @ cvs2svn-2.5.0/test-data/exclude-ntdb-cvsrepos/0000775000175100017510000000000013206450463022305 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/exclude-ntdb-cvsrepos/makerepo.sh0000775000175100017510000000355712203665124024457 0ustar mhaggermhagger00000000000000#! /bin/sh # Run script from the main cvs2svn directory to create the # exclude-ntdb cvs repository. CVSROOT=`pwd`/test-data/exclude-ntdb-cvsrepos TMP=cvs2svn-tmp rm -rf $TMP mkdir $TMP cd $TMP #cvs -d $CVSROOT init rm -rf $CVSROOT/proj mkdir proj echo 'Import proj/file.txt:' cd proj echo '1.1.1.1' >file.txt cvs -d $CVSROOT import -m "First import" proj vendorbranch vendortag1 sleep 2 cd .. echo 'Check out the repository:' cvs -d $CVSROOT co -d wc . echo 'Add a tag and a branch to trunk (these appear on revision 1.1.1.1)' echo 'and commit a revision on the branch:' cd wc/proj cvs tag tag1 cvs tag -b branch1 cvs up -r branch1 echo 1.1.1.1.2.1 >file.txt cvs ci -m 'Commit on branch branch1' sleep 2 cd ../.. echo 'Import proj/file.txt a second time:' cd proj echo '1.1.1.2' >file.txt cvs -d $CVSROOT import -m "Second import" proj vendorbranch vendortag2 sleep 2 cd .. echo 'Add a second tag and branch to trunk (these appear on revision' echo '1.1.1.2) and commit a revision on the branch:' cd wc/proj cvs up -A cvs tag tag2 cvs tag -b branch2 cvs up -r branch2 echo 1.1.1.2.2.1 >file.txt cvs ci -m 'Commit on branch branch2' sleep 2 cd ../.. echo 'Commit directly to trunk. This creates a revision 1.2 and' echo 'changes the default branch back to trunk:' cd wc/proj cvs up -A echo '1.2' >file.txt cvs ci -m 'First explicit commit on trunk' sleep 2 cd ../.. echo 'Import again. This import is no longer on the non-trunk vendor' echo 'branch, so it does not have any effect on trunk:' cd proj echo '1.1.1.3' >file.txt cvs -d $CVSROOT import -m "Third import" proj vendorbranch vendortag3 sleep 2 cd .. echo 'Create a tag and a branch explicitly from the vendor branch, and' echo 'commit a revision on the branch:' cd wc/proj cvs up -r vendorbranch cvs tag tag3 cvs tag -b branch3 cvs up -r branch3 echo 1.1.1.3.2.1 >file.txt cvs ci -m 'Commit on branch branch3' sleep 2 cd ../.. cvs2svn-2.5.0/test-data/exclude-ntdb-cvsrepos/proj/0000775000175100017510000000000013206450463023257 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/exclude-ntdb-cvsrepos/proj/file.txt,v0000664000175100017510000000317512203665124025205 0ustar mhaggermhagger00000000000000head 1.2; access; symbols branch3:1.1.1.3.0.2 tag3:1.1.1.3 vendortag3:1.1.1.3 branch2:1.1.1.2.0.2 tag2:1.1.1.2 vendortag2:1.1.1.2 branch1:1.1.1.1.0.2 tag1:1.1.1.1 vendortag1:1.1.1.1 vendorbranch:1.1.1; locks; strict; comment @# @; 1.2 date 2008.03.23.21.09.25; author mhagger; state Exp; branches; next 1.1; commitid eP23GQr5EN3CgiWs; 1.1 date 2008.03.23.21.09.12; author mhagger; state Exp; branches 1.1.1.1; next ; commitid DP3br0gPAbExgiWs; 1.1.1.1 date 2008.03.23.21.09.12; author mhagger; state Exp; branches 1.1.1.1.2.1; next 1.1.1.2; commitid DP3br0gPAbExgiWs; 1.1.1.2 date 2008.03.23.21.09.18; author mhagger; state Exp; branches 1.1.1.2.2.1; next 1.1.1.3; commitid jW6XTZM1bdCzgiWs; 1.1.1.3 date 2008.03.23.21.09.28; author mhagger; state Exp; branches 1.1.1.3.2.1; next ; commitid 9MGIzCxv7EgDgiWs; 1.1.1.1.2.1 date 2008.03.23.21.09.15; author mhagger; state Exp; branches; next ; commitid 8JhDtGnHHSyygiWs; 1.1.1.2.2.1 date 2008.03.23.21.09.21; author mhagger; state Exp; branches; next ; commitid t6VniT76SxUAgiWs; 1.1.1.3.2.1 date 2008.03.23.21.09.31; author mhagger; state Exp; branches; next ; commitid gM9gAG7c2jgEgiWs; desc @@ 1.2 log @First explicit commit on trunk @ text @1.2 @ 1.1 log @Initial revision @ text @d1 1 a1 1 1.1.1.1 @ 1.1.1.1 log @First import @ text @@ 1.1.1.2 log @Second import @ text @d1 1 a1 1 1.1.1.2 @ 1.1.1.3 log @Third import @ text @d1 1 a1 1 1.1.1.3 @ 1.1.1.3.2.1 log @Commit on branch branch3 @ text @d1 1 a1 1 1.1.1.3.2.1 @ 1.1.1.2.2.1 log @Commit on branch branch2 @ text @d1 1 a1 1 1.1.1.2.2.1 @ 1.1.1.1.2.1 log @Commit on branch branch1 @ text @d1 1 a1 1 1.1.1.1.2.1 @ cvs2svn-2.5.0/test-data/overlapping-branch-cvsrepos/0000775000175100017510000000000013206450463023510 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/overlapping-branch-cvsrepos/nonoverlapping-branch,v0000664000175100017510000000067712203665125030202 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vendorA:1.1.1; locks ; strict; comment @# @; 1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @The content of this file is unimportant, what matters is that it has one branch. @ 1.1.1.1 log @imported @ text @@ cvs2svn-2.5.0/test-data/overlapping-branch-cvsrepos/overlapping-branch,v0000664000175100017510000000103512203665125027454 0ustar mhaggermhagger00000000000000head 1.1; branch 1.1.1; access ; symbols vendorA:1.1.1 vendorB:1.1.1; locks ; strict; comment @# @; 1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2002.11.30.19.27.42; author jrandom; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @The content of this file is unimportant, what matters is that the same branch has two different symbolic names, a condition which cvs2svn.py should warn about. @ 1.1.1.1 log @imported @ text @@ cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/0000775000175100017510000000000013206450463023265 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/root/0000775000175100017510000000000013206450463024250 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/root/b,v0000664000175100017510000000050712203665125024657 0ustar mhaggermhagger00000000000000head 1.1; access; symbols mybranch:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.05.13.30.31; author max; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.05.13.31.12; author max; state Exp; branches; next ; desc @@ 1.1 log @Add a and b to trunk @ text @@ 1.1.2.1 log @Branch commit of b @ text @@ cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/root/c,v0000664000175100017510000000050112203665125024652 0ustar mhaggermhagger00000000000000head 1.1; access; symbols mybranch:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.05.13.31.17; author max; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.05.13.31.18; author max; state Exp; branches; next ; desc @@ 1.1 log @Add c to trunk @ text @@ 1.1.2.1 log @Branch commit of c @ text @@ cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/root/a,v0000664000175100017510000000050712203665125024656 0ustar mhaggermhagger00000000000000head 1.1; access; symbols mybranch:1.1.0.2; locks; strict; comment @# @; 1.1 date 2004.06.05.13.30.31; author max; state Exp; branches 1.1.2.1; next ; 1.1.2.1 date 2004.06.05.13.31.07; author max; state Exp; branches; next ; desc @@ 1.1 log @Add a and b to trunk @ text @@ 1.1.2.1 log @Branch commit of a @ text @@ cvs2svn-2.5.0/test-data/pass5-when-to-fill-cvsrepos/README0000664000175100017510000000034112203665125024142 0ustar mhaggermhagger00000000000000This test checks a case where pass5 generates a fill which isn't actually needed, causing cvs2svn to die when it composes an empty revision in pass8. The bug was originally detected converting the apache 1.3 cvs repository. cvs2svn-2.5.0/test-data/no-revs-file-cvsrepos/0000775000175100017510000000000013206450463022235 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/no-revs-file-cvsrepos/proj/0000775000175100017510000000000013206450463023207 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/no-revs-file-cvsrepos/proj/one-rev.txt,v0000664000175100017510000000031512203665125025563 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2007.11.18.22.28.09; author mhagger; state Exp; branches; next ; commitid cLpur0bMXRgJK6Gs; desc @@ 1.1 log @Add empty file @ text @@ cvs2svn-2.5.0/test-data/no-revs-file-cvsrepos/proj/no-revs.txt,v0000664000175100017510000000010012203665125025571 0ustar mhaggermhagger00000000000000head ; access; symbols; locks; strict; comment @# @; desc @@ cvs2svn-2.5.0/test-data/requires-cvs-cvsrepos/0000775000175100017510000000000013206450463022357 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/requires-cvs-cvsrepos/space-in-authorname,v0000664000175100017510000000064512203665125026410 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.26.23.38.17; author William Lyon Phelps III; state Exp; branches; next 1.1; 1.1 date 2004.07.19.20.57.24; author j random; state Exp; branches; next ; desc @@ 1.2 log @Commit a second revision of every file. @ text @This is the first revision in this file. This line was added in the second revision. @ 1.1 log @Add a file. @ text @d2 1 @ cvs2svn-2.5.0/test-data/requires-cvs-cvsrepos/atsign-add,v0000664000175100017510000000030212203665125024551 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2000.01.01.01.01.01; author someuser; state Exp; branches; next ; desc @@ 1.1 log @Somelogmsg @ text @Sometext /* $Id: */@ cvs2svn-2.5.0/test-data/requires-cvs-cvsrepos/client_lock.idl,v0000664000175100017510000000346712203665125025612 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2001.10.09.07.30.31; author gregh; state Exp; branches; next 1.1; 1.1 date 2001.10.03.00.59.06; author gregh; state Exp; branches; next ; desc @@ 1.2 log @ Integration for locks @ text @//================================================================== -*- C++ -*- // // File client_lock.idl // // Description // Lock for interface objects. // //$Id: client_lock.idl,v 1.1 2001/10/03 00:59:06 gregh Exp $ // //$Log: client_lock.idl,v $ //Revision 1.1 2001/10/03 00:59:06 gregh // //Added graph points to track, and added advisory locks to track, marker //look, sensor, and ownship interfaces. // // //============================================================================== #ifndef _CLIENT_LOCK_IDL_ #define _CLIENT_LOCK_IDL_ #include "tdms.idl" #include "client.idl" #include "exception.idl" module Orb { //============================================================================== interface I_Client_Lock { // Record Locking (used as base interface for all leaf object interfaces) // Note that locks are advisory, so clients need not acquire or honour. void acquire_read_lock(in I_Client objref) raises (Lock_Failed); void acquire_write_lock(in I_Client objref) raises (Lock_Failed); // promotes a read lock void release_lock(in I_Client objref); }; //============================================================================== }; #endif @ 1.1 log @ Added graph points to track, and added advisory locks to track, marker look, sensor, and ownship interfaces. @ text @d8 7 a14 1 //$Id: tdms.idl,v 1.28 2001/09/11 08:10:55 daveb Exp $ a15 1 //$Log: tdms.idl,v $ d24 1 d35 2 a36 2 void acquire_read_lock(in I_Client objref); void acquire_write_lock(in I_Client objref); // this will promote a read lock @ cvs2svn-2.5.0/test-data/requires-cvs-cvsrepos/README0000664000175100017510000000023212203665125023233 0ustar mhaggermhagger00000000000000This repository tests that the --use-cvs flag allows cvs2svn to correctly convert some files that would be troublesome for RCS. See issues 4, 11, and 29. cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/0000775000175100017510000000000013206450463021726 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/proj/0000775000175100017510000000000013206450463022700 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/proj/file.txt,v0000664000175100017510000000035312203665124024621 0ustar mhaggermhagger00000000000000head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2004.07.19.20.57.24; author jrandom; state Exp; branches; next ; desc @@ 1.1 log @Add a file. @ text @This is the only revision in this file. It's made of meat. @ cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/proj/.cvsignore,v0000664000175100017510000000055612203665124025146 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.22.16.03.32; author fitz; state Exp; branches; next 1.1; 1.1 date 2004.07.22.16.02.36; author fitz; state Exp; branches; next ; desc @@ 1.2 log @Add a few more ignore lines, plus a blank line. @ text @*.idx *.aux *.dvi *.log foo bar baz qux @ 1.1 log @initial import @ text @d5 5 @ cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/proj/subdir/0000775000175100017510000000000013206450463024170 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/cvsignore-cvsrepos/proj/subdir/.cvsignore,v0000664000175100017510000000055612203665124026436 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.22.16.03.32; author fitz; state Exp; branches; next 1.1; 1.1 date 2004.07.22.16.02.36; author fitz; state Exp; branches; next ; desc @@ 1.2 log @Add a few more ignore lines, plus a blank line. @ text @*.idx *.aux *.dvi *.log foo bar baz qux @ 1.1 log @initial import @ text @d5 5 @ cvs2svn-2.5.0/test-data/resync-bug-cvsrepos/0000775000175100017510000000000013206450463022005 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-bug-cvsrepos/b,v0000664000175100017510000000062112203665125022411 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2003.07.03.19.35.55; author max; state Exp; branches; next 1.1; 1.1 date 2004.07.03.19.35.06; author max; state Exp; branches; next ; desc @@ 1.2 log @Modify b. This is the commit with the bad timestamp. (Suppose the server's clock was temporarily set for too early.) @ text @modify @ 1.1 log @Modify a. Add b. @ text @d1 1 @ cvs2svn-2.5.0/test-data/resync-bug-cvsrepos/a,v0000664000175100017510000000044312203665125022412 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2004.07.03.19.35.06; author max; state Exp; branches; next 1.1; 1.1 date 2004.07.03.19.33.42; author max; state Exp; branches; next ; desc @@ 1.2 log @Modify a. Add b. @ text @modify @ 1.1 log @Add a. @ text @d1 1 @ cvs2svn-2.5.0/test-data/resync-pass2-push-backward-cvsrepos/0000775000175100017510000000000013206450463025011 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/resync-pass2-push-backward-cvsrepos/file2.txt,v0000664000175100017510000000041712203665125027016 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; 1.2 date 90.04.19.15.10.21; author user1; state Exp; branches; next 1.1; 1.1 date 90.04.19.15.10.21; author user1; state Exp; branches; next ; desc @@ 1.2 log @Initial revision @ text @@ 1.1 log @Summary: foo @ text @@ cvs2svn-2.5.0/test-data/resync-pass2-push-backward-cvsrepos/file1.txt,v0000664000175100017510000000041712203665125027015 0ustar mhaggermhagger00000000000000head 1.2; access; symbols; locks; strict; 1.2 date 90.04.19.15.10.30; author user1; state Exp; branches; next 1.1; 1.1 date 90.04.19.15.10.29; author user1; state Exp; branches; next ; desc @@ 1.2 log @Summary: foo @ text @@ 1.1 log @Initial revision @ text @@ cvs2svn-2.5.0/test-data/vendor-1-1-non-root-cvsrepos/0000775000175100017510000000000013206450463023271 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/test-data/vendor-1-1-non-root-cvsrepos/file001,v0000664000175100017510000000100113206445075024611 0ustar mhaggermhagger00000000000000head 5.1; branch 5.1.0; access; symbols; locks; strict; comment @# @; 5.1 date 2014.01.08.18.04.10; author author1; state Exp; branches 5.1.0.1; next 1.1; 1.1 date 2002.08.23.16.30.15; author author2; state Exp; branches; next ; 5.1.0.1 date 2014.01.08.18.04.10; author author1; state Exp; branches; next ; desc @@ 5.1 log @log 1@ text @This text was last seen in HEAD (revision 5.1) @ 5.1.0.1 log @log 2@ text @@ 1.1 log @Initial revision @ text @d1 1 a1 1 This text was last seen in revision 1.1 @ cvs2svn-2.5.0/setup.py0000775000175100017510000000576512203665123015726 0ustar mhaggermhagger00000000000000#!/usr/bin/env python import sys from distutils.core import setup assert 0x02040000 <= sys.hexversion < 0x03000000, \ "Install Python 2, version 2.4 or greater" def get_version(): "Return the version number of cvs2svn." from cvs2svn_lib.version import VERSION return VERSION setup( # Metadata. name = "cvs2svn", version = get_version(), description = "CVS to Subversion/git/Bazaar/Mercurial repository converter", author = "The cvs2svn team", author_email = "dev@cvs2svn.tigris.org", url = "http://cvs2svn.tigris.org/", download_url = "http://cvs2svn.tigris.org/servlets/ProjectDocumentList?folderID=2976", license = "Apache-style", long_description = """\ cvs2svn_ is a tool for migrating a CVS repository to Subversion_, git_, Bazaar_, or Mercurial_. The main design goals are robustness and 100% data preservation. cvs2svn can convert just about any CVS repository we've ever seen, including gcc, Mozilla, FreeBSD, KDE, GNOME... .. _cvs2svn: http://cvs2svn.tigris.org/ .. _Subversion: http://svn.tigris.org/ .. _git: http://git-scm.com/ .. _Bazaar: http://bazaar-vcs.org/ .. _Mercurial: http://mercurial.selenic.com/ cvs2svn infers what happened in the history of your CVS repository and replicates that history as accurately as possible in the target SCM. All revisions, branches, tags, log messages, author names, and commit dates are converted. cvs2svn deduces what CVS modifications were made at the same time, and outputs these modifications grouped together as changesets in the target SCM. cvs2svn also deals with many CVS quirks and is highly configurable. See the comprehensive `feature list`_. .. _feature list: http://cvs2svn.tigris.org/features.html .. _documentation: http://cvs2svn.tigris.org/cvs2svn.html Please read the documentation_ carefully before using cvs2svn. Latest development version -------------------------- For general use, the most recent released version of cvs2svn is usually the best choice. However, if you want to use the newest cvs2svn features or if you're debugging or patching cvs2svn, you might want to use the trunk version (which is usually quite stable). To do so, use Subversion to check out a working copy from http://cvs2svn.tigris.org/svn/cvs2svn/trunk/ using a command like:: svn co --username=guest --password="" http://cvs2svn.tigris.org/svn/cvs2svn/trunk cvs2svn-trunk """, classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Topic :: Software Development :: Version Control', 'Topic :: Software Development :: Version Control :: CVS', 'Topic :: Utilities', ], # Data. packages = ["cvs2svn_lib", "cvs2svn_rcsparse"], scripts = ["cvs2svn", "cvs2git", "cvs2bzr"], ) cvs2svn-2.5.0/BUGS0000664000175100017510000000441712203665123014665 0ustar mhaggermhagger00000000000000 -*- text -*- REPORTING BUGS ============== This document tells how and where to report bugs in cvs2svn. It is not a list of all outstanding bugs -- we use an online issue tracker for that, see http://cvs2svn.tigris.org/issue_tracker.html Before reporting a bug: a) Verify that you are running the latest version of cvs2svn. b) Read the current frequently-asked-questions list at http://cvs2svn.tigris.org/faq.html to see if your problem has a known solution, and to help determine if your problem is caused by corruption in your CVS repository. c) Check to see if your bug is already filed in the issue tracker (see http://tinyurl.com/2uxwv for a list of all open bugs). Then, mail your bug report to dev@cvs2svn.tigris.org. To be useful, a bug report should include the following information: * The revision of cvs2svn you ran. Run 'cvs2svn --version' to determine this. * The version of Subversion you used it with. Run 'svnadmin --version' to determine this. * The exact cvs2svn command line you invoked, and the output it produced. * The contents of the configuration file that you used (if you used the --config option). * The data you ran it on. If your CVS repository is small (only a few kilobytes), then just provide the repository itself. If it's large, or if the data is confidential, then please try to come up with some smaller, releasable data set that still stimulates the bug. The cvs2svn project includes one script that can often help you narrow down the source of the bug to just a few *,v files, and another that helps strip proprietary information out of your repository. See the FAQ (http://cvs2svn.tigris.org/faq.html) for more information. The most important thing is that we be able to reproduce the bug :-). If we can reproduce it, we can usually fix it. If we can't reproduce it, we'll probably never fix it. So describing the bug conditions accurately is crucial. If in addition to that, you want to add some speculations as to the cause of the bug, or even include a patch to fix it, that's great! Thank you in advance, -The cvs2svn development team cvs2svn-2.5.0/run-tests.py0000775000175100017510000040614113206445076016532 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # # run_tests.py: test suite for cvs2svn # # Usage: run_tests.py [-v | --verbose] [list | ] # # Options: # -v, --verbose # enable verbose output # # Arguments (at most one argument is allowed): # list # If the word "list" is passed as an argument, the list of # available tests is printed (but no tests are run). # # # If a number is passed as an argument, then only the test # with that number is run. # # If no argument is specified, then all tests are run. # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Copyright (c) 2000-2009 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # ###################################################################### # General modules import sys import shutil import stat import re import os import time import os.path import locale import textwrap import calendar import types try: from hashlib import md5 except ImportError: from md5 import md5 from difflib import Differ # Make sure that a supported version of Python is being used: if not (0x02040000 <= sys.hexversion < 0x03000000): sys.stderr.write( 'error: Python 2, version 2.4 or higher required.\n' ) sys.exit(1) # This script needs to run in the correct directory. Make sure we're there. if not (os.path.exists('cvs2svn') and os.path.exists('test-data')): sys.stderr.write("error: I need to be run in the directory containing " "'cvs2svn' and 'test-data'.\n") sys.exit(1) # Load the Subversion test framework. import svntest from svntest import Failure from svntest.main import safe_rmtree from svntest.testcase import TestCase from svntest.testcase import XFail_deco # Test if Mercurial >= 1.1 is available. try: from mercurial import context context.memctx have_hg = True except (ImportError, AttributeError): have_hg = False cvs2svn = os.path.abspath('cvs2svn') cvs2git = os.path.abspath('cvs2git') cvs2hg = os.path.abspath('cvs2hg') # We use the installed svn and svnlook binaries, instead of using # svntest.main.run_svn() and svntest.main.run_svnlook(), because the # behavior -- or even existence -- of local builds shouldn't affect # the cvs2svn test suite. svn_binary = 'svn' svnlook_binary = 'svnlook' svnadmin_binary = 'svnadmin' svnversion_binary = 'svnversion' test_data_dir = 'test-data' tmp_dir = 'cvs2svn-tmp' #---------------------------------------------------------------------- # Helpers. #---------------------------------------------------------------------- # The value to expect for svn:keywords if it is set: KEYWORDS = 'Author Date Id Revision' class RunProgramException(Failure): pass class MissingErrorException(Failure): def __init__(self, error_re): Failure.__init__( self, "Test failed because no error matched '%s'" % (error_re,) ) def run_program(program, error_re, *varargs): """Run PROGRAM with VARARGS, return stdout as a list of lines. If there is any stderr and ERROR_RE is None, raise RunProgramException, and log the stderr lines via svntest.main.logger.info(). If ERROR_RE is not None, it is a string regular expression that must match some line of stderr. If it fails to match, raise MissingErrorExpection.""" # FIXME: exit_code is currently ignored. exit_code, out, err = svntest.main.run_command(program, 1, 0, *varargs) if error_re: # Specified error expected on stderr. if not err: raise MissingErrorException(error_re) else: for line in err: if re.match(error_re, line): return out raise MissingErrorException(error_re) else: # No stderr allowed. if err: log = svntest.main.logger.info log('%s said:' % program) for line in err: log(' ' + line.rstrip()) raise RunProgramException() return out def run_script(script, error_re, *varargs): """Run Python script SCRIPT with VARARGS, returning stdout as a list of lines. If there is any stderr and ERROR_RE is None, raise RunProgramException, and log the stderr lines via svntest.main.logger.info(). If ERROR_RE is not None, it is a string regular expression that must match some line of stderr. If it fails to match, raise MissingErrorException.""" # Use the same python that is running this script return run_program(sys.executable, error_re, script, *varargs) # On Windows, for an unknown reason, the cmd.exe process invoked by # os.system('sort ...') in cvs2svn receives invalid stdio handles, if # cvs2svn is started as "cvs2svn ...". "python cvs2svn ..." avoids # this. Therefore, the redirection of the output to the .s-revs file fails. # We no longer use the problematic invocation on any system, but this # comment remains to warn about this problem. def run_svn(*varargs): """Run svn with VARARGS; return stdout as a list of lines. If there is any stderr, raise RunProgramException, and log the stderr lines via svntest.main.logger.info().""" return run_program(svn_binary, None, *varargs) def repos_to_url(path_to_svn_repos): """This does what you think it does.""" rpath = os.path.abspath(path_to_svn_repos) if rpath[0] != '/': rpath = '/' + rpath return 'file://%s' % rpath.replace(os.sep, '/') def svn_strptime(timestr): return time.strptime(timestr, '%Y-%m-%d %H:%M:%S') class Log: def __init__(self, revision, author, date, symbols): self.revision = revision self.author = author # Internally, we represent the date as seconds since epoch (UTC). # Since standard subversion log output shows dates in localtime # # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)" # # and time.mktime() converts from localtime, it all works out very # happily. self.date = time.mktime(svn_strptime(date[0:19])) # The following symbols are used for string interpolation when # checking paths: self.symbols = symbols # The changed paths will be accumulated later, as log data is read. # Keys here are paths such as '/trunk/foo/bar', values are letter # codes such as 'M', 'A', and 'D'. self.changed_paths = { } # The msg will be accumulated later, as log data is read. self.msg = '' def absorb_changed_paths(self, out): 'Read changed paths from OUT into self, until no more.' while True: line = out.readline() if len(line) == 1: return line = line[:-1] op_portion = line[3:4] path_portion = line[5:] # If we're running on Windows we get backslashes instead of # forward slashes. path_portion = path_portion.replace('\\', '/') # # We could parse out history information, but currently we # # just leave it in the path portion because that's how some # # tests expect it. # # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion) # if m: # path_portion = m.group(1) self.changed_paths[path_portion] = op_portion def __cmp__(self, other): return cmp(self.revision, other.revision) or \ cmp(self.author, other.author) or cmp(self.date, other.date) or \ cmp(self.changed_paths, other.changed_paths) or \ cmp(self.msg, other.msg) def get_path_op(self, path): """Return the operator for the change involving PATH. PATH is allowed to include string interpolation directives (e.g., '%(trunk)s'), which are interpolated against self.symbols. Return None if there is no record for PATH.""" return self.changed_paths.get(path % self.symbols) def check_msg(self, msg): """Verify that this Log's message starts with the specified MSG.""" if self.msg.find(msg) != 0: raise Failure( "Revision %d log message was:\n%s\n\n" "It should have begun with:\n%s\n\n" % (self.revision, self.msg, msg,) ) def check_change(self, path, op): """Verify that this Log includes a change for PATH with operator OP. PATH is allowed to include string interpolation directives (e.g., '%(trunk)s'), which are interpolated against self.symbols.""" path = path % self.symbols found_op = self.changed_paths.get(path, None) if found_op is None: raise Failure( "Revision %d does not include change for path %s " "(it should have been %s).\n" % (self.revision, path, op,) ) if found_op != op: raise Failure( "Revision %d path %s had op %s (it should have been %s)\n" % (self.revision, path, found_op, op,) ) def check_changes(self, changed_paths): """Verify that this Log has precisely the CHANGED_PATHS specified. CHANGED_PATHS is a sequence of tuples (path, op), where the paths strings are allowed to include string interpolation directives (e.g., '%(trunk)s'), which are interpolated against self.symbols.""" cp = {} for (path, op) in changed_paths: cp[path % self.symbols] = op if self.changed_paths != cp: raise Failure( "Revision %d changed paths list was:\n%s\n\n" "It should have been:\n%s\n\n" % (self.revision, self.changed_paths, cp,) ) def check(self, msg, changed_paths): """Verify that this Log has the MSG and CHANGED_PATHS specified. Convenience function to check two things at once. MSG is passed to check_msg(); CHANGED_PATHS is passed to check_changes().""" self.check_msg(msg) self.check_changes(changed_paths) def parse_log(svn_repos, symbols): """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS. Initialize the Logs' symbols with SYMBOLS.""" class LineFeeder: 'Make a list of lines behave like an open file handle.' def __init__(self, lines): self.lines = list(reversed(lines)) def readline(self): if len(self.lines) > 0: return self.lines.pop() else: return None def absorb_message_body(out, num_lines, log): """Read NUM_LINES of log message body from OUT into Log item LOG.""" for i in range(num_lines): log.msg += out.readline() log_start_re = re.compile('^r(?P[0-9]+) \| ' '(?P[^\|]+) \| ' '(?P[^\|]+) ' '\| (?P[0-9]+) (line|lines)$') log_separator = '-' * 72 logs = { } out = LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos))) while True: this_log = None line = out.readline() if not line: break line = line[:-1] if line.find(log_separator) == 0: line = out.readline() if not line: break line = line[:-1] m = log_start_re.match(line) if m: this_log = Log( int(m.group('rev')), m.group('author'), m.group('date'), symbols) line = out.readline() if line == '\n': # No changed paths pass elif line.startswith('Changed paths:'): this_log.absorb_changed_paths(out) else: print 'unexpected log output' print "Line: '%s'" % line sys.exit(1) absorb_message_body(out, int(m.group('lines')), this_log) logs[this_log.revision] = this_log elif len(line) == 0: break # We've reached the end of the log output. else: print 'unexpected log output (missing revision line)' print "Line: '%s'" % line sys.exit(1) else: print 'unexpected log output (missing log separator)' print "Line: '%s'" % line sys.exit(1) return logs def erase(path): """Unconditionally remove PATH and its subtree, if any. PATH may be non-existent, a file or symlink, or a directory.""" if os.path.isdir(path): safe_rmtree(path) elif os.path.exists(path): os.remove(path) log_msg_text_wrapper = textwrap.TextWrapper(width=76, break_long_words=False) def sym_log_msg(symbolic_name, is_tag=None): """Return the expected log message for a cvs2svn-synthesized revision creating branch or tag SYMBOLIC_NAME.""" # This reproduces the logic in SVNSymbolCommit.get_log_msg(). if is_tag: type = 'tag' else: type = 'branch' return log_msg_text_wrapper.fill( "This commit was manufactured by cvs2svn to create %s '%s'." % (type, symbolic_name) ) def make_conversion_id( name, args, passbypass, options_file=None, symbol_hints_file=None ): """Create an identifying tag for a conversion. The return value can also be used as part of a filesystem path. NAME is the name of the CVS repository. ARGS are the extra arguments to be passed to cvs2svn. PASSBYPASS is a boolean indicating whether the conversion is to be run one pass at a time. If OPTIONS_FILE is specified, it is an options file that will be used for the conversion. If SYMBOL_HINTS_FILE is specified, it is a symbol hints file that will be used for the conversion. The 1-to-1 mapping between cvs2svn command parameters and conversion_ids allows us to avoid running the same conversion more than once, when multiple tests use exactly the same conversion.""" conv_id = name args = args[:] if passbypass: args.append('--passbypass') if symbol_hints_file is not None: args.append('--symbol-hints=%s' % (symbol_hints_file,)) # There are some characters that are forbidden in filenames, and # there is a limit on the total length of a path to a file. So use # a hash of the parameters rather than concatenating the parameters # into a string. if args: conv_id += "-" + md5('\0'.join(args)).hexdigest() # Some options-file based tests rely on knowing the paths to which # the repository should be written, so we handle that option as a # predictable string: if options_file is not None: conv_id += '--options=%s' % (options_file,) return conv_id class Conversion: """A record of a cvs2svn conversion. Fields: conv_id -- the conversion id for this Conversion. name -- a one-word name indicating the involved repositories. dumpfile -- the name of the SVN dumpfile created by the conversion (if the DUMPFILE constructor argument was used); otherwise, None. repos -- the path to the svn repository. Unset if DUMPFILE was specified. logs -- a dictionary of Log instances, as returned by parse_log(). Unset if DUMPFILE was specified. symbols -- a dictionary of symbols used for string interpolation in path names. stdout -- a list of lines written by cvs2svn to stdout _wc -- the basename of the svn working copy (within tmp_dir). Unset if DUMPFILE was specified. _wc_path -- the path to the svn working copy, if it has already been created; otherwise, None. (The working copy is created lazily when get_wc() is called.) Unset if DUMPFILE was specified. _wc_tree -- the tree built from the svn working copy, if it has already been created; otherwise, None. The tree is created lazily when get_wc_tree() is called.) Unset if DUMPFILE was specified. _svnrepos -- the basename of the svn repository (within tmp_dir). Unset if DUMPFILE was specified.""" # The number of the last cvs2svn pass (determined lazily by # get_last_pass()). last_pass = None @classmethod def get_last_pass(cls): """Return the number of cvs2svn's last pass.""" if cls.last_pass is None: out = run_script(cvs2svn, None, '--help-passes') cls.last_pass = int(out[-1].split()[0]) return cls.last_pass def __init__( self, conv_id, name, error_re, passbypass, symbols, args, verbosity=None, options_file=None, symbol_hints_file=None, dumpfile=None, ): self.conv_id = conv_id self.name = name self.symbols = symbols if not os.path.isdir(tmp_dir): os.mkdir(tmp_dir) cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name) if dumpfile: self.dumpfile = os.path.join(tmp_dir, dumpfile) # Clean up from any previous invocations of this script. erase(self.dumpfile) else: self.dumpfile = None self.repos = os.path.join(tmp_dir, '%s-svnrepos' % self.conv_id) self._wc = os.path.join(tmp_dir, '%s-wc' % self.conv_id) self._wc_path = None self._wc_tree = None # Clean up from any previous invocations of this script. erase(self.repos) erase(self._wc) args = list(args) if svntest.main.svnadmin_binary != 'svnadmin': args.extend([ '--svnadmin=%s' % (svntest.main.svnadmin_binary,), ]) if options_file: self.options_file = os.path.join(cvsrepos, options_file) args.extend([ '--options=%s' % self.options_file, ]) args.append(verbosity or '-qqqqqq') assert not symbol_hints_file else: self.options_file = None args.extend([ '--tmpdir=%s' % tmp_dir, ]) args.append(verbosity or '-qqqqqq') if symbol_hints_file: self.symbol_hints_file = os.path.join(cvsrepos, symbol_hints_file) args.extend([ '--symbol-hints=%s' % self.symbol_hints_file, ]) if self.dumpfile: args.extend(['--dumpfile=%s' % (self.dumpfile,)]) else: args.extend(['-s', self.repos]) args.extend([cvsrepos]) if passbypass: self.stdout = [] for p in range(1, self.get_last_pass() + 1): self.stdout += run_script(cvs2svn, error_re, '-p', str(p), *args) else: self.stdout = run_script(cvs2svn, error_re, *args) if self.dumpfile: if not os.path.isfile(self.dumpfile): raise Failure( "Dumpfile not created: '%s'" % os.path.join(os.getcwd(), self.dumpfile) ) else: if os.path.isdir(self.repos): self.logs = parse_log(self.repos, self.symbols) elif error_re is None: raise Failure( "Repository not created: '%s'" % os.path.join(os.getcwd(), self.repos) ) def output_found(self, pattern): """Return True if PATTERN matches any line in self.stdout. PATTERN is a regular expression pattern as a string. """ pattern_re = re.compile(pattern) for line in self.stdout: if pattern_re.match(line): # We found the pattern that we were looking for. return True else: return False def find_tag_log(self, tagname): """Search LOGS for a log message containing 'TAGNAME' and return the log in which it was found.""" for i in xrange(len(self.logs), 0, -1): if self.logs[i].msg.find("'"+tagname+"'") != -1: return self.logs[i] raise ValueError("Tag %s not found in logs" % tagname) def get_wc(self, *args): """Return the path to the svn working copy, or a path within the WC. If a working copy has not been created yet, create it now. If ARGS are specified, then they should be strings that form fragments of a path within the WC. They are joined using os.path.join() and appended to the WC path.""" if self._wc_path is None: run_svn('co', repos_to_url(self.repos), self._wc) self._wc_path = self._wc return os.path.join(self._wc_path, *args) def get_wc_tree(self): if self._wc_tree is None: self._wc_tree = svntest.tree.build_tree_from_wc(self.get_wc(), 1) return self._wc_tree def path_exists(self, *args): """Return True if the specified path exists within the repository. (The strings in ARGS are first joined into a path using os.path.join().)""" return os.path.exists(self.get_wc(*args)) def check_props(self, keys, checks): """Helper function for checking lots of properties. For a list of files in the conversion, check that the values of the properties listed in KEYS agree with those listed in CHECKS. CHECKS is a list of tuples: [ (filename, [value, value, ...]), ...], where the values are listed in the same order as the key names are listed in KEYS.""" for (file, values) in checks: assert len(values) == len(keys) props = props_for_path(self.get_wc_tree(), file) for i in range(len(keys)): if props.get(keys[i]) != values[i]: raise Failure( "File %s has property %s set to \"%s\" " "(it should have been \"%s\").\n" % (file, keys[i], props.get(keys[i]), values[i],) ) class GitConversion: """A record of a cvs2svn conversion. Fields: name -- a one-word name indicating the CVS repository to be converted. stdout -- a list of lines written by cvs2svn to stdout.""" def __init__(self, name, error_re, args, verbosity=None, options_file=None): self.name = name if not os.path.isdir(tmp_dir): os.mkdir(tmp_dir) cvsrepos = os.path.join(test_data_dir, '%s-cvsrepos' % self.name) args = list(args) if options_file: self.options_file = os.path.join(cvsrepos, options_file) args.extend([ '--options=%s' % self.options_file, ]) else: self.options_file = None args.append(verbosity or '-qqqqqq') self.stdout = run_script(cvs2git, error_re, *args) # Cache of conversions that have already been done. Keys are conv_id; # values are Conversion instances. already_converted = { } def ensure_conversion( name, error_re=None, passbypass=None, trunk=None, branches=None, tags=None, args=None, verbosity=None, options_file=None, symbol_hints_file=None, dumpfile=None, ): """Convert CVS repository NAME to Subversion, but only if it has not been converted before by this invocation of this script. If it has been converted before, return the Conversion object from the previous invocation. If no error, return a Conversion instance. If ERROR_RE is a string, it is a regular expression expected to match some line of stderr printed by the conversion. If there is an error and ERROR_RE is not set, then raise Failure. If PASSBYPASS is set, then cvs2svn is run multiple times, each time with a -p option starting at 1 and increasing to a (hardcoded) maximum. NAME is just one word. For example, 'main' would mean to convert './test-data/main-cvsrepos', and after the conversion, the resulting Subversion repository would be in './cvs2svn-tmp/main-svnrepos', and a checked out head working copy in './cvs2svn-tmp/main-wc'. Any other options to pass to cvs2svn should be in ARGS, each element being one option, e.g., '--trunk-only'. If the option takes an argument, include it directly, e.g., '--mime-types=PATH'. Arguments are passed to cvs2svn in the order that they appear in ARGS. If VERBOSITY is set, then it is passed to cvs2svn as an option. Otherwise, the verbosity is turned way down so that only error messages are emitted. If OPTIONS_FILE is specified, then it should be the name of a file within the main directory of the cvs repository associated with this test. It is passed to cvs2svn using the --options option (which suppresses some other options that are incompatible with --options). If SYMBOL_HINTS_FILE is specified, then it should be the name of a file within the main directory of the cvs repository associated with this test. It is passed to cvs2svn using the --symbol-hints option. If DUMPFILE is specified, then it is the name of a dumpfile within the temporary directory to which the conversion output should be written.""" if args is None: args = [] else: args = list(args) if trunk is None: trunk = 'trunk' else: args.append('--trunk=%s' % (trunk,)) if branches is None: branches = 'branches' else: args.append('--branches=%s' % (branches,)) if tags is None: tags = 'tags' else: args.append('--tags=%s' % (tags,)) conv_id = make_conversion_id( name, args, passbypass, options_file, symbol_hints_file ) if conv_id not in already_converted: try: # Run the conversion and store the result for the rest of this # session: already_converted[conv_id] = Conversion( conv_id, name, error_re, passbypass, {'trunk' : trunk, 'branches' : branches, 'tags' : tags}, args, verbosity, options_file, symbol_hints_file, dumpfile, ) except Failure: # Remember the failure so that a future attempt to run this conversion # does not bother to retry, but fails immediately. already_converted[conv_id] = None raise conv = already_converted[conv_id] if conv is None: raise Failure() return conv class Cvs2SvnTestFunction(TestCase): """A TestCase based on a naked Python function object. FUNC should be a function that returns None on success and throws an svntest.Failure exception on failure. It should have a brief docstring describing what it does (and fulfilling certain conditions). FUNC must take no arguments. This class is almost identical to svntest.testcase.FunctionTestCase, except that the test function does not require a sandbox and does not accept any parameter (not even sandbox=None). This class can be used as an annotation on a Python function. """ def __init__(self, func): # it better be a function that accepts no parameters and has a # docstring on it. assert isinstance(func, types.FunctionType) name = func.func_name assert func.func_code.co_argcount == 0, \ '%s must not take any arguments' % name doc = func.__doc__.strip() assert doc, '%s must have a docstring' % name # enforce stylistic guidelines for the function docstrings: # - no longer than 50 characters # - should not end in a period # - should not be capitalized assert len(doc) <= 50, \ "%s's docstring must be 50 characters or less" % name assert doc[-1] != '.', \ "%s's docstring should not end in a period" % name assert doc[0].lower() == doc[0], \ "%s's docstring should not be capitalized" % name TestCase.__init__(self, doc=doc) self.func = func def get_function_name(self): return self.func.func_name def get_sandbox_name(self): return None def run(self, sandbox): return self.func() class Cvs2HgTestFunction(Cvs2SvnTestFunction): """Same as Cvs2SvnTestFunction, but for test cases that should be skipped if Mercurial is not available. """ def run(self, sandbox): if not have_hg: raise svntest.Skip() else: return self.func() class Cvs2SvnTestCase(TestCase): def __init__( self, name, doc=None, variant=None, error_re=None, passbypass=None, trunk=None, branches=None, tags=None, args=None, options_file=None, symbol_hints_file=None, dumpfile=None, ): self.name = name if doc is None: # By default, use the first line of the class docstring as the # doc: doc = self.__doc__.splitlines()[0] if variant is not None: # Modify doc to show the variant. Trim doc first if necessary # to stay within the 50-character limit. suffix = '...variant %s' % (variant,) doc = doc[:50 - len(suffix)] + suffix TestCase.__init__(self, doc=doc) self.error_re = error_re self.passbypass = passbypass self.trunk = trunk self.branches = branches self.tags = tags self.args = args self.options_file = options_file self.symbol_hints_file = symbol_hints_file self.dumpfile = dumpfile def ensure_conversion(self): return ensure_conversion( self.name, error_re=self.error_re, passbypass=self.passbypass, trunk=self.trunk, branches=self.branches, tags=self.tags, args=self.args, options_file=self.options_file, symbol_hints_file=self.symbol_hints_file, dumpfile=self.dumpfile, ) def get_sandbox_name(self): return None class Cvs2SvnPropertiesTestCase(Cvs2SvnTestCase): """Test properties resulting from a conversion.""" def __init__(self, name, props_to_test, expected_props, **kw): """Initialize an instance of Cvs2SvnPropertiesTestCase. NAME is the name of the test, passed to Cvs2SvnTestCase. PROPS_TO_TEST is a list of the names of svn properties that should be tested. EXPECTED_PROPS is a list of tuples [(filename, [value,...])], where the second item in each tuple is a list of values expected for the properties listed in PROPS_TO_TEST for the specified filename. If a property must *not* be set, then its value should be listed as None.""" Cvs2SvnTestCase.__init__(self, name, **kw) self.props_to_test = props_to_test self.expected_props = expected_props def run(self, sbox): conv = self.ensure_conversion() conv.check_props(self.props_to_test, self.expected_props) #---------------------------------------------------------------------- # Tests. #---------------------------------------------------------------------- @Cvs2SvnTestFunction def show_usage(): "cvs2svn with no arguments shows usage" out = run_script(cvs2svn, None) if (len(out) > 2 and out[0].find('ERROR:') == 0 and out[1].find('DBM module')): print 'cvs2svn cannot execute due to lack of proper DBM module.' print 'Exiting without running any further tests.' sys.exit(1) if out[0].find('Usage:') < 0: raise Failure('Basic cvs2svn invocation failed.') @Cvs2SvnTestFunction def cvs2svn_manpage(): "generate a manpage for cvs2svn" out = run_script(cvs2svn, None, '--man') @Cvs2SvnTestFunction def cvs2git_manpage(): "generate a manpage for cvs2git" out = run_script(cvs2git, None, '--man') @XFail_deco() @Cvs2HgTestFunction def cvs2hg_manpage(): "generate a manpage for cvs2hg" out = run_script(cvs2hg, None, '--man') @Cvs2SvnTestFunction def show_help_passes(): "cvs2svn --help-passes shows pass information" out = run_script(cvs2svn, None, '--help-passes') if out[0].find('PASSES') < 0: raise Failure('cvs2svn --help-passes failed.') @Cvs2SvnTestFunction def attr_exec(): "detection of the executable flag" if sys.platform == 'win32': raise svntest.Skip() st = os.stat(os.path.join('test-data', 'main-cvsrepos', 'single-files', 'attr-exec,v')) if not st.st_mode & stat.S_IXUSR: # This might be the case if the test is being run on a filesystem # that is mounted "noexec". raise svntest.Skip() conv = ensure_conversion('main') st = os.stat(conv.get_wc('trunk', 'single-files', 'attr-exec')) if not st.st_mode & stat.S_IXUSR: raise Failure() @Cvs2SvnTestFunction def space_fname(): "conversion of filename with a space" conv = ensure_conversion('main') if not conv.path_exists('trunk', 'single-files', 'space fname'): raise Failure() @Cvs2SvnTestFunction def two_quick(): "two commits in quick succession" conv = ensure_conversion('main') logs = parse_log( os.path.join(conv.repos, 'trunk', 'single-files', 'twoquick'), {}) if len(logs) != 2: raise Failure() class PruneWithCare(Cvs2SvnTestCase): "prune, but never too much" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'main', **kw) def run(self, sbox): # Robert Pluim encountered this lovely one while converting the # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS # repository (see issue #1302). Step 4 is the doozy: # # revision 1: adds trunk/blah/, adds trunk/blah/first # revision 2: adds trunk/blah/second # revision 3: deletes trunk/blah/first # revision 4: deletes blah [re-deleting trunk/blah/first pruned blah!] # revision 5: does nothing # # After fixing cvs2svn, the sequence (correctly) looks like this: # # revision 1: adds trunk/blah/, adds trunk/blah/first # revision 2: adds trunk/blah/second # revision 3: deletes trunk/blah/first # revision 4: does nothing [because trunk/blah/first already deleted] # revision 5: deletes blah # # The difference is in 4 and 5. In revision 4, it's not correct # to prune blah/, because second is still in there, so revision 4 # does nothing now. But when we delete second in 5, that should # bubble up and prune blah/ instead. # # ### Note that empty revisions like 4 are probably going to become # ### at least optional, if not banished entirely from cvs2svn's # ### output. Hmmm, or they may stick around, with an extra # ### revision property explaining what happened. Need to think # ### about that. In some sense, it's a bug in Subversion itself, # ### that such revisions don't show up in 'svn log' output. conv = self.ensure_conversion() # Confirm that revision 4 removes '/trunk/full-prune/first', # and that revision 6 removes '/trunk/full-prune'. # # Also confirm similar things about '/full-prune-reappear/...', # which is similar, except that later on it reappears, restored # from pruneland, because a file gets added to it. # # And finally, a similar thing for '/partial-prune/...', except that # in its case, a permanent file on the top level prevents the # pruning from going farther than the subdirectory containing first # and second. for path in ('full-prune/first', 'full-prune-reappear/sub/first', 'partial-prune/sub/first'): conv.logs[5].check_change('/%(trunk)s/' + path, 'D') for path in ('full-prune', 'full-prune-reappear', 'partial-prune/sub'): conv.logs[7].check_change('/%(trunk)s/' + path, 'D') for path in ('full-prune-reappear', 'full-prune-reappear/appears-later'): conv.logs[33].check_change('/%(trunk)s/' + path, 'A') @Cvs2SvnTestFunction def interleaved_commits(): "two interleaved trunk commits, different log msgs" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') # The initial import. rev = 26 conv.logs[rev].check('Initial import.', ( ('/%(trunk)s/interleaved', 'A'), ('/%(trunk)s/interleaved/1', 'A'), ('/%(trunk)s/interleaved/2', 'A'), ('/%(trunk)s/interleaved/3', 'A'), ('/%(trunk)s/interleaved/4', 'A'), ('/%(trunk)s/interleaved/5', 'A'), ('/%(trunk)s/interleaved/a', 'A'), ('/%(trunk)s/interleaved/b', 'A'), ('/%(trunk)s/interleaved/c', 'A'), ('/%(trunk)s/interleaved/d', 'A'), ('/%(trunk)s/interleaved/e', 'A'), )) def check_letters(rev): """Check if REV is the rev where only letters were committed.""" conv.logs[rev].check('Committing letters only.', ( ('/%(trunk)s/interleaved/a', 'M'), ('/%(trunk)s/interleaved/b', 'M'), ('/%(trunk)s/interleaved/c', 'M'), ('/%(trunk)s/interleaved/d', 'M'), ('/%(trunk)s/interleaved/e', 'M'), )) def check_numbers(rev): """Check if REV is the rev where only numbers were committed.""" conv.logs[rev].check('Committing numbers only.', ( ('/%(trunk)s/interleaved/1', 'M'), ('/%(trunk)s/interleaved/2', 'M'), ('/%(trunk)s/interleaved/3', 'M'), ('/%(trunk)s/interleaved/4', 'M'), ('/%(trunk)s/interleaved/5', 'M'), )) # One of the commits was letters only, the other was numbers only. # But they happened "simultaneously", so we don't assume anything # about which commit appeared first, so we just try both ways. rev += 1 try: check_letters(rev) check_numbers(rev + 1) except Failure: check_numbers(rev) check_letters(rev + 1) @Cvs2SvnTestFunction def simple_commits(): "simple trunk commits" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') # The initial import. conv.logs[13].check('Initial import.', ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/default', 'A'), ('/%(trunk)s/proj/sub1', 'A'), ('/%(trunk)s/proj/sub1/default', 'A'), ('/%(trunk)s/proj/sub1/subsubA', 'A'), ('/%(trunk)s/proj/sub1/subsubA/default', 'A'), ('/%(trunk)s/proj/sub1/subsubB', 'A'), ('/%(trunk)s/proj/sub1/subsubB/default', 'A'), ('/%(trunk)s/proj/sub2', 'A'), ('/%(trunk)s/proj/sub2/default', 'A'), ('/%(trunk)s/proj/sub2/subsubA', 'A'), ('/%(trunk)s/proj/sub2/subsubA/default', 'A'), ('/%(trunk)s/proj/sub3', 'A'), ('/%(trunk)s/proj/sub3/default', 'A'), )) # The first commit. conv.logs[18].check('First commit to proj, affecting two files.', ( ('/%(trunk)s/proj/sub1/subsubA/default', 'M'), ('/%(trunk)s/proj/sub3/default', 'M'), )) # The second commit. conv.logs[19].check('Second commit to proj, affecting all 7 files.', ( ('/%(trunk)s/proj/default', 'M'), ('/%(trunk)s/proj/sub1/default', 'M'), ('/%(trunk)s/proj/sub1/subsubA/default', 'M'), ('/%(trunk)s/proj/sub1/subsubB/default', 'M'), ('/%(trunk)s/proj/sub2/default', 'M'), ('/%(trunk)s/proj/sub2/subsubA/default', 'M'), ('/%(trunk)s/proj/sub3/default', 'M') )) class SimpleTags(Cvs2SvnTestCase): "simple tags and branches, no commits" def __init__(self, **kw): # See test-data/main-cvsrepos/proj/README. Cvs2SvnTestCase.__init__(self, 'main', **kw) def run(self, sbox): conv = self.ensure_conversion() # Verify the copy source for the tags we are about to check # No need to verify the copyfrom revision, as simple_commits did that conv.logs[13].check('Initial import.', ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/default', 'A'), ('/%(trunk)s/proj/sub1', 'A'), ('/%(trunk)s/proj/sub1/default', 'A'), ('/%(trunk)s/proj/sub1/subsubA', 'A'), ('/%(trunk)s/proj/sub1/subsubA/default', 'A'), ('/%(trunk)s/proj/sub1/subsubB', 'A'), ('/%(trunk)s/proj/sub1/subsubB/default', 'A'), ('/%(trunk)s/proj/sub2', 'A'), ('/%(trunk)s/proj/sub2/default', 'A'), ('/%(trunk)s/proj/sub2/subsubA', 'A'), ('/%(trunk)s/proj/sub2/subsubA/default', 'A'), ('/%(trunk)s/proj/sub3', 'A'), ('/%(trunk)s/proj/sub3/default', 'A'), )) # Tag on rev 1.1.1.1 of all files in proj conv.logs[16].check(sym_log_msg('B_FROM_INITIALS'), ( ('/%(branches)s/B_FROM_INITIALS (from /%(trunk)s:13)', 'A'), ('/%(branches)s/B_FROM_INITIALS/single-files', 'D'), ('/%(branches)s/B_FROM_INITIALS/partial-prune', 'D'), )) # The same, as a tag log = conv.find_tag_log('T_ALL_INITIAL_FILES') log.check(sym_log_msg('T_ALL_INITIAL_FILES',1), ( ('/%(tags)s/T_ALL_INITIAL_FILES (from /%(trunk)s:13)', 'A'), ('/%(tags)s/T_ALL_INITIAL_FILES/single-files', 'D'), ('/%(tags)s/T_ALL_INITIAL_FILES/partial-prune', 'D'), )) # Tag on rev 1.1.1.1 of all files in proj, except one log = conv.find_tag_log('T_ALL_INITIAL_FILES_BUT_ONE') log.check(sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), ( ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE (from /%(trunk)s:13)', 'A'), ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/single-files', 'D'), ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune', 'D'), ('/%(tags)s/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB', 'D'), )) # The same, as a branch conv.logs[17].check(sym_log_msg('B_FROM_INITIALS_BUT_ONE'), ( ('/%(branches)s/B_FROM_INITIALS_BUT_ONE (from /%(trunk)s:13)', 'A'), ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB', 'D'), ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/single-files', 'D'), ('/%(branches)s/B_FROM_INITIALS_BUT_ONE/partial-prune', 'D'), )) @Cvs2SvnTestFunction def simple_branch_commits(): "simple branch commits" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') conv.logs[23].check('Modify three files, on branch B_MIXED.', ( ('/%(branches)s/B_MIXED/proj/default', 'M'), ('/%(branches)s/B_MIXED/proj/sub1/default', 'M'), ('/%(branches)s/B_MIXED/proj/sub2/subsubA/default', 'M'), )) @Cvs2SvnTestFunction def mixed_time_tag(): "mixed-time tag" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') log = conv.find_tag_log('T_MIXED') log.check_changes(( ('/%(tags)s/T_MIXED (from /%(trunk)s:19)', 'A'), ('/%(tags)s/T_MIXED/single-files', 'D'), ('/%(tags)s/T_MIXED/partial-prune', 'D'), ('/%(tags)s/T_MIXED/proj/sub2/subsubA ' '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'), ('/%(tags)s/T_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'), )) @Cvs2SvnTestFunction def mixed_time_branch_with_added_file(): "mixed-time branch, and a file added to the branch" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') # A branch from the same place as T_MIXED in the previous test, # plus a file added directly to the branch conv.logs[21].check(sym_log_msg('B_MIXED'), ( ('/%(branches)s/B_MIXED (from /%(trunk)s:19)', 'A'), ('/%(branches)s/B_MIXED/partial-prune', 'D'), ('/%(branches)s/B_MIXED/single-files', 'D'), ('/%(branches)s/B_MIXED/proj/sub2/subsubA ' '(from /%(trunk)s/proj/sub2/subsubA:13)', 'R'), ('/%(branches)s/B_MIXED/proj/sub3 (from /%(trunk)s/proj/sub3:18)', 'R'), )) conv.logs[22].check('Add a file on branch B_MIXED.', ( ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'A'), )) @Cvs2SvnTestFunction def mixed_commit(): "a commit affecting both trunk and a branch" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') conv.logs[24].check( 'A single commit affecting one file on branch B_MIXED ' 'and one on trunk.', ( ('/%(trunk)s/proj/sub2/default', 'M'), ('/%(branches)s/B_MIXED/proj/sub2/branch_B_MIXED_only', 'M'), )) @Cvs2SvnTestFunction def split_time_branch(): "branch some trunk files, and later branch the rest" # See test-data/main-cvsrepos/proj/README. conv = ensure_conversion('main') # First change on the branch, creating it conv.logs[25].check(sym_log_msg('B_SPLIT'), ( ('/%(branches)s/B_SPLIT (from /%(trunk)s:24)', 'A'), ('/%(branches)s/B_SPLIT/partial-prune', 'D'), ('/%(branches)s/B_SPLIT/single-files', 'D'), ('/%(branches)s/B_SPLIT/proj/sub1/subsubB', 'D'), )) conv.logs[29].check('First change on branch B_SPLIT.', ( ('/%(branches)s/B_SPLIT/proj/default', 'M'), ('/%(branches)s/B_SPLIT/proj/sub1/default', 'M'), ('/%(branches)s/B_SPLIT/proj/sub1/subsubA/default', 'M'), ('/%(branches)s/B_SPLIT/proj/sub2/default', 'M'), ('/%(branches)s/B_SPLIT/proj/sub2/subsubA/default', 'M'), )) # A trunk commit for the file which was not branched conv.logs[30].check('A trunk change to sub1/subsubB/default. ' 'This was committed about an', ( ('/%(trunk)s/proj/sub1/subsubB/default', 'M'), )) # Add the file not already branched to the branch, with modification:w conv.logs[31].check(sym_log_msg('B_SPLIT'), ( ('/%(branches)s/B_SPLIT/proj/sub1/subsubB ' '(from /%(trunk)s/proj/sub1/subsubB:30)', 'A'), )) conv.logs[32].check('This change affects sub3/default and ' 'sub1/subsubB/default, on branch', ( ('/%(branches)s/B_SPLIT/proj/sub1/subsubB/default', 'M'), ('/%(branches)s/B_SPLIT/proj/sub3/default', 'M'), )) @Cvs2SvnTestFunction def multiple_tags(): "multiple tags referring to same revision" conv = ensure_conversion('main') if not conv.path_exists('tags', 'T_ALL_INITIAL_FILES', 'proj', 'default'): raise Failure() if not conv.path_exists( 'tags', 'T_ALL_INITIAL_FILES_BUT_ONE', 'proj', 'default'): raise Failure() @Cvs2SvnTestFunction def multiply_defined_symbols(): "multiple definitions of symbol names" # We can only check one line of the error output at a time, so test # twice. (The conversion only have to be done once because the # results are cached.) conv = ensure_conversion( 'multiply-defined-symbols', error_re=( r"ERROR\: Multiple definitions of the symbol \'BRANCH\' .*\: " r"1\.2\.4 1\.2\.2" ), ) conv = ensure_conversion( 'multiply-defined-symbols', error_re=( r"ERROR\: Multiple definitions of the symbol \'TAG\' .*\: " r"1\.2 1\.1" ), ) @Cvs2SvnTestFunction def multiply_defined_symbols_renamed(): "rename multiply defined symbols" conv = ensure_conversion( 'multiply-defined-symbols', options_file='cvs2svn-rename.options', ) @Cvs2SvnTestFunction def multiply_defined_symbols_ignored(): "ignore multiply defined symbols" conv = ensure_conversion( 'multiply-defined-symbols', options_file='cvs2svn-ignore.options', ) @Cvs2SvnTestFunction def repeatedly_defined_symbols(): "multiple identical definitions of symbol names" # If a symbol is defined multiple times but has the same value each # time, that should not be an error. conv = ensure_conversion('repeatedly-defined-symbols') @Cvs2SvnTestFunction def bogus_tag(): "conversion of invalid symbolic names" conv = ensure_conversion('bogus-tag') @Cvs2SvnTestFunction def overlapping_branch(): "ignore a file with a branch with two names" conv = ensure_conversion( 'overlapping-branch', verbosity='-qq', error_re='.*cannot also have name \'vendorB\'', ) conv.logs[2].check('imported', ( ('/%(trunk)s/nonoverlapping-branch', 'A'), ('/%(trunk)s/overlapping-branch', 'A'), )) if len(conv.logs) != 2: raise Failure() class PhoenixBranch(Cvs2SvnTestCase): "convert a branch file rooted in a 'dead' revision" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'phoenix', **kw) def run(self, sbox): conv = self.ensure_conversion() conv.logs[8].check('This file was supplied by Jack Moffitt', ( ('/%(branches)s/volsung_20010721', 'A'), ('/%(branches)s/volsung_20010721/phoenix', 'A'), )) conv.logs[9].check('This file was supplied by Jack Moffitt', ( ('/%(branches)s/volsung_20010721/phoenix', 'M'), )) ###TODO: We check for 4 changed paths here to accomodate creating tags ###and branches in rev 1, but that will change, so this will ###eventually change back. @Cvs2SvnTestFunction def ctrl_char_in_log(): "handle a control char in a log message" # This was issue #1106. rev = 2 conv = ensure_conversion('ctrl-char-in-log') conv.logs[rev].check_changes(( ('/%(trunk)s/ctrl-char-in-log', 'A'), )) if conv.logs[rev].msg.find('\x04') < 0: raise Failure( "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong.") @Cvs2SvnTestFunction def overdead(): "handle tags rooted in a redeleted revision" conv = ensure_conversion('overdead') class NoTrunkPrune(Cvs2SvnTestCase): "ensure that trunk doesn't get pruned" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'overdead', **kw) def run(self, sbox): conv = self.ensure_conversion() for rev in conv.logs.keys(): rev_logs = conv.logs[rev] if rev_logs.get_path_op('/%(trunk)s') == 'D': raise Failure() @Cvs2SvnTestFunction def double_delete(): "file deleted twice, in the root of the repository" # This really tests several things: how we handle a file that's # removed (state 'dead') in two successive revisions; how we # handle a file in the root of the repository (there were some # bugs in cvs2svn's svn path construction for top-level files); and # the --no-prune option. conv = ensure_conversion( 'double-delete', args=['--trunk-only', '--no-prune']) path = '/%(trunk)s/twice-removed' rev = 2 conv.logs[rev].check('Updated CVS', ( (path, 'A'), )) conv.logs[rev + 1].check('Remove this file for the first time.', ( (path, 'D'), )) conv.logs[rev + 2].check('Remove this file for the second time,', ( )) @Cvs2SvnTestFunction def split_branch(): "branch created from both trunk and another branch" # See test-data/split-branch-cvsrepos/README. # # The conversion will fail if the bug is present, and # ensure_conversion will raise Failure. conv = ensure_conversion('split-branch') @Cvs2SvnTestFunction def resync_misgroups(): "resyncing should not misorder commit groups" # See test-data/resync-misgroups-cvsrepos/README. # # The conversion will fail if the bug is present, and # ensure_conversion will raise Failure. conv = ensure_conversion('resync-misgroups') class TaggedBranchAndTrunk(Cvs2SvnTestCase): "allow tags with mixed trunk and branch sources" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'tagged-branch-n-trunk', **kw) def run(self, sbox): conv = self.ensure_conversion() tags = conv.symbols.get('tags', 'tags') a_path = conv.get_wc(tags, 'some-tag', 'a.txt') b_path = conv.get_wc(tags, 'some-tag', 'b.txt') if not (os.path.exists(a_path) and os.path.exists(b_path)): raise Failure() if (open(a_path, 'r').read().find('1.24') == -1) \ or (open(b_path, 'r').read().find('1.5') == -1): raise Failure() @Cvs2SvnTestFunction def enroot_race(): "never use the rev-in-progress as a copy source" # See issue #1427 and r8544. conv = ensure_conversion('enroot-race') rev = 6 conv.logs[rev].check_changes(( ('/%(branches)s/mybranch (from /%(trunk)s:5)', 'A'), ('/%(branches)s/mybranch/proj/a.txt', 'D'), ('/%(branches)s/mybranch/proj/b.txt', 'D'), )) conv.logs[rev + 1].check_changes(( ('/%(branches)s/mybranch/proj/c.txt', 'M'), ('/%(trunk)s/proj/a.txt', 'M'), ('/%(trunk)s/proj/b.txt', 'M'), )) @Cvs2SvnTestFunction def enroot_race_obo(): "do use the last completed rev as a copy source" conv = ensure_conversion('enroot-race-obo') conv.logs[3].check_change('/%(branches)s/BRANCH (from /%(trunk)s:2)', 'A') if not len(conv.logs) == 3: raise Failure() class BranchDeleteFirst(Cvs2SvnTestCase): "correctly handle deletion as initial branch action" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'branch-delete-first', **kw) def run(self, sbox): # See test-data/branch-delete-first-cvsrepos/README. # # The conversion will fail if the bug is present, and # ensure_conversion would raise Failure. conv = self.ensure_conversion() branches = conv.symbols.get('branches', 'branches') # 'file' was deleted from branch-1 and branch-2, but not branch-3 if conv.path_exists(branches, 'branch-1', 'file'): raise Failure() if conv.path_exists(branches, 'branch-2', 'file'): raise Failure() if not conv.path_exists(branches, 'branch-3', 'file'): raise Failure() @Cvs2SvnTestFunction def nonascii_cvsignore(): "non ascii files in .cvsignore" # The output seems to be in the C locale, where it looks like this # (at least on one test system): expected = ( 'Sp?\\195?\\164tzle\n' 'Cr?\\195?\\168meBr?\\195?\\187l?\\195?\\169e\n' 'Jam?\\195?\\179nIb?\\195?\\169rico\n' 'Am?\\195?\\170ijoas?\\195?\\128Bulh?\\195?\\163oPato\n' ) conv = ensure_conversion('non-ascii', args=['--encoding=latin1']) props = props_for_path(conv.get_wc_tree(), 'trunk/single-files') if props['svn:ignore'] != expected: raise Failure() @Cvs2SvnTestFunction def nonascii_filenames(): "non ascii files converted incorrectly" # see issue #1255 # on a en_US.iso-8859-1 machine this test fails with # svn: Can't recode ... # # as described in the issue # on a en_US.UTF-8 machine this test fails with # svn: Malformed XML ... # # which means at least it fails. Unfortunately it won't fail # with the same error... # mangle current locale settings so we know we're not running # a UTF-8 locale (which does not exhibit this problem) current_locale = locale.getlocale() new_locale = 'en_US.ISO8859-1' locale_changed = None # From http://docs.python.org/lib/module-sys.html # # getfilesystemencoding(): # # Return the name of the encoding used to convert Unicode filenames # into system file names, or None if the system default encoding is # used. The result value depends on the operating system: # # - On Windows 9x, the encoding is ``mbcs''. # - On Mac OS X, the encoding is ``utf-8''. # - On Unix, the encoding is the user's preference according to the # result of nl_langinfo(CODESET), or None if the # nl_langinfo(CODESET) failed. # - On Windows NT+, file names are Unicode natively, so no conversion is # performed. # So we're going to skip this test on Mac OS X for now. if sys.platform == "darwin": raise svntest.Skip() try: # change locale to non-UTF-8 locale to generate latin1 names locale.setlocale(locale.LC_ALL, # this might be too broad? new_locale) locale_changed = 1 except locale.Error: raise svntest.Skip() try: srcrepos_path = os.path.join(test_data_dir, 'non-ascii-cvsrepos') dstrepos_path = os.path.join(test_data_dir, 'non-ascii-copy-cvsrepos') if not os.path.exists(dstrepos_path): # create repos from existing main repos shutil.copytree(srcrepos_path, dstrepos_path) base_path = os.path.join(dstrepos_path, 'single-files') os.remove(os.path.join(base_path, '.cvsignore,v')) shutil.copyfile(os.path.join(base_path, 'twoquick,v'), os.path.join(base_path, 'two\366uick,v')) new_path = os.path.join(dstrepos_path, 'single\366files') os.rename(base_path, new_path) conv = ensure_conversion('non-ascii-copy', args=['--encoding=latin1']) finally: if locale_changed: locale.setlocale(locale.LC_ALL, current_locale) safe_rmtree(dstrepos_path) class UnicodeTest(Cvs2SvnTestCase): "metadata contains Unicode" warning_pattern = r'ERROR\: There were warnings converting .* messages' def __init__(self, name, warning_expected, **kw): if warning_expected: error_re = self.warning_pattern else: error_re = None Cvs2SvnTestCase.__init__(self, name, error_re=error_re, **kw) self.warning_expected = warning_expected def run(self, sbox): try: # ensure the availability of the "utf_8" encoding: u'a'.encode('utf_8').decode('utf_8') except LookupError: raise svntest.Skip() self.ensure_conversion() class UnicodeAuthor(UnicodeTest): "author name contains Unicode" def __init__(self, warning_expected, **kw): UnicodeTest.__init__(self, 'unicode-author', warning_expected, **kw) class UnicodeLog(UnicodeTest): "log message contains Unicode" def __init__(self, warning_expected, **kw): UnicodeTest.__init__(self, 'unicode-log', warning_expected, **kw) @Cvs2SvnTestFunction def vendor_branch_sameness(): "avoid spurious changes for initial revs" conv = ensure_conversion( 'vendor-branch-sameness', args=['--keep-trivial-imports'] ) # The following files are in this repository: # # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have # the same contents, the file's default branch is 1.1.1, # and both revisions are in state 'Exp'. # # b.txt: Like a.txt, except that 1.1.1.1 has a real change from # 1.1 (the addition of a line of text). # # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'. # # d.txt: This file was created by 'cvs add' instead of import, so # it has only 1.1 -- no 1.1.1.1, and no default branch. # The timestamp on the add is exactly the same as for the # imports of the other files. # # e.txt: Like a.txt, except that the log message for revision 1.1 # is not the standard import log message. # # (Aside from e.txt, the log messages for the same revisions are the # same in all files.) # # We expect that only a.txt is recognized as an import whose 1.1 # revision can be omitted. The other files should be added on trunk # then filled to vbranchA, whereas a.txt should be added to vbranchA # then copied to trunk. In the copy of 1.1.1.1 back to trunk, a.txt # and e.txt should be copied untouched; b.txt should be 'M'odified, # and c.txt should be 'D'eleted. rev = 2 conv.logs[rev].check('Initial revision', ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/b.txt', 'A'), ('/%(trunk)s/proj/c.txt', 'A'), ('/%(trunk)s/proj/d.txt', 'A'), )) conv.logs[rev + 1].check(sym_log_msg('vbranchA'), ( ('/%(branches)s/vbranchA (from /%(trunk)s:2)', 'A'), ('/%(branches)s/vbranchA/proj/d.txt', 'D'), )) conv.logs[rev + 2].check('First vendor branch revision.', ( ('/%(branches)s/vbranchA/proj/a.txt', 'A'), ('/%(branches)s/vbranchA/proj/b.txt', 'M'), ('/%(branches)s/vbranchA/proj/c.txt', 'D'), )) conv.logs[rev + 3].check('This commit was generated by cvs2svn ' 'to compensate for changes in r4,', ( ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:4)', 'A'), ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:4)', 'R'), ('/%(trunk)s/proj/c.txt', 'D'), )) rev = 7 conv.logs[rev].check('This log message is not the standard', ( ('/%(trunk)s/proj/e.txt', 'A'), )) conv.logs[rev + 2].check('First vendor branch revision', ( ('/%(branches)s/vbranchB/proj/e.txt', 'M'), )) conv.logs[rev + 3].check('This commit was generated by cvs2svn ' 'to compensate for changes in r9,', ( ('/%(trunk)s/proj/e.txt (from /%(branches)s/vbranchB/proj/e.txt:9)', 'R'), )) @Cvs2SvnTestFunction def vendor_branch_trunk_only(): "handle vendor branches with --trunk-only" conv = ensure_conversion('vendor-branch-sameness', args=['--trunk-only']) rev = 2 conv.logs[rev].check('Initial revision', ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/b.txt', 'A'), ('/%(trunk)s/proj/c.txt', 'A'), ('/%(trunk)s/proj/d.txt', 'A'), )) conv.logs[rev + 1].check('First vendor branch revision', ( ('/%(trunk)s/proj/a.txt', 'A'), ('/%(trunk)s/proj/b.txt', 'M'), ('/%(trunk)s/proj/c.txt', 'D'), )) conv.logs[rev + 2].check('This log message is not the standard', ( ('/%(trunk)s/proj/e.txt', 'A'), )) conv.logs[rev + 3].check('First vendor branch revision', ( ('/%(trunk)s/proj/e.txt', 'M'), )) @Cvs2SvnTestFunction def default_branches(): "handle default branches correctly" conv = ensure_conversion('default-branches') # There are seven files in the repository: # # a.txt: # Imported in the traditional way, so 1.1 and 1.1.1.1 are the # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2 # committed (thus losing the default branch "1.1.1"), then # 1.1.1.4 was imported. All vendor import release tags are # still present. # # b.txt: # Like a.txt, but without rev 1.2. # # c.txt: # Exactly like b.txt, just s/b.txt/c.txt/ in content. # # d.txt: # Same as the previous two, but 1.1.1 branch is unlabeled. # # e.txt: # Same, but missing 1.1.1 label and all tags but 1.1.1.3. # # deleted-on-vendor-branch.txt,v: # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'. # # added-then-imported.txt,v: # Added with 'cvs add' to create 1.1, then imported with # completely different contents to create 1.1.1.1, therefore # never had a default branch. # conv.logs[2].check("Import (vbranchA, vtag-1).", ( ('/%(branches)s/unlabeled-1.1.1', 'A'), ('/%(branches)s/unlabeled-1.1.1/proj', 'A'), ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'A'), ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'A'), ('/%(branches)s/vbranchA', 'A'), ('/%(branches)s/vbranchA/proj', 'A'), ('/%(branches)s/vbranchA/proj/a.txt', 'A'), ('/%(branches)s/vbranchA/proj/b.txt', 'A'), ('/%(branches)s/vbranchA/proj/c.txt', 'A'), ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'), )) conv.logs[3].check("This commit was generated by cvs2svn " "to compensate for changes in r2,", ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/a.txt (from /%(branches)s/vbranchA/proj/a.txt:2)', 'A'), ('/%(trunk)s/proj/b.txt (from /%(branches)s/vbranchA/proj/b.txt:2)', 'A'), ('/%(trunk)s/proj/c.txt (from /%(branches)s/vbranchA/proj/c.txt:2)', 'A'), ('/%(trunk)s/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt ' '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:2)', 'A'), ('/%(trunk)s/proj/e.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:2)', 'A'), )) conv.logs[4].check(sym_log_msg('vtag-1',1), ( ('/%(tags)s/vtag-1 (from /%(branches)s/vbranchA:2)', 'A'), ('/%(tags)s/vtag-1/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:2)', 'A'), )) conv.logs[5].check("Import (vbranchA, vtag-2).", ( ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'), ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'), ('/%(branches)s/vbranchA/proj/a.txt', 'M'), ('/%(branches)s/vbranchA/proj/b.txt', 'M'), ('/%(branches)s/vbranchA/proj/c.txt', 'M'), ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'M'), )) conv.logs[6].check("This commit was generated by cvs2svn " "to compensate for changes in r5,", ( ('/%(trunk)s/proj/a.txt ' '(from /%(branches)s/vbranchA/proj/a.txt:5)', 'R'), ('/%(trunk)s/proj/b.txt ' '(from /%(branches)s/vbranchA/proj/b.txt:5)', 'R'), ('/%(trunk)s/proj/c.txt ' '(from /%(branches)s/vbranchA/proj/c.txt:5)', 'R'), ('/%(trunk)s/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'R'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt ' '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:5)', 'R'), ('/%(trunk)s/proj/e.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:5)', 'R'), )) conv.logs[7].check(sym_log_msg('vtag-2',1), ( ('/%(tags)s/vtag-2 (from /%(branches)s/vbranchA:5)', 'A'), ('/%(tags)s/vtag-2/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:5)', 'A'), )) conv.logs[8].check("Import (vbranchA, vtag-3).", ( ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'), ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'), ('/%(branches)s/vbranchA/proj/a.txt', 'M'), ('/%(branches)s/vbranchA/proj/b.txt', 'M'), ('/%(branches)s/vbranchA/proj/c.txt', 'M'), ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'D'), )) conv.logs[9].check("This commit was generated by cvs2svn " "to compensate for changes in r8,", ( ('/%(trunk)s/proj/a.txt ' '(from /%(branches)s/vbranchA/proj/a.txt:8)', 'R'), ('/%(trunk)s/proj/b.txt ' '(from /%(branches)s/vbranchA/proj/b.txt:8)', 'R'), ('/%(trunk)s/proj/c.txt ' '(from /%(branches)s/vbranchA/proj/c.txt:8)', 'R'), ('/%(trunk)s/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'R'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'), ('/%(trunk)s/proj/e.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'R'), )) conv.logs[10].check(sym_log_msg('vtag-3',1), ( ('/%(tags)s/vtag-3 (from /%(branches)s/vbranchA:8)', 'A'), ('/%(tags)s/vtag-3/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:8)', 'A'), ('/%(tags)s/vtag-3/proj/e.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:8)', 'A'), )) conv.logs[11].check("First regular commit, to a.txt, on vtag-3.", ( ('/%(trunk)s/proj/a.txt', 'M'), )) conv.logs[12].check("Add a file to the working copy.", ( ('/%(trunk)s/proj/added-then-imported.txt', 'A'), )) conv.logs[13].check(sym_log_msg('vbranchA'), ( ('/%(branches)s/vbranchA/proj/added-then-imported.txt ' '(from /%(trunk)s/proj/added-then-imported.txt:12)', 'A'), )) conv.logs[14].check("Import (vbranchA, vtag-4).", ( ('/%(branches)s/unlabeled-1.1.1/proj/d.txt', 'M'), ('/%(branches)s/unlabeled-1.1.1/proj/e.txt', 'M'), ('/%(branches)s/vbranchA/proj/a.txt', 'M'), ('/%(branches)s/vbranchA/proj/added-then-imported.txt', 'M'), # CHECK!!! ('/%(branches)s/vbranchA/proj/b.txt', 'M'), ('/%(branches)s/vbranchA/proj/c.txt', 'M'), ('/%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt', 'A'), )) conv.logs[15].check("This commit was generated by cvs2svn " "to compensate for changes in r14,", ( ('/%(trunk)s/proj/b.txt ' '(from /%(branches)s/vbranchA/proj/b.txt:14)', 'R'), ('/%(trunk)s/proj/c.txt ' '(from /%(branches)s/vbranchA/proj/c.txt:14)', 'R'), ('/%(trunk)s/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'R'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt ' '(from /%(branches)s/vbranchA/proj/deleted-on-vendor-branch.txt:14)', 'A'), ('/%(trunk)s/proj/e.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/e.txt:14)', 'R'), )) conv.logs[16].check(sym_log_msg('vtag-4',1), ( ('/%(tags)s/vtag-4 (from /%(branches)s/vbranchA:14)', 'A'), ('/%(tags)s/vtag-4/proj/d.txt ' '(from /%(branches)s/unlabeled-1.1.1/proj/d.txt:14)', 'A'), )) @Cvs2SvnTestFunction def default_branches_trunk_only(): "handle default branches with --trunk-only" conv = ensure_conversion('default-branches', args=['--trunk-only']) conv.logs[2].check("Import (vbranchA, vtag-1).", ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/a.txt', 'A'), ('/%(trunk)s/proj/b.txt', 'A'), ('/%(trunk)s/proj/c.txt', 'A'), ('/%(trunk)s/proj/d.txt', 'A'), ('/%(trunk)s/proj/e.txt', 'A'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'), )) conv.logs[3].check("Import (vbranchA, vtag-2).", ( ('/%(trunk)s/proj/a.txt', 'M'), ('/%(trunk)s/proj/b.txt', 'M'), ('/%(trunk)s/proj/c.txt', 'M'), ('/%(trunk)s/proj/d.txt', 'M'), ('/%(trunk)s/proj/e.txt', 'M'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'M'), )) conv.logs[4].check("Import (vbranchA, vtag-3).", ( ('/%(trunk)s/proj/a.txt', 'M'), ('/%(trunk)s/proj/b.txt', 'M'), ('/%(trunk)s/proj/c.txt', 'M'), ('/%(trunk)s/proj/d.txt', 'M'), ('/%(trunk)s/proj/e.txt', 'M'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'D'), )) conv.logs[5].check("First regular commit, to a.txt, on vtag-3.", ( ('/%(trunk)s/proj/a.txt', 'M'), )) conv.logs[6].check("Add a file to the working copy.", ( ('/%(trunk)s/proj/added-then-imported.txt', 'A'), )) conv.logs[7].check("Import (vbranchA, vtag-4).", ( ('/%(trunk)s/proj/b.txt', 'M'), ('/%(trunk)s/proj/c.txt', 'M'), ('/%(trunk)s/proj/d.txt', 'M'), ('/%(trunk)s/proj/e.txt', 'M'), ('/%(trunk)s/proj/deleted-on-vendor-branch.txt', 'A'), )) @Cvs2SvnTestFunction def default_branch_and_1_2(): "do not allow 1.2 revision with default branch" conv = ensure_conversion( 'default-branch-and-1-2', error_re=( r'.*File \'.*\' has default branch=1\.1\.1 but also a revision 1\.2' ), ) @Cvs2SvnTestFunction def compose_tag_three_sources(): "compose a tag from three sources" conv = ensure_conversion('compose-tag-three-sources') conv.logs[2].check("Add on trunk", ( ('/%(trunk)s/tagged-on-trunk-1.1', 'A'), ('/%(trunk)s/tagged-on-trunk-1.2-a', 'A'), ('/%(trunk)s/tagged-on-trunk-1.2-b', 'A'), ('/%(trunk)s/tagged-on-b1', 'A'), ('/%(trunk)s/tagged-on-b2', 'A'), )) conv.logs[3].check(sym_log_msg('b1'), ( ('/%(branches)s/b1 (from /%(trunk)s:2)', 'A'), )) conv.logs[4].check(sym_log_msg('b2'), ( ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'), )) conv.logs[5].check("Commit on branch b1", ( ('/%(branches)s/b1/tagged-on-trunk-1.1', 'M'), ('/%(branches)s/b1/tagged-on-trunk-1.2-a', 'M'), ('/%(branches)s/b1/tagged-on-trunk-1.2-b', 'M'), ('/%(branches)s/b1/tagged-on-b1', 'M'), ('/%(branches)s/b1/tagged-on-b2', 'M'), )) conv.logs[6].check("Commit on branch b2", ( ('/%(branches)s/b2/tagged-on-trunk-1.1', 'M'), ('/%(branches)s/b2/tagged-on-trunk-1.2-a', 'M'), ('/%(branches)s/b2/tagged-on-trunk-1.2-b', 'M'), ('/%(branches)s/b2/tagged-on-b1', 'M'), ('/%(branches)s/b2/tagged-on-b2', 'M'), )) conv.logs[7].check("Commit again on trunk", ( ('/%(trunk)s/tagged-on-trunk-1.2-a', 'M'), ('/%(trunk)s/tagged-on-trunk-1.2-b', 'M'), ('/%(trunk)s/tagged-on-trunk-1.1', 'M'), ('/%(trunk)s/tagged-on-b1', 'M'), ('/%(trunk)s/tagged-on-b2', 'M'), )) conv.logs[8].check(sym_log_msg('T',1), ( ('/%(tags)s/T (from /%(trunk)s:7)', 'A'), ('/%(tags)s/T/tagged-on-trunk-1.1 ' '(from /%(trunk)s/tagged-on-trunk-1.1:2)', 'R'), ('/%(tags)s/T/tagged-on-b1 (from /%(branches)s/b1/tagged-on-b1:5)', 'R'), ('/%(tags)s/T/tagged-on-b2 (from /%(branches)s/b2/tagged-on-b2:6)', 'R'), )) @Cvs2SvnTestFunction def pass5_when_to_fill(): "reserve a svn revnum for a fill only when required" # The conversion will fail if the bug is present, and # ensure_conversion would raise Failure. conv = ensure_conversion('pass5-when-to-fill') class EmptyTrunk(Cvs2SvnTestCase): "don't break when the trunk is empty" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'empty-trunk', **kw) def run(self, sbox): # The conversion will fail if the bug is present, and # ensure_conversion would raise Failure. conv = self.ensure_conversion() @Cvs2SvnTestFunction def no_spurious_svn_commits(): "ensure that we don't create any spurious commits" conv = ensure_conversion('phoenix') # Check spurious commit that could be created in # SVNCommitCreator._pre_commit() # # (When you add a file on a branch, CVS creates a trunk revision # in state 'dead'. If the log message of that commit is equal to # the one that CVS generates, we do not ever create a 'fill' # SVNCommit for it.) # # and spurious commit that could be created in # SVNCommitCreator._commit() # # (When you add a file on a branch, CVS creates a trunk revision # in state 'dead'. If the log message of that commit is equal to # the one that CVS generates, we do not create a primary SVNCommit # for it.) conv.logs[17].check('File added on branch xiphophorus', ( ('/%(branches)s/xiphophorus/added-on-branch.txt', 'A'), )) # Check to make sure that a commit *is* generated: # (When you add a file on a branch, CVS creates a trunk revision # in state 'dead'. If the log message of that commit is NOT equal # to the one that CVS generates, we create a primary SVNCommit to # serve as a home for the log message in question. conv.logs[18].check('file added-on-branch2.txt was initially added on ' + 'branch xiphophorus,\nand this log message was tweaked', ()) # Check spurious commit that could be created in # SVNCommitCreator._commit_symbols(). conv.logs[19].check('This file was also added on branch xiphophorus,', ( ('/%(branches)s/xiphophorus/added-on-branch2.txt', 'A'), )) class PeerPathPruning(Cvs2SvnTestCase): "make sure that filling prunes paths correctly" def __init__(self, **kw): Cvs2SvnTestCase.__init__(self, 'peer-path-pruning', **kw) def run(self, sbox): conv = self.ensure_conversion() conv.logs[6].check(sym_log_msg('BRANCH'), ( ('/%(branches)s/BRANCH (from /%(trunk)s:4)', 'A'), ('/%(branches)s/BRANCH/bar', 'D'), ('/%(branches)s/BRANCH/foo (from /%(trunk)s/foo:5)', 'R'), )) @Cvs2SvnTestFunction def invalid_closings_on_trunk(): "verify correct revs are copied to default branches" # The conversion will fail if the bug is present, and # ensure_conversion would raise Failure. conv = ensure_conversion('invalid-closings-on-trunk') @Cvs2SvnTestFunction def individual_passes(): "run each pass individually" conv = ensure_conversion('main') conv2 = ensure_conversion('main', passbypass=1) if conv.logs != conv2.logs: raise Failure() @Cvs2SvnTestFunction def resync_bug(): "reveal a big bug in our resync algorithm" # This will fail if the bug is present conv = ensure_conversion('resync-bug') @Cvs2SvnTestFunction def branch_from_default_branch(): "reveal a bug in our default branch detection code" conv = ensure_conversion('branch-from-default-branch') # This revision will be a default branch synchronization only # if cvs2svn is correctly determining default branch revisions. # # The bug was that cvs2svn was treating revisions on branches off of # default branches as default branch revisions, resulting in # incorrectly regarding the branch off of the default branch as a # non-trunk default branch. Crystal clear? I thought so. See # issue #42 for more incoherent blathering. conv.logs[5].check("This commit was generated by cvs2svn", ( ('/%(trunk)s/proj/file.txt ' '(from /%(branches)s/upstream/proj/file.txt:4)', 'R'), )) @Cvs2SvnTestFunction def file_in_attic_too(): "die if a file exists in and out of the attic" ensure_conversion( 'file-in-attic-too', error_re=( r'.*A CVS repository cannot contain both ' r'(.*)' + re.escape(os.sep) + r'(.*) ' + r'and ' r'\1' + re.escape(os.sep) + r'Attic' + re.escape(os.sep) + r'\2' ) ) @Cvs2SvnTestFunction def retain_file_in_attic_too(): "test --retain-conflicting-attic-files option" conv = ensure_conversion( 'file-in-attic-too', args=['--retain-conflicting-attic-files']) if not conv.path_exists('trunk', 'file.txt'): raise Failure() if not conv.path_exists('trunk', 'Attic', 'file.txt'): raise Failure() @Cvs2SvnTestFunction def symbolic_name_filling_guide(): "reveal a big bug in our SymbolFillingGuide" # This will fail if the bug is present conv = ensure_conversion('symbolic-name-overfill') # Helpers for tests involving file contents and properties. class NodeTreeWalkException: "Exception class for node tree traversals." pass def node_for_path(node, path): "In the tree rooted under SVNTree NODE, return the node at PATH." if node.name != '__SVN_ROOT_NODE': raise NodeTreeWalkException() path = path.strip('/') components = path.split('/') for component in components: node = svntest.tree.get_child(node, component) return node # Helper for tests involving properties. def props_for_path(node, path): "In the tree rooted under SVNTree NODE, return the prop dict for PATH." return node_for_path(node, path).props class EOLMime(Cvs2SvnPropertiesTestCase): """eol settings and mime types together The files are as follows: trunk/foo.txt: no -kb, mime file says nothing. trunk/foo.xml: no -kb, mime file says text. trunk/foo.zip: no -kb, mime file says non-text. trunk/foo.bin: has -kb, mime file says nothing. trunk/foo.csv: has -kb, mime file says text. trunk/foo.dbf: has -kb, mime file says non-text. """ def __init__(self, args, **kw): # TODO: It's a bit klugey to construct this path here. But so far # there's only one test with a mime.types file. If we have more, # we should abstract this into some helper, which would be located # near ensure_conversion(). Note that it is a convention of this # test suite for a mime.types file to be located in the top level # of the CVS repository to which it applies. self.mime_path = os.path.join( test_data_dir, 'eol-mime-cvsrepos', 'mime.types') Cvs2SvnPropertiesTestCase.__init__( self, 'eol-mime', props_to_test=['svn:eol-style', 'svn:mime-type', 'svn:keywords'], args=['--mime-types=%s' % self.mime_path] + args, **kw) # We do four conversions. Each time, we pass --mime-types=FILE with # the same FILE, but vary --default-eol and --eol-from-mime-type. # Thus there's one conversion with neither flag, one with just the # former, one with just the latter, and one with both. # Neither --no-default-eol nor --eol-from-mime-type: eol_mime1 = EOLMime( variant=1, args=[], expected_props=[ ('trunk/foo.txt', [None, None, None]), ('trunk/foo.xml', [None, 'text/xml', None]), ('trunk/foo.zip', [None, 'application/zip', None]), ('trunk/foo.bin', [None, 'application/octet-stream', None]), ('trunk/foo.csv', [None, 'text/csv', None]), ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]), ]) # Just --no-default-eol, not --eol-from-mime-type: eol_mime2 = EOLMime( variant=2, args=['--default-eol=native'], expected_props=[ ('trunk/foo.txt', ['native', None, KEYWORDS]), ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]), ('trunk/foo.zip', ['native', 'application/zip', KEYWORDS]), ('trunk/foo.bin', [None, 'application/octet-stream', None]), ('trunk/foo.csv', [None, 'text/csv', None]), ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]), ]) # Just --eol-from-mime-type, not --no-default-eol: eol_mime3 = EOLMime( variant=3, args=['--eol-from-mime-type'], expected_props=[ ('trunk/foo.txt', [None, None, None]), ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]), ('trunk/foo.zip', [None, 'application/zip', None]), ('trunk/foo.bin', [None, 'application/octet-stream', None]), ('trunk/foo.csv', [None, 'text/csv', None]), ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]), ]) # Both --no-default-eol and --eol-from-mime-type: eol_mime4 = EOLMime( variant=4, args=['--eol-from-mime-type', '--default-eol=native'], expected_props=[ ('trunk/foo.txt', ['native', None, KEYWORDS]), ('trunk/foo.xml', ['native', 'text/xml', KEYWORDS]), ('trunk/foo.zip', [None, 'application/zip', None]), ('trunk/foo.bin', [None, 'application/octet-stream', None]), ('trunk/foo.csv', [None, 'text/csv', None]), ('trunk/foo.dbf', [None, 'application/what-is-dbf', None]), ]) cvs_revnums_off = Cvs2SvnPropertiesTestCase( 'eol-mime', doc='test non-setting of cvs2svn:cvs-rev property', args=[], props_to_test=['cvs2svn:cvs-rev'], expected_props=[ ('trunk/foo.txt', [None]), ('trunk/foo.xml', [None]), ('trunk/foo.zip', [None]), ('trunk/foo.bin', [None]), ('trunk/foo.csv', [None]), ('trunk/foo.dbf', [None]), ]) cvs_revnums_on = Cvs2SvnPropertiesTestCase( 'eol-mime', doc='test setting of cvs2svn:cvs-rev property', args=['--cvs-revnums'], props_to_test=['cvs2svn:cvs-rev'], expected_props=[ ('trunk/foo.txt', ['1.2']), ('trunk/foo.xml', ['1.2']), ('trunk/foo.zip', ['1.2']), ('trunk/foo.bin', ['1.2']), ('trunk/foo.csv', ['1.2']), ('trunk/foo.dbf', ['1.2']), ]) keywords = Cvs2SvnPropertiesTestCase( 'keywords', doc='test setting of svn:keywords property among others', args=['--default-eol=native'], props_to_test=['svn:keywords', 'svn:eol-style', 'svn:mime-type'], expected_props=[ ('trunk/foo.default', [KEYWORDS, 'native', None]), ('trunk/foo.kkvl', [KEYWORDS, 'native', None]), ('trunk/foo.kkv', [KEYWORDS, 'native', None]), ('trunk/foo.kb', [None, None, 'application/octet-stream']), ('trunk/foo.kk', [None, 'native', None]), ('trunk/foo.ko', [None, 'native', None]), ('trunk/foo.kv', [None, 'native', None]), ]) @Cvs2SvnTestFunction def ignore(): "test setting of svn:ignore property" conv = ensure_conversion('cvsignore') wc_tree = conv.get_wc_tree() topdir_props = props_for_path(wc_tree, 'trunk/proj') subdir_props = props_for_path(wc_tree, '/trunk/proj/subdir') if topdir_props['svn:ignore'] != \ '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n': raise Failure() if subdir_props['svn:ignore'] != \ '*.idx\n*.aux\n*.dvi\n*.log\nfoo\nbar\nbaz\nqux\n': raise Failure() @Cvs2SvnTestFunction def requires_cvs(): "test that CVS can still do what RCS can't" # See issues 4, 11, 29 for the bugs whose regression we're testing for. conv = ensure_conversion( 'requires-cvs', args=['--use-cvs', '--default-eol=native'], ) atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read() cl_contents = file(conv.get_wc("trunk", "client_lock.idl")).read() if atsign_contents[-1:] == "@": raise Failure() if cl_contents.find("gregh\n//\n//Integration for locks") < 0: raise Failure() if not (conv.logs[6].author == "William Lyon Phelps III" and conv.logs[5].author == "j random"): raise Failure() @Cvs2SvnTestFunction def questionable_branch_names(): "test that we can handle weird branch names" conv = ensure_conversion('questionable-symbols') # If the conversion succeeds, then we're okay. We could check the # actual branch paths, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def questionable_tag_names(): "test that we can handle weird tag names" conv = ensure_conversion('questionable-symbols') conv.find_tag_log('Tag_A').check(sym_log_msg('Tag_A', 1), ( ('/%(tags)s/Tag_A (from /trunk:8)', 'A'), )) conv.find_tag_log('TagWith/Backslash_E').check( sym_log_msg('TagWith/Backslash_E',1), ( ('/%(tags)s/TagWith', 'A'), ('/%(tags)s/TagWith/Backslash_E (from /trunk:8)', 'A'), ) ) conv.find_tag_log('TagWith/Slash_Z').check( sym_log_msg('TagWith/Slash_Z',1), ( ('/%(tags)s/TagWith/Slash_Z (from /trunk:8)', 'A'), ) ) @Cvs2SvnTestFunction def revision_reorder_bug(): "reveal a bug that reorders file revisions" conv = ensure_conversion('revision-reorder-bug') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def exclude(): "test that exclude really excludes everything" conv = ensure_conversion('main', args=['--exclude=.*']) for log in conv.logs.values(): for item in log.changed_paths.keys(): if item.startswith('/branches/') or item.startswith('/tags/'): raise Failure() @Cvs2SvnTestFunction def vendor_branch_delete_add(): "add trunk file that was deleted on vendor branch" # This will error if the bug is present conv = ensure_conversion('vendor-branch-delete-add') @Cvs2SvnTestFunction def resync_pass2_pull_forward(): "ensure pass2 doesn't pull rev too far forward" conv = ensure_conversion('resync-pass2-pull-forward') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def native_eol(): "only LFs for svn:eol-style=native files" conv = ensure_conversion('native-eol', args=['--default-eol=native']) lines = run_program(svntest.main.svnadmin_binary, None, 'dump', '-q', conv.repos) # Verify that all files in the dump have LF EOLs. We're actually # testing the whole dump file, but the dump file itself only uses # LF EOLs, so we're safe. for line in lines: if line[-1] != '\n' or line[:-1].find('\r') != -1: raise Failure() @Cvs2SvnTestFunction def double_fill(): "reveal a bug that created a branch twice" conv = ensure_conversion('double-fill') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @XFail_deco() @Cvs2SvnTestFunction def double_fill2(): "reveal a second bug that created a branch twice" conv = ensure_conversion('double-fill2') conv.logs[6].check_msg(sym_log_msg('BRANCH1')) conv.logs[7].check_msg(sym_log_msg('BRANCH2')) try: # This check should fail: conv.logs[8].check_msg(sym_log_msg('BRANCH2')) except Failure: pass else: raise Failure('Symbol filled twice in a row') @Cvs2SvnTestFunction def resync_pass2_push_backward(): "ensure pass2 doesn't push rev too far backward" conv = ensure_conversion('resync-pass2-push-backward') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def double_add(): "reveal a bug that added a branch file twice" conv = ensure_conversion('double-add') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def bogus_branch_copy(): "reveal a bug that copies a branch file wrongly" conv = ensure_conversion('bogus-branch-copy') # If the conversion succeeds, then we're okay. We could check the # actual revisions, too, but the main thing is to know that the # conversion doesn't fail. @Cvs2SvnTestFunction def nested_ttb_directories(): "require error if ttb directories are not disjoint" opts_list = [ {'trunk' : 'a', 'branches' : 'a',}, {'trunk' : 'a', 'tags' : 'a',}, {'branches' : 'a', 'tags' : 'a',}, # This option conflicts with the default trunk path: {'branches' : 'trunk',}, # Try some nested directories: {'trunk' : 'a', 'branches' : 'a/b',}, {'trunk' : 'a/b', 'tags' : 'a/b/c/d',}, {'branches' : 'a', 'tags' : 'a/b',}, ] for opts in opts_list: ensure_conversion( 'main', error_re=r'The following paths are not disjoint\:', **opts ) class AutoProps(Cvs2SvnPropertiesTestCase): """Test auto-props. The files are as follows: trunk/foo.txt: no -kb, mime auto-prop says nothing. trunk/foo.xml: no -kb, mime auto-prop says text and eol-style=CRLF. trunk/foo.zip: no -kb, mime auto-prop says non-text. trunk/foo.asc: no -kb, mime auto-prop says text and eol-style=. trunk/foo.bin: has -kb, mime auto-prop says nothing. trunk/foo.csv: has -kb, mime auto-prop says text and eol-style=CRLF. trunk/foo.dbf: has -kb, mime auto-prop says non-text. trunk/foo.UPCASE1: no -kb, no mime type. trunk/foo.UPCASE2: no -kb, no mime type. """ def __init__(self, args, **kw): ### TODO: It's a bit klugey to construct this path here. See also ### the comment in eol_mime(). auto_props_path = os.path.join( test_data_dir, 'eol-mime-cvsrepos', 'auto-props') Cvs2SvnPropertiesTestCase.__init__( self, 'eol-mime', props_to_test=[ 'myprop', 'svn:eol-style', 'svn:mime-type', 'svn:keywords', 'svn:executable', ], args=[ '--auto-props=%s' % auto_props_path, '--eol-from-mime-type' ] + args, **kw) auto_props_ignore_case = AutoProps( doc="test auto-props", args=['--default-eol=native'], expected_props=[ ('trunk/foo.txt', ['txt', 'native', None, KEYWORDS, None]), ('trunk/foo.xml', ['xml', 'CRLF', 'text/xml', KEYWORDS, None]), ('trunk/foo.zip', ['zip', None, 'application/zip', None, None]), ('trunk/foo.asc', ['asc', None, 'text/plain', None, None]), ('trunk/foo.bin', ['bin', None, 'application/octet-stream', None, '']), ('trunk/foo.csv', ['csv', 'CRLF', 'text/csv', None, None]), ('trunk/foo.dbf', ['dbf', None, 'application/what-is-dbf', None, None]), ('trunk/foo.UPCASE1', ['UPCASE1', 'native', None, KEYWORDS, None]), ('trunk/foo.UPCASE2', ['UPCASE2', 'native', None, KEYWORDS, None]), ]) @Cvs2SvnTestFunction def ctrl_char_in_filename(): "do not allow control characters in filenames" try: srcrepos_path = os.path.join(test_data_dir,'main-cvsrepos') dstrepos_path = os.path.join(test_data_dir,'ctrl-char-filename-cvsrepos') if os.path.exists(dstrepos_path): safe_rmtree(dstrepos_path) # create repos from existing main repos shutil.copytree(srcrepos_path, dstrepos_path) base_path = os.path.join(dstrepos_path, 'single-files') try: shutil.copyfile(os.path.join(base_path, 'twoquick,v'), os.path.join(base_path, 'two\rquick,v')) except: # Operating systems that don't allow control characters in # filenames will hopefully have thrown an exception; in that # case, just skip this test. raise svntest.Skip() conv = ensure_conversion( 'ctrl-char-filename', error_re=(r'.*Subversion does not allow character .*.'), ) finally: safe_rmtree(dstrepos_path) @Cvs2SvnTestFunction def commit_dependencies(): "interleaved and multi-branch commits to same files" conv = ensure_conversion("commit-dependencies") conv.logs[2].check('adding', ( ('/%(trunk)s/interleaved', 'A'), ('/%(trunk)s/interleaved/file1', 'A'), ('/%(trunk)s/interleaved/file2', 'A'), )) conv.logs[3].check('big commit', ( ('/%(trunk)s/interleaved/file1', 'M'), ('/%(trunk)s/interleaved/file2', 'M'), )) conv.logs[4].check('dependant small commit', ( ('/%(trunk)s/interleaved/file1', 'M'), )) conv.logs[5].check('adding', ( ('/%(trunk)s/multi-branch', 'A'), ('/%(trunk)s/multi-branch/file1', 'A'), ('/%(trunk)s/multi-branch/file2', 'A'), )) conv.logs[6].check(sym_log_msg("branch"), ( ('/%(branches)s/branch (from /%(trunk)s:5)', 'A'), ('/%(branches)s/branch/interleaved', 'D'), )) conv.logs[7].check('multi-branch-commit', ( ('/%(trunk)s/multi-branch/file1', 'M'), ('/%(trunk)s/multi-branch/file2', 'M'), ('/%(branches)s/branch/multi-branch/file1', 'M'), ('/%(branches)s/branch/multi-branch/file2', 'M'), )) @Cvs2SvnTestFunction def double_branch_delete(): "fill branches before modifying files on them" conv = ensure_conversion('double-branch-delete') # Test for issue #102. The file IMarshalledValue.java is branched, # deleted, readded on the branch, and then deleted again. If the # fill for the file on the branch is postponed until after the # modification, the file will end up live on the branch instead of # dead! Make sure it happens at the right time. conv.logs[6].check('JBAS-2436 - Adding LGPL Header2', ( ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'A'), )); conv.logs[7].check('JBAS-3025 - Removing dependency', ( ('/%(branches)s/Branch_4_0/IMarshalledValue.java', 'D'), )); @Cvs2SvnTestFunction def symbol_mismatches(): "error for conflicting tag/branch" ensure_conversion( 'symbol-mess', args=['--symbol-default=strict'], error_re=r'.*Problems determining how symbols should be converted', ) @Cvs2SvnTestFunction def overlook_symbol_mismatches(): "overlook conflicting tag/branch when --trunk-only" # This is a test for issue #85. ensure_conversion('symbol-mess', args=['--trunk-only']) @Cvs2SvnTestFunction def force_symbols(): "force symbols to be tags/branches" conv = ensure_conversion( 'symbol-mess', args=['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG']) if conv.path_exists('tags', 'BRANCH') \ or not conv.path_exists('branches', 'BRANCH'): raise Failure() if not conv.path_exists('tags', 'TAG') \ or conv.path_exists('branches', 'TAG'): raise Failure() if conv.path_exists('tags', 'MOSTLY_BRANCH') \ or not conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY_TAG') \ or conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def commit_blocks_tags(): "commit prevents forced tag" basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'] ensure_conversion( 'symbol-mess', args=(basic_args + ['--force-tag=BRANCH_WITH_COMMIT']), error_re=( r'.*The following branches cannot be forced to be tags ' r'because they have commits' ) ) @Cvs2SvnTestFunction def blocked_excludes(): "error for blocked excludes" basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'] for blocker in ['BRANCH', 'COMMIT', 'UNNAMED']: try: ensure_conversion( 'symbol-mess', args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker])) raise MissingErrorException() except Failure: pass @Cvs2SvnTestFunction def unblock_blocked_excludes(): "excluding blocker removes blockage" basic_args = ['--force-branch=MOSTLY_BRANCH', '--force-tag=MOSTLY_TAG'] for blocker in ['BRANCH', 'COMMIT']: ensure_conversion( 'symbol-mess', args=(basic_args + ['--exclude=BLOCKED_BY_%s' % blocker, '--exclude=BLOCKING_%s' % blocker])) @Cvs2SvnTestFunction def regexp_force_symbols(): "force symbols via regular expressions" conv = ensure_conversion( 'symbol-mess', args=['--force-branch=MOST.*_BRANCH', '--force-tag=MOST.*_TAG']) if conv.path_exists('tags', 'MOSTLY_BRANCH') \ or not conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY_TAG') \ or conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def heuristic_symbol_default(): "test 'heuristic' symbol default" conv = ensure_conversion( 'symbol-mess', args=['--symbol-default=heuristic']) if conv.path_exists('tags', 'MOSTLY_BRANCH') \ or not conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY_TAG') \ or conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def branch_symbol_default(): "test 'branch' symbol default" conv = ensure_conversion( 'symbol-mess', args=['--symbol-default=branch']) if conv.path_exists('tags', 'MOSTLY_BRANCH') \ or not conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if conv.path_exists('tags', 'MOSTLY_TAG') \ or not conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def tag_symbol_default(): "test 'tag' symbol default" conv = ensure_conversion( 'symbol-mess', args=['--symbol-default=tag']) if not conv.path_exists('tags', 'MOSTLY_BRANCH') \ or conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY_TAG') \ or conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def symbol_transform(): "test --symbol-transform" conv = ensure_conversion( 'symbol-mess', args=[ '--symbol-default=heuristic', '--symbol-transform=BRANCH:branch', '--symbol-transform=TAG:tag', '--symbol-transform=MOSTLY_(BRANCH|TAG):MOSTLY.\\1', ]) if not conv.path_exists('branches', 'branch'): raise Failure() if not conv.path_exists('tags', 'tag'): raise Failure() if not conv.path_exists('branches', 'MOSTLY.BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY.TAG'): raise Failure() @Cvs2SvnTestFunction def write_symbol_info(): "test --write-symbol-info" expected_lines = [ ['0', '.trunk.', 'trunk', 'trunk', '.'], ['0', 'BLOCKED_BY_UNNAMED', 'branch', 'branches/BLOCKED_BY_UNNAMED', '.trunk.'], ['0', 'BLOCKING_COMMIT', 'branch', 'branches/BLOCKING_COMMIT', 'BLOCKED_BY_COMMIT'], ['0', 'BLOCKED_BY_COMMIT', 'branch', 'branches/BLOCKED_BY_COMMIT', '.trunk.'], ['0', 'BLOCKING_BRANCH', 'branch', 'branches/BLOCKING_BRANCH', 'BLOCKED_BY_BRANCH'], ['0', 'BLOCKED_BY_BRANCH', 'branch', 'branches/BLOCKED_BY_BRANCH', '.trunk.'], ['0', 'MOSTLY_BRANCH', '.', '.', '.'], ['0', 'MOSTLY_TAG', '.', '.', '.'], ['0', 'BRANCH_WITH_COMMIT', 'branch', 'branches/BRANCH_WITH_COMMIT', '.trunk.'], ['0', 'BRANCH', 'branch', 'branches/BRANCH', '.trunk.'], ['0', 'TAG', 'tag', 'tags/TAG', '.trunk.'], ['0', 'unlabeled-1.1.12.1.2', 'branch', 'branches/unlabeled-1.1.12.1.2', 'BLOCKED_BY_UNNAMED'], ] expected_lines.sort() symbol_info_file = os.path.join(tmp_dir, 'symbol-mess-symbol-info.txt') try: ensure_conversion( 'symbol-mess', args=[ '--symbol-default=strict', '--write-symbol-info=%s' % (symbol_info_file,), '--passes=:CollateSymbolsPass', ], ) raise MissingErrorException() except Failure: pass lines = [] comment_re = re.compile(r'^\s*\#') for l in open(symbol_info_file, 'r'): if comment_re.match(l): continue lines.append(l.strip().split()) lines.sort() if lines != expected_lines: s = ['Symbol info incorrect\n'] differ = Differ() for diffline in differ.compare( [' '.join(line) + '\n' for line in expected_lines], [' '.join(line) + '\n' for line in lines], ): s.append(diffline) raise Failure(''.join(s)) @Cvs2SvnTestFunction def symbol_hints(): "test --symbol-hints for setting branch/tag" conv = ensure_conversion( 'symbol-mess', symbol_hints_file='symbol-mess-symbol-hints.txt', ) if not conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if not conv.path_exists('tags', 'MOSTLY_TAG'): raise Failure() conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), ( ('/tags/MOSTLY_TAG (from /trunk:2)', 'A'), )) conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), ( ('/branches/BRANCH_WITH_COMMIT (from /trunk:2)', 'A'), )) conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), ( ('/branches/MOSTLY_BRANCH (from /trunk:2)', 'A'), )) @Cvs2SvnTestFunction def parent_hints(): "test --symbol-hints for setting parent" conv = ensure_conversion( 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints.txt', ) conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), ( ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'), )) @Cvs2SvnTestFunction def parent_hints_invalid(): "test --symbol-hints with an invalid parent" # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.; # this symbol hints file sets the preferred parent to BRANCH # instead: conv = ensure_conversion( 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-invalid.txt', error_re=( r"BLOCKED_BY_BRANCH is not a valid parent for BRANCH_WITH_COMMIT" ), ) @Cvs2SvnTestFunction def parent_hints_wildcards(): "test --symbol-hints wildcards" # BRANCH_WITH_COMMIT is usually determined to branch from .trunk.; # this symbol hints file sets the preferred parent to BRANCH # instead: conv = ensure_conversion( 'symbol-mess', symbol_hints_file='symbol-mess-parent-hints-wildcards.txt', ) conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), ( ('/%(branches)s/BRANCH_WITH_COMMIT (from /branches/BRANCH:8)', 'A'), )) @Cvs2SvnTestFunction def path_hints(): "test --symbol-hints for setting svn paths" conv = ensure_conversion( 'symbol-mess', symbol_hints_file='symbol-mess-path-hints.txt', ) conv.logs[1].check('Standard project directories initialized by cvs2svn.', ( ('/trunk', 'A'), ('/a', 'A'), ('/a/strange', 'A'), ('/a/strange/trunk', 'A'), ('/a/strange/trunk/path', 'A'), ('/branches', 'A'), ('/tags', 'A'), )) conv.logs[3].check(sym_log_msg('MOSTLY_TAG', 1), ( ('/special', 'A'), ('/special/tag', 'A'), ('/special/tag/path (from /a/strange/trunk/path:2)', 'A'), )) conv.logs[9].check(sym_log_msg('BRANCH_WITH_COMMIT'), ( ('/special/other', 'A'), ('/special/other/branch', 'A'), ('/special/other/branch/path (from /a/strange/trunk/path:2)', 'A'), )) conv.logs[10].check(sym_log_msg('MOSTLY_BRANCH'), ( ('/special/branch', 'A'), ('/special/branch/path (from /a/strange/trunk/path:2)', 'A'), )) @Cvs2SvnTestFunction def issue_99(): "test problem from issue 99" conv = ensure_conversion('issue-99') @Cvs2SvnTestFunction def issue_100(): "test problem from issue 100" conv = ensure_conversion('issue-100') file1 = conv.get_wc('trunk', 'file1.txt') if file(file1).read() != 'file1.txt<1.2>\n': raise Failure() @Cvs2SvnTestFunction def issue_106(): "test problem from issue 106" conv = ensure_conversion('issue-106') @Cvs2SvnTestFunction def options_option(): "use of the --options option" conv = ensure_conversion('main', options_file='cvs2svn.options') @Cvs2SvnTestFunction def multiproject(): "multiproject conversion" conv = ensure_conversion( 'main', options_file='cvs2svn-multiproject.options' ) conv.logs[1].check('Standard project directories initialized by cvs2svn.', ( ('/partial-prune', 'A'), ('/partial-prune/trunk', 'A'), ('/partial-prune/branches', 'A'), ('/partial-prune/tags', 'A'), ('/partial-prune/releases', 'A'), )) @Cvs2SvnTestFunction def crossproject(): "multiproject conversion with cross-project commits" conv = ensure_conversion( 'main', options_file='cvs2svn-crossproject.options' ) @Cvs2SvnTestFunction def tag_with_no_revision(): "tag defined but revision is deleted" conv = ensure_conversion('tag-with-no-revision') @Cvs2SvnTestFunction def delete_cvsignore(): "svn:ignore should vanish when .cvsignore does" # This is issue #81. conv = ensure_conversion('delete-cvsignore') wc_tree = conv.get_wc_tree() props = props_for_path(wc_tree, 'trunk/proj') if props.has_key('svn:ignore'): raise Failure() @Cvs2SvnTestFunction def repeated_deltatext(): "ignore repeated deltatext blocks with warning" conv = ensure_conversion( 'repeated-deltatext', verbosity='-qq', error_re=r'.*Deltatext block for revision 1.1 appeared twice', ) @Cvs2SvnTestFunction def nasty_graphs(): "process some nasty dependency graphs" # It's not how well the bear can dance, but that the bear can dance # at all: conv = ensure_conversion('nasty-graphs') @XFail_deco() @Cvs2SvnTestFunction def tagging_after_delete(): "optimal tag after deleting files" conv = ensure_conversion('tagging-after-delete') # tag should be 'clean', no deletes log = conv.find_tag_log('tag1') expected = ( ('/%(tags)s/tag1 (from /%(trunk)s:3)', 'A'), ) log.check_changes(expected) @Cvs2SvnTestFunction def crossed_branches(): "branches created in inconsistent orders" conv = ensure_conversion('crossed-branches') @Cvs2SvnTestFunction def file_directory_conflict(): "error when filename conflicts with directory name" conv = ensure_conversion( 'file-directory-conflict', error_re=r'.*Directory name conflicts with filename', ) @Cvs2SvnTestFunction def attic_directory_conflict(): "error when attic filename conflicts with dirname" # This tests the problem reported in issue #105. conv = ensure_conversion( 'attic-directory-conflict', error_re=r'.*Directory name conflicts with filename', ) @Cvs2SvnTestFunction def use_rcs(): "verify that --use-rcs and --use-internal-co agree" rcs_conv = ensure_conversion( 'main', args=['--use-rcs', '--default-eol=native'], dumpfile='use-rcs-rcs.dump', ) conv = ensure_conversion( 'main', args=['--default-eol=native'], dumpfile='use-rcs-int.dump', ) if conv.output_found(r'WARNING\: internal problem\: leftover revisions'): raise Failure() rcs_lines = list(open(rcs_conv.dumpfile, 'rb')) lines = list(open(conv.dumpfile, 'rb')) # Compare all lines following the repository UUID: if lines[3:] != rcs_lines[3:]: raise Failure() @Cvs2SvnTestFunction def internal_co_exclude(): "verify that --use-internal-co --exclude=... works" rcs_conv = ensure_conversion( 'internal-co', args=['--use-rcs', '--exclude=BRANCH', '--default-eol=native'], dumpfile='internal-co-exclude-rcs.dump', ) conv = ensure_conversion( 'internal-co', args=['--exclude=BRANCH', '--default-eol=native'], dumpfile='internal-co-exclude-int.dump', ) if conv.output_found(r'WARNING\: internal problem\: leftover revisions'): raise Failure() rcs_lines = list(open(rcs_conv.dumpfile, 'rb')) lines = list(open(conv.dumpfile, 'rb')) # Compare all lines following the repository UUID: if lines[3:] != rcs_lines[3:]: raise Failure() @Cvs2SvnTestFunction def internal_co_trunk_only(): "verify that --use-internal-co --trunk-only works" conv = ensure_conversion( 'internal-co', args=['--trunk-only', '--default-eol=native'], ) if conv.output_found(r'WARNING\: internal problem\: leftover revisions'): raise Failure() @Cvs2SvnTestFunction def leftover_revs(): "check for leftover checked-out revisions" conv = ensure_conversion( 'leftover-revs', args=['--exclude=BRANCH', '--default-eol=native'], ) if conv.output_found(r'WARNING\: internal problem\: leftover revisions'): raise Failure() @Cvs2SvnTestFunction def requires_internal_co(): "test that internal co can do more than RCS" # See issues 4, 11 for the bugs whose regression we're testing for. # Unlike in requires_cvs above, issue 29 is not covered. conv = ensure_conversion('requires-cvs') atsign_contents = file(conv.get_wc("trunk", "atsign-add")).read() if atsign_contents[-1:] == "@": raise Failure() if not (conv.logs[6].author == "William Lyon Phelps III" and conv.logs[5].author == "j random"): raise Failure() @Cvs2SvnTestFunction def internal_co_keywords(): "test that internal co handles keywords correctly" conv_ic = ensure_conversion('internal-co-keywords', args=["--keywords-off"]) conv_cvs = ensure_conversion('internal-co-keywords', args=["--use-cvs", "--keywords-off"]) ko_ic = file(conv_ic.get_wc('trunk', 'dir', 'ko.txt')).read() ko_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'ko.txt')).read() kk_ic = file(conv_ic.get_wc('trunk', 'dir', 'kk.txt')).read() kk_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kk.txt')).read() kv_ic = file(conv_ic.get_wc('trunk', 'dir', 'kv.txt')).read() kv_cvs = file(conv_cvs.get_wc('trunk', 'dir', 'kv.txt')).read() # Ensure proper "/Attic" expansion of $Source$ keyword in files # which are in a deleted state in trunk del_ic = file(conv_ic.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read() del_cvs = file(conv_cvs.get_wc('branches/b', 'dir', 'kv-deleted.txt')).read() if ko_ic != ko_cvs: raise Failure() if kk_ic != kk_cvs: raise Failure() if del_ic != del_cvs: raise Failure() # The date format changed between cvs and co ('/' instead of '-'). # Accept either one: date_substitution_re = re.compile(r' ([0-9]*)-([0-9]*)-([0-9]*) ') if kv_ic != kv_cvs \ and date_substitution_re.sub(r' \1/\2/\3 ', kv_ic) != kv_cvs: raise Failure() @Cvs2SvnTestFunction def timestamp_chaos(): "test timestamp adjustments" conv = ensure_conversion('timestamp-chaos') # The times are expressed here in UTC: times = [ '2007-01-01 21:00:00', # Initial commit '2007-01-01 21:00:00', # revision 1.1 of both files '2007-01-01 21:00:01', # revision 1.2 of file1.txt, adjusted forwards '2007-01-01 21:00:02', # revision 1.2 of file2.txt, adjusted backwards '2007-01-01 22:00:00', # revision 1.3 of both files ] # Convert the times to seconds since the epoch, in UTC: times = [calendar.timegm(svn_strptime(t)) for t in times] for i in range(len(times)): if abs(conv.logs[i + 1].date - times[i]) > 0.1: raise Failure() @Cvs2SvnTestFunction def symlinks(): "convert a repository that contains symlinks" # This is a test for issue #97. proj = os.path.join(test_data_dir, 'symlinks-cvsrepos', 'proj') links = [ ( os.path.join('..', 'file.txt,v'), os.path.join(proj, 'dir1', 'file.txt,v'), ), ( 'dir1', os.path.join(proj, 'dir2'), ), ] try: os.symlink except AttributeError: # Apparently this OS doesn't support symlinks, so skip test. raise svntest.Skip() try: for (src,dst) in links: os.symlink(src, dst) conv = ensure_conversion('symlinks') conv.logs[2].check('', ( ('/%(trunk)s/proj', 'A'), ('/%(trunk)s/proj/file.txt', 'A'), ('/%(trunk)s/proj/dir1', 'A'), ('/%(trunk)s/proj/dir1/file.txt', 'A'), ('/%(trunk)s/proj/dir2', 'A'), ('/%(trunk)s/proj/dir2/file.txt', 'A'), )) finally: for (src,dst) in links: os.remove(dst) @Cvs2SvnTestFunction def empty_trunk_path(): "allow --trunk to be empty if --trunk-only" # This is a test for issue #53. conv = ensure_conversion( 'main', args=['--trunk-only', '--trunk='], ) @Cvs2SvnTestFunction def preferred_parent_cycle(): "handle a cycle in branch parent preferences" conv = ensure_conversion('preferred-parent-cycle') @Cvs2SvnTestFunction def branch_from_empty_dir(): "branch from an empty directory" conv = ensure_conversion('branch-from-empty-dir') @Cvs2SvnTestFunction def trunk_readd(): "add a file on a branch then on trunk" conv = ensure_conversion('trunk-readd') @Cvs2SvnTestFunction def branch_from_deleted_1_1(): "branch from a 1.1 revision that will be deleted" conv = ensure_conversion('branch-from-deleted-1-1') conv.logs[5].check('Adding b.txt:1.1.2.1', ( ('/%(branches)s/BRANCH1/proj/b.txt', 'A'), )) conv.logs[6].check('Adding b.txt:1.1.4.1', ( ('/%(branches)s/BRANCH2/proj/b.txt', 'A'), )) conv.logs[7].check('Adding b.txt:1.2', ( ('/%(trunk)s/proj/b.txt', 'A'), )) conv.logs[8].check('Adding c.txt:1.1.2.1', ( ('/%(branches)s/BRANCH1/proj/c.txt', 'A'), )) conv.logs[9].check('Adding c.txt:1.1.4.1', ( ('/%(branches)s/BRANCH2/proj/c.txt', 'A'), )) @Cvs2SvnTestFunction def add_on_branch(): "add a file on a branch using newer CVS" conv = ensure_conversion('add-on-branch') conv.logs[6].check('Adding b.txt:1.1', ( ('/%(trunk)s/proj/b.txt', 'A'), )) conv.logs[7].check('Adding b.txt:1.1.2.2', ( ('/%(branches)s/BRANCH1/proj/b.txt', 'A'), )) conv.logs[8].check('Adding c.txt:1.1', ( ('/%(trunk)s/proj/c.txt', 'A'), )) conv.logs[9].check('Removing c.txt:1.2', ( ('/%(trunk)s/proj/c.txt', 'D'), )) conv.logs[10].check('Adding c.txt:1.2.2.2', ( ('/%(branches)s/BRANCH2/proj/c.txt', 'A'), )) conv.logs[11].check('Adding d.txt:1.1', ( ('/%(trunk)s/proj/d.txt', 'A'), )) conv.logs[12].check('Adding d.txt:1.1.2.2', ( ('/%(branches)s/BRANCH3/proj/d.txt', 'A'), )) @Cvs2SvnTestFunction def main_git(): "test output in git-fast-import format" # Note: To test importing into git, do # # ./run-tests # rm -rf cvs2svn-tmp/main.git # git init --bare cvs2svn-tmp/main.git # cd cvs2svn-tmp/main.git # cat ../git-{blob,dump}.dat | git fast-import # # Or, to load the dumpfiles separately: # # cat ../git-blob.dat | git fast-import --export-marks=../git-marks.dat # cat ../git-dump.dat | git fast-import --import-marks=../git-marks.dat # # Then use "gitk --all", "git log", etc. to test the contents of the # repository or "git clone" to make a non-bare clone. # We don't have the infrastructure to check that the resulting git # repository is correct, so we just check that the conversion runs # to completion: conv = GitConversion('main', None, [ '--blobfile=cvs2svn-tmp/git-blob.dat', '--dumpfile=cvs2svn-tmp/git-dump.dat', '--username=cvs2git', 'test-data/main-cvsrepos', ]) @Cvs2SvnTestFunction def main_git2(): "test cvs2git --use-external-blob-generator option" # See comment in main_git() for more information. conv = GitConversion('main', None, [ '--use-external-blob-generator', '--blobfile=cvs2svn-tmp/blobfile.out', '--dumpfile=cvs2svn-tmp/dumpfile.out', '--username=cvs2git', 'test-data/main-cvsrepos', ]) @Cvs2SvnTestFunction def main_git_merged(): "cvs2git with no blobfile" # Note: To test importing into git, do # # ./run-tests # rm -rf cvs2svn-tmp/main.git # git init --bare cvs2svn-tmp/main.git # cd cvs2svn-tmp/main.git # cat ../git-dump.dat | git fast-import conv = GitConversion('main', None, [ '--dumpfile=cvs2svn-tmp/git-dump.dat', '--username=cvs2git', 'test-data/main-cvsrepos', ]) @Cvs2SvnTestFunction def main_git2_merged(): "cvs2git external with no blobfile" # See comment in main_git_merged() for more information. conv = GitConversion('main', None, [ '--use-external-blob-generator', '--dumpfile=cvs2svn-tmp/dumpfile.out', '--username=cvs2git', 'test-data/main-cvsrepos', ]) @Cvs2SvnTestFunction def git_options(): "test cvs2git using options file" conv = GitConversion('main', None, [], options_file='cvs2git.options') @Cvs2SvnTestFunction def main_hg(): "output in git-fast-import format with inline data" # The output should be suitable for import by Mercurial. # We don't have the infrastructure to check that the resulting # Mercurial repository is correct, so we just check that the # conversion runs to completion: conv = GitConversion('main', None, [], options_file='cvs2hg.options') @Cvs2SvnTestFunction def invalid_symbol(): "a symbol with the incorrect format" conv = ensure_conversion( 'invalid-symbol', verbosity='-qq', error_re=r".*branch 'SYMBOL' references invalid revision 1$", ) @Cvs2SvnTestFunction def invalid_symbol_ignore(): "ignore a symbol using a SymbolMapper" conv = ensure_conversion( 'invalid-symbol', options_file='cvs2svn-ignore.options' ) @Cvs2SvnTestFunction def invalid_symbol_ignore2(): "ignore a symbol using an IgnoreSymbolTransform" conv = ensure_conversion( 'invalid-symbol', options_file='cvs2svn-ignore2.options' ) class EOLVariants(Cvs2SvnTestCase): "handle various --eol-style options" eol_style_strings = { 'LF' : '\n', 'CR' : '\r', 'CRLF' : '\r\n', 'native' : '\n', } def __init__(self, eol_style): self.eol_style = eol_style self.dumpfile = 'eol-variants-%s.dump' % (self.eol_style,) Cvs2SvnTestCase.__init__( self, 'eol-variants', variant=self.eol_style, dumpfile=self.dumpfile, args=[ '--default-eol=%s' % (self.eol_style,), ], ) def run(self, sbox): conv = self.ensure_conversion() dump_contents = open(conv.dumpfile, 'rb').read() expected_text = self.eol_style_strings[self.eol_style].join( ['line 1', 'line 2', '\n\n'] ) if not dump_contents.endswith(expected_text): raise Failure() @Cvs2SvnTestFunction def no_revs_file(): "handle a file with no revisions (issue #80)" conv = ensure_conversion('no-revs-file') @Cvs2SvnTestFunction def mirror_keyerror_test(): "a case that gave KeyError in SVNRepositoryMirror" conv = ensure_conversion('mirror-keyerror') @Cvs2SvnTestFunction def exclude_ntdb_test(): "exclude a non-trunk default branch" symbol_info_file = os.path.join(tmp_dir, 'exclude-ntdb-symbol-info.txt') conv = ensure_conversion( 'exclude-ntdb', args=[ '--write-symbol-info=%s' % (symbol_info_file,), '--exclude=branch3', '--exclude=tag3', '--exclude=vendortag3', '--exclude=vendorbranch', ], ) @Cvs2SvnTestFunction def mirror_keyerror2_test(): "a case that gave KeyError in RepositoryMirror" conv = ensure_conversion('mirror-keyerror2') @Cvs2SvnTestFunction def mirror_keyerror3_test(): "a case that gave KeyError in RepositoryMirror" conv = ensure_conversion('mirror-keyerror3') @XFail_deco() @Cvs2SvnTestFunction def add_cvsignore_to_branch_test(): "check adding .cvsignore to an existing branch" # This a test for issue #122. conv = ensure_conversion('add-cvsignore-to-branch') wc_tree = conv.get_wc_tree() trunk_props = props_for_path(wc_tree, 'trunk/dir') if trunk_props['svn:ignore'] != '*.o\n\n': raise Failure() branch_props = props_for_path(wc_tree, 'branches/BRANCH/dir') if branch_props['svn:ignore'] != '*.o\n\n': raise Failure() @Cvs2SvnTestFunction def missing_deltatext(): "a revision's deltatext is missing" # This is a type of RCS file corruption that has been observed. conv = ensure_conversion( 'missing-deltatext', error_re=( r"ERROR\: .* has no deltatext section for revision 1\.1\.4\.4" ), ) @Cvs2SvnTestFunction def transform_unlabeled_branch_name(): "transform name of unlabeled branch" conv = ensure_conversion( 'unlabeled-branch', args=[ '--symbol-transform=unlabeled-1.1.4:BRANCH2', ], ) if conv.path_exists('branches', 'unlabeled-1.1.4'): raise Failure('Branch unlabeled-1.1.4 not excluded') if not conv.path_exists('branches', 'BRANCH2'): raise Failure('Branch BRANCH2 not found') @Cvs2SvnTestFunction def ignore_unlabeled_branch(): "ignoring an unlabeled branch is not allowed" conv = ensure_conversion( 'unlabeled-branch', options_file='cvs2svn-ignore.options', error_re=( r"ERROR\: The unlabeled branch \'unlabeled\-1\.1\.4\' " r"in \'.*\' contains commits" ), ) @Cvs2SvnTestFunction def exclude_unlabeled_branch(): "exclude unlabeled branch" conv = ensure_conversion( 'unlabeled-branch', args=['--exclude=unlabeled-.*'], ) if conv.path_exists('branches', 'unlabeled-1.1.4'): raise Failure('Branch unlabeled-1.1.4 not excluded') @Cvs2SvnTestFunction def unlabeled_branch_name_collision(): "transform unlabeled branch to same name as branch" conv = ensure_conversion( 'unlabeled-branch', args=[ '--symbol-transform=unlabeled-1.1.4:BRANCH', ], error_re=( r"ERROR\: Symbol name \'BRANCH\' is already used" ), ) @Cvs2SvnTestFunction def collision_with_unlabeled_branch_name(): "transform branch to same name as unlabeled branch" conv = ensure_conversion( 'unlabeled-branch', args=[ '--symbol-transform=BRANCH:unlabeled-1.1.4', ], error_re=( r"ERROR\: Symbol name \'unlabeled\-1\.1\.4\' is already used" ), ) @Cvs2SvnTestFunction def many_deletes(): "a repo with many removable dead revisions" conv = ensure_conversion('many-deletes') conv.logs[5].check('Add files on BRANCH', ( ('/%(branches)s/BRANCH/proj/b.txt', 'A'), )) conv.logs[6].check('Add files on BRANCH2', ( ('/%(branches)s/BRANCH2/proj/b.txt', 'A'), ('/%(branches)s/BRANCH2/proj/c.txt', 'A'), ('/%(branches)s/BRANCH2/proj/d.txt', 'A'), )) cvs_description = Cvs2SvnPropertiesTestCase( 'main', doc='test handling of CVS file descriptions', props_to_test=['cvs:description'], expected_props=[ ('trunk/proj/default', ['This is an example file description.']), ('trunk/proj/sub1/default', [None]), ]) @Cvs2SvnTestFunction def include_empty_directories(): "test --include-empty-directories option" conv = ensure_conversion( 'empty-directories', args=['--include-empty-directories'], ) conv.logs[1].check('Standard project directories', ( ('/%(trunk)s', 'A'), ('/%(branches)s', 'A'), ('/%(tags)s', 'A'), ('/%(trunk)s/root-empty-directory', 'A'), ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'), )) conv.logs[3].check('Add b.txt.', ( ('/%(trunk)s/direct', 'A'), ('/%(trunk)s/direct/b.txt', 'A'), ('/%(trunk)s/direct/empty-directory', 'A'), ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'), )) conv.logs[4].check('Add c.txt.', ( ('/%(trunk)s/indirect', 'A'), ('/%(trunk)s/indirect/subdirectory', 'A'), ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'), ('/%(trunk)s/indirect/empty-directory', 'A'), ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'), )) conv.logs[5].check('Remove b.txt', ( ('/%(trunk)s/direct', 'D'), )) conv.logs[6].check('Remove c.txt', ( ('/%(trunk)s/indirect', 'D'), )) conv.logs[7].check('Re-add b.txt.', ( ('/%(trunk)s/direct', 'A'), ('/%(trunk)s/direct/b.txt', 'A'), ('/%(trunk)s/direct/empty-directory', 'A'), ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'), )) conv.logs[8].check('Re-add c.txt.', ( ('/%(trunk)s/indirect', 'A'), ('/%(trunk)s/indirect/subdirectory', 'A'), ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'), ('/%(trunk)s/indirect/empty-directory', 'A'), ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'), )) conv.logs[9].check('This commit was manufactured', ( ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'), )) conv.logs[10].check('This commit was manufactured', ( ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'), )) conv.logs[11].check('Import d.txt.', ( ('/%(branches)s/VENDORBRANCH', 'A'), ('/%(branches)s/VENDORBRANCH/import', 'A'), ('/%(branches)s/VENDORBRANCH/import/d.txt', 'A'), ('/%(branches)s/VENDORBRANCH/root-empty-directory', 'A'), ('/%(branches)s/VENDORBRANCH/root-empty-directory/empty-subdirectory', 'A'), ('/%(branches)s/VENDORBRANCH/import/empty-directory', 'A'), ('/%(branches)s/VENDORBRANCH/import/empty-directory/empty-subdirectory', 'A'), )) conv.logs[12].check('This commit was generated', ( ('/%(trunk)s/import', 'A'), ('/%(trunk)s/import/d.txt ' '(from /%(branches)s/VENDORBRANCH/import/d.txt:11)', 'A'), ('/%(trunk)s/import/empty-directory', 'A'), ('/%(trunk)s/import/empty-directory/empty-subdirectory', 'A'), )) @Cvs2SvnTestFunction def include_empty_directories_no_prune(): "test --include-empty-directories with --no-prune" conv = ensure_conversion( 'empty-directories', args=['--include-empty-directories', '--no-prune'], ) conv.logs[1].check('Standard project directories', ( ('/%(trunk)s', 'A'), ('/%(branches)s', 'A'), ('/%(tags)s', 'A'), ('/%(trunk)s/root-empty-directory', 'A'), ('/%(trunk)s/root-empty-directory/empty-subdirectory', 'A'), )) conv.logs[3].check('Add b.txt.', ( ('/%(trunk)s/direct', 'A'), ('/%(trunk)s/direct/b.txt', 'A'), ('/%(trunk)s/direct/empty-directory', 'A'), ('/%(trunk)s/direct/empty-directory/empty-subdirectory', 'A'), )) conv.logs[4].check('Add c.txt.', ( ('/%(trunk)s/indirect', 'A'), ('/%(trunk)s/indirect/subdirectory', 'A'), ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'), ('/%(trunk)s/indirect/empty-directory', 'A'), ('/%(trunk)s/indirect/empty-directory/empty-subdirectory', 'A'), )) conv.logs[5].check('Remove b.txt', ( ('/%(trunk)s/direct/b.txt', 'D'), )) conv.logs[6].check('Remove c.txt', ( ('/%(trunk)s/indirect/subdirectory/c.txt', 'D'), )) conv.logs[7].check('Re-add b.txt.', ( ('/%(trunk)s/direct/b.txt', 'A'), )) conv.logs[8].check('Re-add c.txt.', ( ('/%(trunk)s/indirect/subdirectory/c.txt', 'A'), )) conv.logs[9].check('This commit was manufactured', ( ('/%(tags)s/TAG (from /%(trunk)s:8)', 'A'), )) conv.logs[10].check('This commit was manufactured', ( ('/%(branches)s/BRANCH (from /%(trunk)s:8)', 'A'), )) @Cvs2SvnTestFunction def exclude_symbol_default(): "test 'exclude' symbol default" conv = ensure_conversion( 'symbol-mess', args=['--symbol-default=exclude']) if conv.path_exists('tags', 'MOSTLY_BRANCH') \ or conv.path_exists('branches', 'MOSTLY_BRANCH'): raise Failure() if conv.path_exists('tags', 'MOSTLY_TAG') \ or conv.path_exists('branches', 'MOSTLY_TAG'): raise Failure() @Cvs2SvnTestFunction def add_on_branch2(): "another add-on-branch test case" conv = ensure_conversion('add-on-branch2') if len(conv.logs) != 2: raise Failure() conv.logs[2].check('add file on branch', ( ('/%(branches)s/BRANCH', 'A'), ('/%(branches)s/BRANCH/file1', 'A'), )) @Cvs2SvnTestFunction def branch_from_vendor_branch(): "branch from vendor branch" ensure_conversion( 'branch-from-vendor-branch', symbol_hints_file='branch-from-vendor-branch-symbol-hints.txt', ) @Cvs2SvnTestFunction def strange_default_branch(): "default branch too deep in the hierarchy" ensure_conversion( 'strange-default-branch', error_re=( r'ERROR\: The default branch 1\.2\.4\.3\.2\.1\.2 ' r'in file .* is not a top-level branch' ), ) @Cvs2SvnTestFunction def move_parent(): "graft onto preferred parent that was itself moved" conv = ensure_conversion( 'move-parent', ) conv.logs[2].check('first', ( ('/%(trunk)s/file1', 'A'), ('/%(trunk)s/file2', 'A'), )) conv.logs[3].check('This commit was manufactured', ( ('/%(branches)s/b2 (from /%(trunk)s:2)', 'A'), )) conv.logs[4].check('second', ( ('/%(branches)s/b2/file1', 'M'), )) conv.logs[5].check('This commit was manufactured', ( ('/%(branches)s/b1 (from /%(branches)s/b2:4)', 'A'), )) # b2 and b1 are equally good parents for b3, so accept either one. # (Currently, cvs2svn chooses b1 as the preferred parent because it # comes earlier than b2 in alphabetical order.) try: conv.logs[6].check('This commit was manufactured', ( ('/%(branches)s/b3 (from /%(branches)s/b1:5)', 'A'), )) except Failure: conv.logs[6].check('This commit was manufactured', ( ('/%(branches)s/b3 (from /%(branches)s/b2:4)', 'A'), )) @Cvs2SvnTestFunction def log_message_eols(): "nonstandard EOLs in log messages" conv = ensure_conversion( 'log-message-eols', ) conv.logs[2].check('The CRLF at the end of this line\nshould', ( ('/%(trunk)s/lottalogs', 'A'), )) conv.logs[3].check('The CR at the end of this line\nshould', ( ('/%(trunk)s/lottalogs', 'M'), )) @Cvs2SvnTestFunction def missing_vendor_branch(): "default branch not present in RCS file" conv = ensure_conversion( 'missing-vendor-branch', verbosity='-qq', error_re=r'.*vendor branch \'1\.1\.1\' is not present in file and will be ignored', ) @Cvs2SvnTestFunction def newphrases(): "newphrases in RCS files" ensure_conversion( 'newphrases', ) @Cvs2SvnTestFunction def vendor_1_1_not_root(): "supposed vendor 1.1 commit is not a root commit" ensure_conversion( 'vendor-1-1-non-root', ) ######################################################################## # Run the tests # list all tests here, starting with None: test_list = [ None, # 1: show_usage, cvs2svn_manpage, cvs2git_manpage, cvs2hg_manpage, attr_exec, space_fname, two_quick, PruneWithCare(), PruneWithCare(variant=1, trunk='a', branches='b', tags='c'), # 10: PruneWithCare(variant=2, trunk='a/1', branches='b/1', tags='c/1'), PruneWithCare(variant=3, trunk='a/1', branches='a/2', tags='a/3'), interleaved_commits, simple_commits, SimpleTags(), SimpleTags(variant=1, trunk='a', branches='b', tags='c'), SimpleTags(variant=2, trunk='a/1', branches='b/1', tags='c/1'), SimpleTags(variant=3, trunk='a/1', branches='a/2', tags='a/3'), simple_branch_commits, mixed_time_tag, # 20: mixed_time_branch_with_added_file, mixed_commit, split_time_branch, bogus_tag, overlapping_branch, PhoenixBranch(), PhoenixBranch(variant=1, trunk='a/1', branches='b/1', tags='c/1'), ctrl_char_in_log, overdead, NoTrunkPrune(), # 30: NoTrunkPrune(variant=1, trunk='a', branches='b', tags='c'), NoTrunkPrune(variant=2, trunk='a/1', branches='b/1', tags='c/1'), NoTrunkPrune(variant=3, trunk='a/1', branches='a/2', tags='a/3'), double_delete, split_branch, resync_misgroups, TaggedBranchAndTrunk(), TaggedBranchAndTrunk(variant=1, trunk='a/1', branches='a/2', tags='a/3'), enroot_race, enroot_race_obo, # 40: BranchDeleteFirst(), BranchDeleteFirst(variant=1, trunk='a/1', branches='a/2', tags='a/3'), nonascii_cvsignore, nonascii_filenames, UnicodeAuthor( warning_expected=1), UnicodeAuthor( warning_expected=0, variant='encoding', args=['--encoding=utf_8']), UnicodeAuthor( warning_expected=0, variant='fallback-encoding', args=['--fallback-encoding=utf_8']), UnicodeLog( warning_expected=1), UnicodeLog( warning_expected=0, variant='encoding', args=['--encoding=utf_8']), UnicodeLog( warning_expected=0, variant='fallback-encoding', args=['--fallback-encoding=utf_8']), # 50: vendor_branch_sameness, vendor_branch_trunk_only, default_branches, default_branches_trunk_only, default_branch_and_1_2, compose_tag_three_sources, pass5_when_to_fill, PeerPathPruning(), PeerPathPruning(variant=1, trunk='a/1', branches='a/2', tags='a/3'), EmptyTrunk(), # 60: EmptyTrunk(variant=1, trunk='a', branches='b', tags='c'), EmptyTrunk(variant=2, trunk='a/1', branches='a/2', tags='a/3'), no_spurious_svn_commits, invalid_closings_on_trunk, individual_passes, resync_bug, branch_from_default_branch, file_in_attic_too, retain_file_in_attic_too, symbolic_name_filling_guide, # 70: eol_mime1, eol_mime2, eol_mime3, eol_mime4, cvs_revnums_off, cvs_revnums_on, keywords, ignore, requires_cvs, questionable_branch_names, # 80: questionable_tag_names, revision_reorder_bug, exclude, vendor_branch_delete_add, resync_pass2_pull_forward, native_eol, double_fill, double_fill2, resync_pass2_push_backward, double_add, # 90: bogus_branch_copy, nested_ttb_directories, auto_props_ignore_case, ctrl_char_in_filename, commit_dependencies, show_help_passes, multiple_tags, multiply_defined_symbols, multiply_defined_symbols_renamed, multiply_defined_symbols_ignored, # 100: repeatedly_defined_symbols, double_branch_delete, symbol_mismatches, overlook_symbol_mismatches, force_symbols, commit_blocks_tags, blocked_excludes, unblock_blocked_excludes, regexp_force_symbols, heuristic_symbol_default, # 110: branch_symbol_default, tag_symbol_default, symbol_transform, write_symbol_info, symbol_hints, parent_hints, parent_hints_invalid, parent_hints_wildcards, path_hints, issue_99, # 120: issue_100, issue_106, options_option, multiproject, crossproject, tag_with_no_revision, delete_cvsignore, repeated_deltatext, nasty_graphs, tagging_after_delete, # 130: crossed_branches, file_directory_conflict, attic_directory_conflict, use_rcs, internal_co_exclude, internal_co_trunk_only, internal_co_keywords, leftover_revs, requires_internal_co, timestamp_chaos, # 140: symlinks, empty_trunk_path, preferred_parent_cycle, branch_from_empty_dir, trunk_readd, branch_from_deleted_1_1, add_on_branch, main_git, main_git2, main_git_merged, # 150: main_git2_merged, git_options, main_hg, invalid_symbol, invalid_symbol_ignore, invalid_symbol_ignore2, EOLVariants('LF'), EOLVariants('CR'), EOLVariants('CRLF'), EOLVariants('native'), # 160: no_revs_file, mirror_keyerror_test, exclude_ntdb_test, mirror_keyerror2_test, mirror_keyerror3_test, add_cvsignore_to_branch_test, missing_deltatext, transform_unlabeled_branch_name, ignore_unlabeled_branch, exclude_unlabeled_branch, # 170: unlabeled_branch_name_collision, collision_with_unlabeled_branch_name, many_deletes, cvs_description, include_empty_directories, include_empty_directories_no_prune, exclude_symbol_default, add_on_branch2, branch_from_vendor_branch, strange_default_branch, # 180: move_parent, log_message_eols, missing_vendor_branch, newphrases, vendor_1_1_not_root, ] if __name__ == '__main__': # Configure the environment for reproducable output from svn, etc. os.environ["LC_ALL"] = "C" # Unfortunately, there is no way under Windows to make Subversion # think that the local time zone is UTC, so we just work in the # local time zone. # The Subversion test suite code assumes it's being invoked from # within a working copy of the Subversion sources, and tries to use # the binaries in that tree. Since the cvs2svn tree never contains # a Subversion build, we just use the system's installed binaries. svntest.main.svn_binary = svn_binary svntest.main.svnlook_binary = svnlook_binary svntest.main.svnadmin_binary = svnadmin_binary svntest.main.svnversion_binary = svnversion_binary svntest.main.run_tests(test_list) # NOTREACHED ### End of file. cvs2svn-2.5.0/doc/0000775000175100017510000000000013206450463014744 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/doc/properties.txt0000664000175100017510000001135212203665123017700 0ustar mhaggermhagger00000000000000This file documents how file and revision properties are used in cvs2svn. cvs2svn allows arbitrary properties to be associated with CVSFile and CVSRevision instances. These properties are combined to form the effective properties for each CVSRevision. Properties set in a CVSRevision take precedence over properties set in the corresponding CVSFile. These properties can be set very flexibly by FilePropertySetter and RevisionPropertySetter objects, which in turn can be implemented arbitrarily and set via the conversion configuration file. Several types of PropertySetters are already provided, and examples of there use are shown in the example configuration files. The properties are determined early in the conversion and are retained for the duration of the conversion. CVSFile.properties holds properties that do not change for the life of the file; for example, whether keywords should be expanded in the file contents. CVSRevision.properties holds properties that can vary from one file revision to another. The only current example of a revision property is the cvs2svn:rev-num property. Properties whose names start with underscore are reserved for the internal use of cvs2svn. The properties can be used by backends for any purpose. Currently, they are used for two purposes: 1. Passing RevisionReaders information about how a file revision's contents should be transformed before being written to the new VCS. Please note that this does not necessarily correspond to how the revision contents will look after being checked out of the new VCS; for example, Subversion requires keywords to be *unexpanded* in the dumpfile stream if Subversion is going to expand them. These properties are: _keyword_handling -- How should RCS keywords be handled? 'untouched' -- The keywords should be output literally as they are recorded in the RCS file. Please note that this results in the keywords' being expanded the way they were when the revision was checked *in* to CVS, which typically reflects how CVS expanded them when the *previous* revision was checked *out* of CVS. This mode is appropriate for binary files. 'collapsed' -- The keywords should be collapsed in the output; e.g., "$Author: jrandom $" -> "$Author$". This mode is appropriate for output of non-binary files to Subversion (because Subversion re-expands the keywords itself) and might be useful for text files for other VCSs if you would like this history to be "clean" of keywords. 'expanded' -- The keywords should be expanded in the output the same way as CVS would expand keywords when checking out the revision; e.g., "$Author$" -> "$Author: jrandom $". If this value is used, keywords are expanded regardless of whether CVS considers the file to be a text file. This mode might be useful for outputting text files to other VCSs if you would like the content of historical revisions to be as similar as possible to the content as it would be checked out of CVS. 'deleted' -- The keywords and their values (and some surrounding whitespace?) should be deleted entirely. NOT YET IMPLEMENTED. 'replaced' -- The keywords should be deleted entirely and replaced by their values; e.g., "$Author$" -> "jrandom", like CVS's "-kv" option. This is not a very useful feature, but is listed for completeness. NOT YET IMPLEMENTED. _eol_fix -- Should end-of-line sequences be made uniform before committing to the target VCS? If this property is set to a non-empty value, then every end-of-line character sequence ('\n', '\r\n', or '\r') is converted to the specified value (which should obviously be a valid end-of-line character sequence). If this property is not set, then the end-of-line character sequences are output literally as they are recorded in the RCS file. 2. cvs2svn: Specifying Subversion versioned properties. Any properties that do not start with an underscore are converted into Subversion versioned properties on the associated file. By this mechanism, arbitrary Subversion properties can be set. A number of PropertySetters are provided to set common Subversion properties such as svn:mime-type, svn:eol-style, svn:executable, and svn:keywords. Other properties can be set via the AutoPropsPropertySetter or by implementing custom PropertySetters. cvs2svn-2.5.0/doc/design-notes.txt0000664000175100017510000007027612203665123020115 0ustar mhaggermhagger00000000000000 How cvs2svn Works ================= Theory and requirements ------ --- ------------ There are two main problem converting a CVS repository to SVN: - CVS does not record enough information to determine what actually happened to a repository. For example, CVS does not record: - Which file modifications were part of the same commit - The timestamp of tag and branch creations - Exactly which revision was the base of a branch (there is ambiguity between x.y, x.y.2.0, x.y.4.0, etc.) - When the default branch was changed (for example, from a vendor branch back to trunk). - The timestamps in a CVS archive are not reliable. It can easily happen that timestamps are not even monotonic, and large errors (for example due to a failing server clock battery) are not unusual. The absolutely crucial, sine qua non requirement of a conversion is that the dependency relationships within a file be honored, mainly: - A revision depends on its predecessor - A branch creation depends on the revision from which it branched, and commits on the branch depend on the branch creation - A tag creation depends on the revision being tagged These dependencies are reliably defined in the CVS repository, and they trump all others, so they are the scaffolding of the conversion. Moreover, it is highly desirable that the timestamps of the SVN commits be monotonically increasing. Within these constraints we also want the results of the conversion to resemble the history of the CVS repository as closely as possible. For example, the set of file changes grouped together in an SVN commit should be the same as the files changed within the corresponding CVS commit, insofar as that can be achieved in a manner that is consistent with the dependency requirements. And the SVN commit timestamps should recreate the time of the CVS commit as far as possible without violating the monotonicity requirement. The basic idea of the conversion is this: create the largest conceivable changesets, then split up changesets as necessary to break any cycles in the graph of changeset dependencies. When all cycles have been removed, then do a topological sort of the changesets (with ambiguities resolved using CVS timestamps) to determine a self-consistent changeset commit order. The quality of the conversion (not in terms of correctness, but in terms of minimizing the number of svn commits) is mostly determined by the cleverness of the heuristics used to split up cycles. And all of this has to be affordable, especially in terms of conversion time and RAM usage, for even the largest CVS repositories. Implementation -------------- A cvs2svn run consists of a number of passes. Each pass saves the data it produces to files on disk, so that a) we don't hold huge amounts of state in memory, and b) the conversion process is resumable. The intermediate files are referred to here by the symbolic constants holding their filenames in config.py. CollectRevsPass (formerly called pass1) =============== The goal of this pass is to collect from the CVS files all of the data that will be required for the conversion. If the --use-internal-co option was used, this pass also collects the file delta data; for -use-rcs or -use-cvs, the actual file contents are read again in OutputPass. To collect this data, we walk over the repository, collecting data about the RCS files into an instance of CollectData. Each RCS file is processed with rcsparse.parse(), which invokes callbacks from an instance of cvs2svn's _FileDataCollector class (which is a subclass of rcsparse.Sink). While a file is being processed, all of the data for the file (except for contents and log messages) is held in memory. When the file has been read completely, its data is converted into an instance of CVSFileItems, and this instance is manipulated a bit then pickled and stored to CVS_ITEMS_STORE. For each RCS file, the first thing the parser encounters is the administrative header, including the head revision, the principal branch, symbolic names, RCS comments, etc. The main thing that happens here is that _FileDataCollector.define_tag() is invoked on each symbolic name and its attached revision, so all the tags and branches of this file get collected. Next, the parser hits the revision summary section. That's the part of the RCS file that looks like this: 1.6 date 2002.06.12.04.54.12; author captnmark; state Exp; branches 1.6.2.1; next 1.5; 1.5 date 2002.05.28.18.02.11; author captnmark; state Exp; branches; next 1.4; [...] For each revision summary, _FileDataCollector.define_revision() is invoked, recording that revision's metadata in various variables of the _FileDataCollector class instance. Next, the parser encounters the *real* revision data, which has the log messages and file contents. For each revision, it invokes _FileDataCollector.set_revision_info(), which sets some more fields in _RevisionData. When the parser is done with the file, _ProjectDataCollector takes the resulting CVSFileItems object and manipulates it to handle some CVS features: - If the file had a vendor branch, make some adjustments to the file dependency graph to reflect implicit dependencies related to the vendor branch. Also delete the 1.1 revision in the usual case that it doesn't contain any useful information. - If the file was added on a branch rather than on trunk, then delete the "dead" 1.1 revision on trunk in the usual case that it doesn't contain any useful information. - If the file was added on a branch after it already existed on trunk, then recent versions of CVS add an extra "dead" revision on the branch. Remove this revision in the usual case that it doesn't contain any useful information, and sever the branch from trunk (since the branch version is independent of the trunk version). - If the conversion was started with the --trunk-only option, then 1. graft any non-trunk default branch revisions onto trunk (because they affect the history of the default branch), and 2. delete all branches and tags and all remaining branch revisions. Finally, the CVSFileItems instance is stored to a database and statistics about how symbols were used in the file are recorded. That's it -- the RCS file is done. When every CVS file is done, CollectRevsPass is complete, and: - The basic information about each project is stored to PROJECTS. - The basic information about each file and directory (filename, path, etc) is written as a pickled CVSPath instance to CVS_PATHS_DB. - Information about each symbol seen, along with statistics like how often it was used as a branch or tag, is written as a pickled symbol_statistics._Stat object to SYMBOL_STATISTICS. This includes the following information: ID -- a unique positive identifying integer NAME -- the symbol name TAG_CREATE_COUNT -- the number of times the symbol was used as a tag BRANCH_CREATE_COUNT -- the number of times the symbol was used as a branch BRANCH_COMMIT_COUNT -- the number of files in which there was a commit on a branch with this name. BRANCH_BLOCKERS -- the set of other symbols that ever sprouted from a branch with this name. (A symbol cannot be excluded from the conversion unless all of its blockers are also excluded.) POSSIBLE_PARENTS -- a count of in how many files each other branch could have served as the symbol's source. These data are used to look for inconsistencies in the use of symbols under CVS and to decide which symbols can be excluded or forced to be branches and/or tags. The POSSIBLE_PARENTS data is used to pick the "optimum" parent from which the symbol should sprout in as many files as possible. For a multiproject conversion, distinct symbol records (and IDs) are created for symbols in separate projects, even if they have the same name. This is to prevent symbols in separate projects from being filled at the same time. - Information about each CVS event is converted into a CVSItem instance and stored to CVS_ITEMS_STORE. There are several types of CVSItems: CVSRevision -- A specific revision of a specific CVS file. CVSBranch -- The creation of a branch tag in a specific CVS file. CVSTag -- The creation of a non-branch tag in a specific CVS file. The CVSItems are grouped into CVSFileItems instances, one per CVSFile. But a multi-file commit will still be scattered all over the place. - Selected metadata for each CVS revision, including the author and log message, is written to METADATA_INDEX_TABLE and METADATA_STORE. The purpose is twofold: first, to save space by not having to save this information multiple times, and second because CVSRevisions that have the same metadata are candidates to be combined into an SVN changeset. First, an SHA digest is created for each set of metadata. The digest is constructed so that CVSRevisions that can be combined are all mapped to the same digest. CVSRevisions that were part of a single CVS commit always have a common author and log message, therefore these fields are always included in the digest. Moreover: - if ctx.cross_project_commits is False, we avoid combining CVS revisions from separate projects by including the project.id in the digest. - if ctx.cross_branch_commits is False, we avoid combining CVS revisions from different branches by including the branch name in the digest. During the database creation phase, the database keeps track of a map digest (20-byte string) -> metadata_id (int) to allow the record for a set of metadata to be located efficiently. As data are collected, it stores a map metadata_id (int) -> (author, log_msg,) (tuple) into the database for use in future passes. CVSRevision records include the metadata_id. During this run, each CVSFile, Symbol, CVSItem, and metadata record is assigned an arbitrary unique ID that is used throughout the conversion to refer to it. CleanMetadataPass ================= Encode the cvs revision metadata as UTF-8, ensuring that all entries can be decoded using the chosen encodings. Output the results to METADATA_CLEAN_INDEX_TABLE and METADATA_CLEAN_STORE. CollateSymbolsPass ================== Use the symbol statistics collected in CollectRevsPass and any runtime options to determine which symbols should be treated as branches, which as tags, and which should be excluded from the conversion altogether. Create SYMBOL_DB, which contains a pickle of a list of TypedSymbol (Branch, Tag, or ExcludedSymbol) instances indicating how each symbol should be processed in the conversion. The IDs used for a TypedSymbol is the same as the ID allocated to the corresponding symbol in CollectRevsPass, so references in CVSItems do not have to be updated. FilterSymbolsPass ================= This pass works through the CVSFileItems instances stored in CVS_ITEMS_STORE, processing all of the items from each file as a group. (This is the last pass in which all of the CVSItems for a file are in memory at once.) It does the following things: - Exclude any symbols that CollateSymbolsPass determined should be excluded, and any revisions on such branches. Also delete references from other CVSItems to those that are being deleted. - Transform any branches to tags or vice versa, also depending on the results of CollateSymbolsPass, and fix up the references from other CVSItems. - Decide what line of development to use as the parent for each symbol in the file, and adjust the file's dependency tree accordingly. - For each CVSRevision, record the list of symbols that the revision opens and closes. - Write each surviving CVSRevision to CVS_REVS_DATAFILE. Each line of the file has the format METADATA_ID TIMESTAMP CVS_REVISION where TIMESTAMP is a fixed-width timestamp, and CVS_REVISION is the pickled CVSRevision in a format that does not contain any newlines. These summaries will be sorted in SortRevisionsPass then used by InitializeChangesetsPass to create preliminary RevisionChangesets. - Write the CVSSymbols to CVS_SYMBOLS_DATAFILE. Each line of the file has the format SYMBOL_ID CVS_SYMBOL where CVS_SYMBOL is the pickled CVSSymbol in a format that does not contain any newlines. This information will be sorted by SYMBOL_ID in SortSymbolsPass then used to create preliminary SymbolChangesets. - Invokes callback methods of the registered RevisionCollector. The purpose of RevisionCollectors and RevisionReaders is documented in the file revision-reader.txt. SortRevisionsPass ================= Sort CVS_REVS_DATAFILE (written by FilterSymbolsPass), creating CVS_REVS_SORTED_DATAFILE. The sort groups items that might be added to the same changeset together and, within a group, sorts revisions by timestamp. This step makes it easy for InitializeChangesetsPass to read the initial draft of RevisionChangesets straight from the file. SortSymbolsPass =============== Sort CVS_SYMBOLS_DATAFILE (written by FilterSymbolsPass), creating CVS_SYMBOLS_SORTED_DATAFILE. The sort groups together symbol items that might be added to the same changeset (though not in anything resembling chronological order). The output of this pass is used by InitializeChangesetsPass. InitializeChangesetsPass ======================== This pass creates first-draft changesets, splitting them using COMMIT_THRESHOLD and breaking up any revision changesets that have internal dependencies. The raw material for creating revision changesets is CVS_REVS_SORTED_DATAFILE, which already has CVSRevisions sorted in such a way that potential changesets are grouped together and sorted by date. The contents of this file are read line by line, and the corresponding CVSRevisions are accumulated into a changeset. Whenever the metadata_id changes, or whenever there is a time gap of more than COMMIT_THRESHOLD (currently set to 5 minutes) between CVSRevisions, then a new changeset is started. At this point a revision changeset can have internal dependencies if two commits were made to the same file with the same log message within COMMIT_THRESHOLD of each other. The next job of this pass is to split up changesets in such a way to break such internal dependencies. This is done by sorting the CVSRevisions within a changeset by timestamp, then choosing the split point that breaks the most internal dependencies. This procedure is continued recursively until there are no more dependencies internal to a single changeset. Analogously, the CVSSymbol items from CVS_SYMBOLS_SORTED_DATAFILE are grouped into symbol changesets. (Symbol changesets cannot have internal dependencies, so there is no need to break them up at this stage.) Finally, this pass writes a CVSItem database with the CVSItems written in order grouped by the preliminary changeset to which they belong. Even though the preliminary changesets still have to be split up to form final changesets, grouping the CVSItems this way improves the locality of disk accesses and thereby speeds up later passes. The result of this pass is two databases: - CVS_ITEM_TO_CHANGESET, which maps CVSItem ids to the id of the changeset containing the item, and - CHANGESETS_STORE and CHANGESETS_INDEX, which contain the changeset objects themselves, indexed by changeset id. - CVS_ITEMS_SORTED_STORE and CVS_ITEMS_SORTED_INDEX_TABLE, which contain the pickled CVSItems ordered by changeset. BreakRevisionChangesetCyclesPass ================================ There can still be cycles in the dependency graph of RevisionChangesets caused by: - Interleaved commits. Since CVS commits are not atomic, it can happen that two commits are in progress at the same time and each alters the same two files, but in different orders. These should be small cycles involving only a few revision changesets. To resolve these cycles, one or more of the RevisionChangesets have to be split up (eventually becoming separate svn commits). - Cycles involving a RevisionChangeset formed by the accidental combination of unrelated items within a short period of time that have the same author and log message. These should also be small cycles involving only a few changesets. The job of this pass is to break up such cycles (those involving only CVSRevisions). This pass works by building up the graph of revision changesets and their dependencies in memory, then attempting a topological sort of the changesets. Whenever the topological sort stalls, that implies the existence of a cycle, one of which can easily be determined. This cycle is broken through the use of heuristics that try to determine an "efficient" way of splitting one or more of the changesets that are involved. The new RevisionChangesets are written to CVS_ITEM_TO_CHANGESET_REVBROKEN, CHANGESETS_REVBROKEN_STORE, and CHANGESETS_REVBROKEN_INDEX, along with the unmodified SymbolChangesets. These files are in the same format as the analogous files produced by InitializeChangesetsPass. RevisionTopologicalSortPass =========================== Topologically sort the RevisionChangesets, thereby picking the order in which the RevisionChangesets will be committed. (Since the previous pass eliminated any dependency cycles, this sort is guaranteed to succeed.) Ambiguities in the topological sort are resolved using the changesets' timestamps. Then simplify the changeset graph into a linear chain by converting each RevisionChangeset into an OrderedChangeset that stores dependency links only to its commit-order predecessor and successor. This simplified graph enforces the commit order that resulted from the topological sort, even after the SymbolChangesets are added back into the graph later. Store the OrderedChangesets into CHANGESETS_REVSORTED_STORE and CHANGESETS_REVSORTED_INDEX along with the unmodified SymbolChangesets. BreakSymbolChangesetCyclesPass ============================== It is possible for there to be cycles in the graph of SymbolChangesets caused by: - Split creation of branches. It is possible that branch A depends on branch B in one file, but B depends on A in another file. These cycles can be large, but they only involve SymbolChangesets. Break up such dependency loops. Output the results to CVS_ITEM_TO_CHANGESET_SYMBROKEN, CHANGESETS_SYMBROKEN_STORE, and CHANGESETS_SYMBROKEN_INDEX. BreakAllChangesetCyclesPass =========================== The complete changeset graph (including both RevisionChangesets and BranchChangesets) can still have dependency cycles cause by: - Split creation of branches. The same branch tag can be added to different files at completely different times. It is possible that the revision that was branched later depends on a RevisionChangeset that involves a file on the branch that was created earlier. These cycles can be large, but they always involve a SymbolChangeset. To resolve these cycles, the SymbolChangeset is split up into two changesets. In fact, tag changesets do not have to be considered--CVSTags cannot participate in dependency cycles because no other CVSItem can depend on a CVSTag. Since the input of this pass has been through RevisionTopologicalSortPass, all revision cycles have already been broken up and the order that the RevisionChangesets will be committed has been determined. In this pass, the complete changeset graph is created in memory, including the linear list of OrderedChangesets from RevisionTopologicalSortPass plus all of the symbol changesets. Because this pass doesn't break up any OrderedChangesets, it is constrained to finding places within the revision changeset sequence in which the symbol changeset commits can be inserted. The new changesets are written to CVS_ITEM_TO_CHANGESET_ALLBROKEN, CHANGESETS_ALLBROKEN_STORE, and CHANGESETS_ALLBROKEN_INDEX, which are in the same format as the analogous files produced by InitializeChangesetsPass. TopologicalSortPass =================== Now that the earlier passes have broken up any dependency cycles among the changesets, it is possible to order all of the changesets in such a way that all of a changeset's dependencies are committed before the changeset itself. This pass does so by again building up the graph of changesets in memory, then at each step picking a changeset that has no remaining dependencies and removing it from the graph. Whenever more than one dependency-free changeset is available, symbol changesets are chosen before revision changesets. As changesets are processed, the timestamp sequence is ensured to be monotonic by the simple expedient of adjusting retrograde timestamps to be later than their predecessor. Timestamps that lie in the future, on the other hand, are assumed to be bogus and are adjusted backwards, also to be just later than their predecessor. This pass writes a line to CHANGESETS_SORTED_DATAFILE for each RevisionChangeset, in the order that the changesets should be committed. Each lines contains CHANGESET_ID TIMESTAMP where CHANGESET_ID is the id of the changeset in the CHANGESETS_ALLBROKEN_* databases and TIMESTAMP is the timstamp that should be assigned to it when it is committed. Both values are written in hexadecimal. CreateRevsPass (formerly called pass5) ============== This pass generates SVNCommits from Changesets and records symbol openings and closings. (One Changeset can result in multiple SVNCommits, for example if it causes symbols to be filled or copies to a vendor branch.) This pass does the following: 1. Creates a database file to map Subversion revision numbers to SVNCommit instances (SVN_COMMITS_STORE and SVN_COMMITS_INDEX_TABLE). Creates another database file to map CVS Revisions to their Subversion Revision numbers (CVS_REVS_TO_SVN_REVNUMS). 2. When a file is copied to a symbolic name in cvs2svn, it is copied from a specific source: either a CVSRevision, or a copy created by a previous CVSBranch of the file. The copy has to be made from an SVN revision that is during the lifetime of the source. The SVN revision when the source was created is called the symbol's "opening", and the SVN revision when it was deleted or overwritten is called the symbol's "closing". In this pass, the SymbolingsLogger class writes out a line to SYMBOL_OPENINGS_CLOSINGS for each symbol opening or closing. Note that some openings do not have closings, namely if the corresponding source is still present at the HEAD revision. The format of each line is: SYMBOL_ID SVN_REVNUM TYPE CVS_SYMBOL_ID For example: 1c 234 O 1a7 34 245 O 1a9 18a 241 C 1a7 122 201 O 1b3 Here is what the columns mean: SYMBOL_ID -- The id of the branch or tag that has an opening in this SVN_REVNUM, in hexadecimal. SVN_REVNUM -- The Subversion revision number in which the opening or closing occurred. (There can be multiple openings and closings per SVN_REVNUM). TYPE -- "O" for openings and "C" for closings. CVS_SYMBOL_ID -- The id of the CVSSymbol instance whose opening or closing is being described, in hexadecimal. Each CVSSymbol that tags a non-dead file has exactly one opening and either zero or one closing. The closing, if it exists, always occurs in a later SVN revision than the opening. See SymbolingsLogger for more details. SortSymbolOpeningsClosingsPass (formerly called pass6) ============================== This pass sorts SYMBOL_OPENINGS_CLOSINGS into SYMBOL_OPENINGS_CLOSINGS_SORTED. This orders the file first by symbol ID, and second by Subversion revision number, thus grouping all openings and closings for each symbolic name together. IndexSymbolsPass (formerly called pass7) ================ This pass iterates through all the lines in SYMBOL_OPENINGS_CLOSINGS_SORTED, writing out a pickle file (SYMBOL_OFFSETS_DB) mapping SYMBOL_ID to the file offset in SYMBOL_OPENINGS_CLOSINGS_SORTED where SYMBOL_ID is first encountered. This will allow us to seek to the various offsets in the file and sequentially read only the openings and closings that we need. OutputPass (formerly called pass8) ========== This pass opens the svn-commits database and sequentially plays out all the commits to either a Subversion repository or to a dumpfile. It also decides what sources to use to fill symbols. In --dumpfile mode, the result of this pass is a Subversion repository dumpfile (suitable for input to 'svnadmin load'). The dumpfile is the data's last static stage: last chance to check over the data, run it through svndumpfilter, move the dumpfile to another machine, etc. When not in --dumpfile mode, no full dumpfile is created. Instead, miniature dumpfiles representing a single revisions are created, loaded into the repository, and then removed. In both modes, the dumpfile revisions are created by walking through the SVN_COMMITS_* database. The database in MIRROR_NODES_STORE and MIRROR_NODES_INDEX_TABLE holds a skeletal mirror of the repository structure at each SVN revision. This mirror keeps track of which files existed on each LOD, but does not record any file contents. cvs2svn requires this information to decide which paths to copy when filling branches and tags. When .cvsignore files are modified, cvs2svn computes the corresponding svn:ignore properties and applies the properties to the parent directory. The .cvsignore files themselves are not included in the output unless the --keep-cvsignore option was specified. But in either case, the .cvsignore files are recorded within the repository mirror as if they were being written to disk, to ensure that the containing directory is not pruned if the directory in CVS still contained a .cvsignore file. =============================== Branches and Tags Plan. =============================== This pass is also where tag and branch creation is done. Since subversion does tags and branches by copying from existing revisions (then maybe editing the copy, making subcopies underneath, etc), the big question for cvs2svn is how to achieve the minimum number of operations per creation. For example, if it's possible to get the right tag by just copying revision 53, then it's better to do that than, say, copying revision 51 and then sub-copying in bits of revision 52 and 53. Tags are created as soon as cvs2svn encounters the last CVS Revision that is a source for that tag. The whole tag is created in one Subversion commit. Branches are created as soon as all of their prerequisites are in place. If a branch creation had to be broken up due to dependency cycles, then non-final parts are also created as soon as their prerequisites are ready. In such a case, the SymbolChangeset specifies how much of the branch can be created in each step. How just-in-time branch creation works: In order to make the "best" set of copies/deletes when creating a branch, cvs2svn keeps track of two sets of trees while it's making commits: 1. A skeleton mirror of the subversion repository, that is, a record of which file existed on which LOD for each SVN revision. 2. A tree for each CVS symbolic name, and the svn file/directory revisions from which various parts of that tree could be copied. Each LOD is recorded as a tree using the following schema: unique keys map to marshal.dumps() representations of dictionaries, which in turn map path component names to other unique keys: root_key ==> { entryname1 : entrykey1, entryname2 : entrykey2, ... } entrykey1 ==> { entrynameX : entrykeyX, ... } entrykey2 ==> { entrynameY : entrykeyY, ... } entrykeyX ==> { etc, etc ...} entrykeyY ==> { etc, etc ...} (The leaf nodes -- files -- are represented by None.) The repository mirror allows cvs2svn to remember what paths exist in what revisions. For details on how branches and tags are created, please see the docstring the SymbolingsLogger class (and its methods). cvs2svn-2.5.0/doc/symbol-notes.txt0000664000175100017510000003561112203665123020143 0ustar mhaggermhagger00000000000000This is a description of how symbols (tags and branches) are handled by cvs2svn, determined by reading the code. Notation ======== CVSFile -- a single file within the CVS repository. This object basically only records the filename of the corresponding RCS file, and the relative filename that this file will have within the SVN repository. A single CVSFile object is used for all of the CVSItems on all lines of development related to that file. The following terms and the corresponding classes represent project-wide concepts. For example, a project will only have a single Branch named "foo" even if many files appear on that branch. Each of these objects is assigned a unique integer ID during CollectRevsPass which is preserved during the entire conversion (even if, say, a Branch is mutated into a Tag). Trunk -- the main line of development for a particular Project in CVS. The Trunk class inherits from LineOfDevelopment. Symbol -- a Branch or a Tag within a particular Project (see below). Instances of this class are also used to represent symbols early in the conversion, before it has been decided whether to convert the symbol as a Branch or as a Tag. A Symbol contains an id, a Project, and a name. Branch -- a symbol within a particular Project that will be treated as a branch in SVN. Usually corresponds to a branch tag in CVS, but might be a non-branch tag that was mutated in CollateSymbolsPass. In SVN, this will correspond to a subdirectory of the project's "branches" directory. The Branch class inherits from Symbol and from LineOfDevelopment. Tag -- a symbol within a particular Project that will be treated as a tag in SVN. Usually corresponds to a non-branch tag in CVS, but might be a branch tag that was mutated in CollateSymbolsPass. In SVN, this will correspond to a subdirectory of the project's "tags" directory. The Tags class inherits from Symbol and from LineOfDevelopment. ExcludedSymbol -- a CVS symbol that will be excluded from the cvs2svn output. LineOfDevelopment -- a Trunk, Branch, or Tag. The following terms and the corresponding classes represent particular CVS events in particular CVS files. For example, the CVSBranch representing the creation of Branch "foo" in one file will be distinct from the CVSBranch representing the creation of branch "foo" in another file, even if the two files are in the same Project. Each CVSItem is assigned a unique integer ID during CollectRevsPass which is preserved during the entire conversion (even if, say, a CVSBranch is mutated into a CVSTag). CVSItem -- abstract base class representing any discernible event within a single RCS file, for example the creation of revision 1.6, or the tagging of the file with tag "bar". Each CVSItem has a unique integer ID. CVSRevision -- a particular revision within a particular file (e.g., file.txt:1.6). A CVSRevision occurs on a particular LineOfDevelopment. CVSRevision inherits from CVSItem. CVSSymbol -- a CVSBranch or CVSTag (see below). CVSSymbol inherits from CVSItem. CVSBranch -- the creation of a particular Branch on a particular file. A CVSBranch has a Symbol instance telling the Symbol associated with the branch, and also records the LineOfDevelopment from which the branch was created. In the SVN repository, a CVSBranch corresponds to an "svn copy" of a file to a subdirectory of the project's "branches" directory. CVSBranch inherits from CVSSymbol. CVSTag -- the creation of a particular Tag on a particular file. A CVSTag has a Symbol instance telling the Symbol associated with the tag, and also records the LineOfDevelopment from which the tag was created. In the SVN repository, a CVSTag corresponds to an "svn copy" of a file to a subdirectory of the project's "tags" directory. CVSTag inherits from CVSSymbol. CollectRevsPass =============== Collect all information about CVS tags and branches from the CVS repository. For each project, create a Trunk object to represent the trunk line of development for that project. The Trunk object for one Project is distinct from the Trunk objects for other Projects. For each symbol name seen in each project, create a Symbol object. The Symbol object contains its id, project, and name. The very first thing that is done when a symbol is read is that the Project's symbol transform rules are given a chance to transform the symbol name (or even cause it to be discarded). The result of the transformation is used as the symbol name in the rest of the program. Because this transformation process is so low-level, it is capable of making a more fundamental kind of change than the symbol strategy rules that come later: * Symbols can be renamed. * Symbols can be fully discarded, as if they never appeared in the CVS repository. This can even be done for a malformed symbol or for a branch symbol that refers to the same branch as another branch symbol (which would otherwise be a fatal error). * Two distinct symbols in different files within the same project can be transformed to the same name, in which case they are treated as a single symbol. * Two distinct symbols within a single file can be transformed to the same name, provided they refer to the same revision number. This effectively discards one of the symbols. * Two symbols with the same name in different files can be given distinct names, in which case they are treated as completely separate symbols. For each Symbol object, collect the following statistics: * In how many files was the symbol used as a branch and in how many was it used as a tag. * In how many files was there a commit on a branch with that name. * Which other symbols branched off of a branch with that name. * In how many files could each other line of development have served as the source of this symbol. These are called the "possible parents" of the symbol. These statistics are used in CollateSymbolsPass to determine which symbols can be excluded or converted from tags to branches or vice versa. The possible parents information is important because CVS is ambiguous about what line of development was the source of a branch. A branch numbered 1.3.6 might have been created from trunk (revision 1.3), from branch 1.3.2, or from branch 1.3.4; it is simply impossible to tell based on the information in a single RCS file. [Actually, the situation is even more confusing. If a branch tag is deleted from CVS, the branch number is recycled. So it is even possible that branch 1.3.6 was created from branch 1.3.8 or 1.3.10 or ... We address this confusion by noting the order that the branches were listed in the RCS file header. It appears that CVS lists branches in the header in reverse chronological order of creation.] For each tag seen within each file, create a CVSTag object recording its id, CVSFile, Symbol, and the id of the CVSRevision being tagged. For each branch seen within each file, create a CVSBranch object recording an id, CVSFile, Symbol, the branch number (e.g., '1.4.2'), the id of the CVSRevision from which the branch sprouts, and the id of the first CVSRevision on the branch (if any). For each revision seen within each file, create a CVSRevision object recording (among other things) and id, the line of development (trunk or branch) on which the revision appeared, a list of ids of CVSTags tagging the revision, and a list of ids of CVSBranches sprouting from the revision. This pass also adjusts the CVS dependency tree to work around some CVS quirks. (See design-notes.txt for the details.) These adjustments can result in CVSBranches being deleted, for example, if a file was added on a branch. In such a case, any CVSRevisions that were previously on the branch will be created by adding the file to the branch directory, rather than copying the file from the source directory to the branch directory. CleanMetadataPass ================= N/A CollateSymbolsPass ================== Allow the project's symbol strategy rules to affect how symbols are converted: * A symbol can be excluded from the conversion output (as long as there are no other non-excluded symbols that depend on it). In this case, the Symbol will be converted into an ExcludedSymbol instance. * A tag symbol can be converted as a branch. In this case, the Symbol will be converted into a Branch instance. * A branch symbol can be converted as a tag, provided there were never any commits on the branch. In this case, the Symbol will be converted into a Tag instance. * The SVN path where a symbol will be placed is determined. Typically, symbols are laid out in the standard trunk/branches/tags Subversion repository layout, but strategy rules can in fact place symbols arbitrarily. * The preferred parent of each symbol is determined. The preferred parent of a Symbol is chosen to be the line of development that appeared as a possible parent of this symbol in the most CVSFiles. This pass creates the symbol database, SYMBOL_DB, which is accessed in later passes via the SymbolDatabase class. The SymbolDatabase contains TypedSymbol (Branch, Tag, or ExcludedSymbol) instances indicating how each symbol should be processed in the conversion. The ID used for a TypedSymbol is the same as the ID allocated to the corresponding symbol in CollectRevsPass, so references in CVSItems do not have to be updated. FilterSymbolsPass ================= Iterate through all of the CVSItems, mutating CVSTags to CVSBranches and vice versa and excluding other CVSSymbols as specified by the types of the TypedSymbols in the SymbolDatabase. Additionally, filter out any CVSRevisions that reside on excluded CVSBranches. Write a line of text to CVS_SYMBOLS_DATAFILE for each surviving CVSSymbol, containing its Symbol id and a pickled version of the CVSSymbol. (This file will be sorted in SortSymbolsPass then used in InitializeChangesetsPass to create SymbolChangesets.) Also adjust the file's dependency tree by grafting CVSSymbols onto their preferred parents. This is not always possible; if not, leave the CVSSymbol where it was. Finally, record symbol "openings" and "closings". A CVSSymbol is considered "opened" by the CVSRevision or CVSBranch from which the CVSSymbol sprouts. A CVSSymbol is considered "closed" by the CVSRevision that overwrites or deletes the CVSSymbol's opening. (Every CVSSymbol has an opening, but not all of them have closings; for example, the opening CVSRevision might still exist at HEAD.) Record in each CVSRevision and CVSBranch a list of all of the CVSSymbols that it opens. Record in each CVSRevision a list of all of the CVSSymbols that it closes (CVSBranches cannot close CVSSymbols). SortRevisionsPass ================= N/A SortSymbolsPass =============== Sort CVS_SYMBOLS_DATAFILE, creating CVS_SYMBOLS_SORTED_DATAFILE. The sort groups together symbol items that might be added to the same SymbolChangeset. InitializeChangesetsPass ======================== Read CVS_SYMBOLS_SORTED_DATAFILE, grouping CVSSymbol items with the same Symbol id into SymbolChangesets. BreakRevisionChangesetCyclesPass ================================ N/A RevisionTopologicalSortPass =========================== N/A BreakSymbolChangesetCyclesPass ============================== Read in the changeset graph consisting only of SymbolChangesets and break up symbol changesets as necessary to break any cycles that are found. BreakAllChangesetCyclesPass =========================== Read in the entire changeset graph and break up symbol changesets as necessary to break any cycles that are found. TopologicalSortPass =================== Update the conversion statistics with excluded symbols omitted. CreateRevsPass ============== Create SVNCommits and assign svn revision numbers to each one. Create a database (SVN_COMMITS_*) to map svn revision numbers to SVNCommits and another (CVS_REVS_TO_SVN_REVNUMS) to map each CVSRevision id to the number of the svn revision containing it. Also, SymbolingsLogger writes a line to SYMBOL_OPENINGS_CLOSINGS for each opening or closing for each CVSSymbol, noting in what SVN revision the opening or closing occurred. SortSymbolOpeningsClosingsPass ============================== This pass sorts SYMBOL_OPENINGS_CLOSINGS into SYMBOL_OPENINGS_CLOSINGS_SORTED. This orders the file first by symbol ID, and second by Subversion revision number, thus grouping all openings and closings for each symbolic name together. IndexSymbolsPass ================ Iterate through all the lines in SYMBOL_OPENINGS_CLOSINGS_SORTED, writing out a pickled map to SYMBOL_OFFSETS_DB telling at what offset in SYMBOL_OPENINGS_CLOSINGS_SORTED the lines corresponding to each Symbol begin. This will allow us to seek to the various offsets in the file and sequentially read only the openings and closings that we need. OutputPass ========== The filling of a symbol is triggered when SVNSymbolCommit.commit() calls SVNRepositoryMirror.fill_symbol(). The SVNSymbolCommit contains the list of CVSSymbols that have to be copied to a symbol directory in this revision. However, we still have to do a lot of work to figure out what SVN revision number to use as the source of these copies, and also to group file copies together into directory copies when possible. The SYMBOL_OPENINGS_CLOSINGS_SORTED file lists the opening and closing SVN revision of each revision that has to be copied to the symbol directory. We use this information to try to find SVN revision numbers that can serve as the source for as many files as possible, to avoid having to pick and choose sources from many SVN revisions. Furthermore, when a bunch of files in a directory have to be copied at the same time, it is cheaper to copy the directory as a whole. But if not *all* of the files within the directory had to be copied, then the unneeded files have to be deleted again from the copied directory. Or if some of the files have to be copied from different source SVN revision numbers, then those files have to be overwritten in the copied directory with the correct versions. Finally, it can happen that a single Symbol has to be filled multiple times (because the initial SymbolChangeset had to be broken up). In this case, the first fill can copy the source directory to the destination directory (maybe with fixups), but subsequent copies have to copy individual files to avoid overwriting content that is already present in the destination directory. To figure all of this out, we need to know all of the files that existed in every previous SVN revision, in every line of development. This is done using the SVNRepositoryMirror class, which keeps a skeleton record of the entire SVN history in a database using data structures similar to those used by SVN itself. cvs2svn-2.5.0/doc/revision-reader.txt0000664000175100017510000001156012203665123020603 0ustar mhaggermhagger00000000000000This file contains a description of the RevisionCollector / RevisionReader mechanism. cvs2svn now includes hooks to make it possible to avoid having to invoke CVS or RCS zillions of times in OutputPass (which is otherwise the most expensive part of the conversion). Here is a brief description of how the hooks work. Most conversions [1] require an instance of RevisionReader, whose responsibility is to produce the text contents of CVS revisions on demand during OutputPass. The RevisionReader can read the CVS revision contents directly out of the RCS files during OutputPass. But additional hooks support the construction of different kinds of RevisionReader that record the CVS file revisions' contents during FilterSymbolsPass then output the contents during OutputPass. The interface that is used during FilterSymbolsPass to allow the collection of revision information is: RevisionCollector -- can collect information during FilterSymbolsPass to help the RevisionReader produce RCS file revision contents during OutputPass. The type of RevisionCollector/RevisionReader to be used for a run of cvs2svn can be set using --use-internal-co, --use-rcs, or --use-cvs, or via the --options file with lines like: ctx.revision_collector = MyRevisionCollector() ctx.revision_reader = MyRevisionReader() The following RevisionCollectors are supplied with cvs2svn: NullRevisionCollector -- does nothing (for RevisionReaders that don't need anything to happen in FilterSymbolsPass). InternalRevisionCollector -- records the delta text and dependencies for required revisions in FilterSymbolsPass, for use with the InternalRevisionReader. GitRevisionCollector -- uses another RevisionReader to reconstruct the revisions' fulltext during FilterSymbolsPass, then writes the fulltexts to a blobfile in git-fast-import format. This file, combined with the dumpfile created in OutputPass, can be loaded into git. ExternalBlobGenerator -- uses an external Python program to reconstruct the revision fulltexts in FilterSymbolsPass and write them to a blobfile in git-fast-import format. This option is very fast because (1) it uses code similar to that used by InternalRevisionCollector/InternalRevisionReader, and (2) it processes all revisions from a file at once, thereby avoiding a lot of disk seeking. The following RevisionReaders are supplied with cvs2svn: InternalRevisionReader -- reconstitutes the revisions' contents during OutputPass from the data recorded by InternalRevisionCollector. This is by far the fastest option for cvs2svn conversions, but it requires a substantial amount of temporary disk space for the duration of the conversion. RCSRevisionReader -- uses RCS's "co" command to extract the revision text during OutputPass. This is slower than InternalRevisionReader because "co" has to be executed very many times, but is better tested and does not require any temporary disk space. RCSRevisionReader does not use a RevisionCollector. CVSRevisionReader -- uses the "cvs" command to extract the revision text during OutputPass. This is even slower than RCSRevisionReader, but it can handle some CVS file quirks that stymy RCSRevisionReader (see the cvs2svn HTML documentation). CVSRevisionReader does not use a RevisionCollector. It is possible to write your own RevisionCollector and RevisionReader if you would like to do things differently. A RevisionCollector, with callback methods that are invoked as the CVS files are parsed, can be used to collect information during FilterSymbolsPass. Its process_file() method is allowed to set an arbitrary token (for example, a content hash) in CVSItem.revision_reader_token. This token is carried along by cvs2svn for use by the RevisionReader in OutputPass. Later, when OutputPass requires the file contents, it calls RevisionReader.get_content(), which is passed a CVSRevision instance and has to return the file revision's contents. The fancy RevisionReader could use the token to retrieve the pre-stored file contents without having to call CVS or RCS at all. [1] The exception is cvs2git conversions, which need a RevisionCollector but not a RevisionReader. The reason is that "git fast-import" allows file revision contents to be written as "blobs" in arbitrary order, to be hooked together later into proper changesets. This feature is very beneficial to the performance of cvs2git, because it allows all revisions of a single file to be generated at the same time (with good disk locality) rather than having to jump around from file to file getting single revisions in changeset order. Unfortunately, neither "bzr fast-import" nor "hg fastimport" support separate blobs. cvs2svn-2.5.0/doc/text-transformations.txt0000664000175100017510000000750412203665123021723 0ustar mhaggermhagger00000000000000This file collects information about how the various VCSs deal with keyword expansion, EOL handling, and file permissions. CVS/RCS ======= There are several keyword expansion modes that can be set on files. These options also affect whether EOLs are all converted to/from the local convention. The following keywords are recognized: Author, CVSHeader (CVS only), Date, Header, Id, Locker, Name, RCSfile, Revision, Source, State, and Log. ("Log" is a special case because its expansion is irreversible.) Keyword names are case sensitive. There is also a provision to define "Local keywords", which use a user-defined name but expand as one of the standard keywords. These modes are selected via a "-k" command-line option, so for example the "kv" option is selected using the option "-kkv". The available modes are: * kv, kvl: Expand keywords, or expand them including locker name. Also munge line endings. * k: Collapse keywords (e.g. "$Revision: 1.2 $ -> "$Revision$"), except for the $Log$ option, which is expanded. Also munge line endings. * o: Leave keywords (including the $Log$ keyword) in the form that they were checked in. Also munge line endings. * v: Generate only keyword values instead of the full keyword string; e.g., "$Revision$" -> "5.7". (This is mostly useful for export.) Also munge line endings. * b: Leave keywords in the form that they were checked in, inhibit munging of line endings between canonical LF to the local convention, and prevent merging of file differences. Whether or not a file is executable is determined from the executable bit of the corresponding RCS file in the repository. Please note that CVSNT handles file modes differently: it supports additional modes, and it allows the file mode to differ from one file revision to another. This is the main reason that cvs2svn doesn't work reliably with CVSNT repositories. Subversion ========== * svn:executable: If this property is set, the file is marked as executable upon checkout. * svn:mime-type: Used to decide whether line-based merging is safe, whether to show diff-based deltas between revisions, etc. * svn:keywords: List the keywords that will be expanded in the file. * svn:eol-style: Specifies how to manipulate EOL characters. If this property is set, then the file can be committed to Subversion using somewhat more flexible EOL conventions. In the Subversion repository the text is normalized to a particular EOL style, probably the "svnadmin dump" style listed below. On checkout or export, the LFs will be converted to the specified EOL style. Possible values: * LF, CRLF, CR: On commit: text can contain any mixture of EOL styles. svnadmin dump: file text contains the specified EOL format. svnadmin load: should presumably be consistent with the "svnadmin dump" format. * native: On commit: text can use any EOL style, but lines must be consistent. svnadmin dump: file text contains the canonical LF format. svnadmin load: should presumably be consistent with the "svnadmin dump" format. Git === * The executable status of a file is determined by a file mode attribute in the fast-import file. * Keywords: Not obvious what to do. There is support for expanding an $Id$ keyword via the gitattributes mechanism. Others could only be supported via custom-written filters. * EOL style: Normally, git does not do any line-end conversion. However, there is a way to use gitattributes to mark particular files as text files, and to use the configuration settings of core.autocrlf and core.safecrlf to affect conversions between LF and CRLF formats. * The "diff" gitattribute can be used to tell how to generate diffs for a file (otherwise a heuristic is used). This attribute can be used to force a file to be treated as text/binary, or tell what "diff driver" to use. cvs2svn-2.5.0/doc/making-releases.txt0000664000175100017510000000643612203665123020562 0ustar mhaggermhagger00000000000000Making releases =============== Pre-release (repeat as appropriate): A. Backport changes if appropriate. B. Update CHANGES. C. Run the testsuite, check everything is OK. D. Trial-run ./dist.sh, check the output is sane. E. Run "www/validate.sh www/*.html" to make sure that the documentation HTML is valid. Creating the release: 1. If this is an A.B.0 release, make a branch: svn copy http://cvs2svn.tigris.org/svn/cvs2svn/trunk \ http://cvs2svn.tigris.org/svn/cvs2svn/branches/A.B.x and then increment the -dev VERSION in cvs2svn_lib/version.py on trunk. 2. Set the release number and date in CHANGES on trunk. 3. Switch to a branch working copy. 4. Merge CHANGES to the release branch. 5. Make a trial distribution and see that the unit tests run: ./dist.sh tar -xzf cvs2svn-A.B.C-dev.tar.gz cd cvs2svn-A.B.C-dev ./run-tests.py cd .. rm -rf cvs2svn-A.B.C-dev 6. Set VERSION in cvs2svn_lib/version.py and then run: svn copy . http://cvs2svn.tigris.org/svn/cvs2svn/tags/A.B.C 7. Increment the -dev VERSION in cvs2svn_lib/version.py on the A.B.x branch. 8. Switch to the tag. 9. Run: ./dist.sh 10. Create a detached signature for the tar file: gpg --detach-sign -a cvs2svn-A.B.C.tar.gz Publishing the release: 1. Upload tarball and signature to website download area (log in, go to "Downloads", then "Releases" folder, then "Add new file"). 2. Move old releases into the 'Old' folder of the download area (click on the "Edit" link next to the files, then change Status -> "Archival" and Folder -> "Old"). 3. Create a project announcement on the website. See example template below. 4. Send an announcement to announce@cvs2svn.tigris.org. (users@cvs2svn.tigris.org is subscribed to announce, so there is no need to send to both lists.) See example template below. 5. Update the topic on #cvs2svn. Publishing the release to PyPI (http://pypi.python.org): 1. Unpack the release tarball. 2. Run python setup.py register Within PyPI, cvs2svn appears here: http://pypi.python.org/pypi/cvs2svn Release announcement templates ============================== Here are suggested release announcement templates. Fill in the substitutions as appropriate, and refer to previous announcements for examples. Web: [[[ cvs2svn VERSION is now released.
The MD5 checksum is CHECKSUM
For more information see CHANGES.
Download: cvs2svn-VERSION.tar.gz. ]]] Email: [[[ Subject: cvs2svn VERSION released To: announce@cvs2svn.tigris.org, git@vger.kernel.org Reply-to: users@cvs2svn.tigris.org cvs2svn VERSION is now released. BRIEF_SUMMARY_OF_VERSION_HIGHLIGHTS For more information see: http://cvs2svn.tigris.org/source/browse/cvs2svn/tags/VERSION/CHANGES?view=markup You can get it here: http://cvs2svn.tigris.org/files/documents/1462/NNNNN/cvs2svn-VERSION.tar.gz The MD5 checksum is CHECKSUM. Please send any bug reports and comments to users@cvs2svn.tigris.org. YOUR_NAME, on behalf of the cvs2svn development team. ]]] cvs2svn-2.5.0/COPYING0000664000175100017510000000524512203665123015235 0ustar mhaggermhagger00000000000000This license applies to all portions of cvs2svn which are not externally-maintained libraries (e.g. rcsparse). Such libraries have their own licenses; we recommend you read them, as their terms may differ from the terms below. This is version 1 of this license. It is also available online at http://subversion.tigris.org/license-1.html. If newer versions of this license are posted there (the same URL, but with the version number incremented: .../license-2.html, .../license-3.html, and so on), you may use a newer version instead, at your option. ==================================================================== Copyright (c) 2000-2007 CollabNet. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by CollabNet (http://www.Collab.Net/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The hosted project names must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact info@collab.net. 5. Products derived from this software may not use the "Tigris" name nor may "Tigris" appear in their names without prior written permission of CollabNet. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== This software consists of voluntary contributions made by many individuals on behalf of CollabNet. cvs2svn-2.5.0/cvs2svn0000775000175100017510000000464212203665123015534 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== import sys # Make sure that a supported version of Python is being used. Do this # as early as possible, using only code compatible with Python 1.5.2 # and Python 3.x before the check. Remember: # # Python 1.5.2 doesn't have sys.version_info or ''.join(). # Python 3.0 doesn't have string.join(). # There are plans to start deprecating the string formatting '%' # operator in Python 3.1 (but we use it here anyway). version_error = """\ ERROR: cvs2svn requires Python 2, version 2.4 or later; it does not work with Python 3. You are currently using""" version_advice = """\ Please restart cvs2svn using a different version of the Python interpreter. Visit http://www.python.org or consult your local system administrator if you need help. HINT: If you already have a usable Python version installed, it might be possible to invoke cvs2svn with the correct Python interpreter by typing something like 'python2.5 """ + sys.argv[0] + """ [...]'. """ try: version = sys.version_info except AttributeError: # This is probably a pre-2.0 version of Python. sys.stderr.write(version_error + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(sys.version + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(version_advice) sys.exit(1) if not ((2,4) <= version < (3,0)): sys.stderr.write( version_error + ' version %d.%d.%d.\n' % (version[0], version[1], version[2],) ) sys.stderr.write(version_advice) sys.exit(1) import os from cvs2svn_lib.common import FatalException from cvs2svn_lib.main import svn_main try: svn_main(os.path.basename(sys.argv[0]), sys.argv[1:]) except FatalException, e: sys.stderr.write(str(e) + '\n') sys.exit(1) cvs2svn-2.5.0/MANIFEST.in0000664000175100017510000000073712203665123015741 0ustar mhaggermhagger00000000000000include *.py dist.sh MANIFEST.in Makefile include README BUGS COMMITTERS COPYING HACKING CHANGES include cvs2svn-example.options include cvs2git-example.options include cvs2bzr-example.options include cvs2hg-example.options recursive-include svntest * recursive-include test-data * recursive-include doc * recursive-include www * recursive-include contrib *.py *.pl cvs2svn_memlog prune www/tigris-branding prune www/xhtml1-20020801 prune www/xhtml1.catalog prune www/xhtml1.tgz cvs2svn-2.5.0/contrib/0000775000175100017510000000000013206450463015637 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/contrib/verify-cvs2svn.py0000775000175100017510000005137212510420534021123 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2009 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== # # The purpose of verify-cvs2svn is to verify the result of a cvs2svn # repository conversion. The following tests are performed: # # 1. Content checking of the HEAD revision of trunk, all tags and all # branches. Only the tags and branches in the Subversion # repository are checked, i.e. there are no checks to verify that # all tags and branches in the CVS repository are present. # # This program only works if you converted a subdirectory of a CVS # repository, and not the whole repository. If you really did convert # a whole repository and need to check it, you must create a CVSROOT # directory above the current root using cvs init. # # ==================================================================== import os import sys import optparse import subprocess import shutil import re import tarfile # CVS and Subversion command line client commands CVS_CMD = 'cvs' SVN_CMD = 'svn' HG_CMD = 'hg' GIT_CMD = 'git' def pipe(cmd): """Run cmd as a pipe. Return (output, status).""" child = subprocess.Popen(cmd, stdout=subprocess.PIPE) output = child.stdout.read() status = child.wait() return (output, status) def cmd_failed(cmd, output, status): print 'CMD FAILED:', ' '.join(cmd) print 'Output:' sys.stdout.write(output) raise RuntimeError('%s command failed!' % cmd[0]) def split_output(cmd): (output, status) = pipe(cmd) if status: cmd_failed(cmd, output, status) retval = output.split(os.linesep)[:-1] if retval and not retval[-1]: del retval[-1] return retval class CvsRepos: def __init__(self, path): """Open the CVS repository at PATH.""" path = os.path.abspath(path) if not os.path.isdir(path): raise RuntimeError('CVS path is not a directory') if os.path.exists(os.path.join(path, 'CVSROOT')): # The whole repository self.module = "." self.cvsroot = path else: self.cvsroot = os.path.dirname(path) self.module = os.path.basename(path) while not os.path.exists(os.path.join(self.cvsroot, 'CVSROOT')): parent = os.path.dirname(self.cvsroot) if parent == self.cvsroot: raise RuntimeError('Cannot find the CVSROOT') self.module = os.path.join(os.path.basename(self.cvsroot), self.module) self.cvsroot = parent def __str__(self): return os.path.basename(self.cvsroot) def export(self, dest_path, rev=None, keyword_opt=None): """Export revision REV to DEST_PATH where REV can be None to export the HEAD revision, or any valid CVS revision string to export that revision.""" os.mkdir(dest_path) cmd = [CVS_CMD, '-Q', '-d', ':local:' + self.cvsroot, 'export'] if rev: cmd.extend(['-r', rev]) else: cmd.extend(['-D', 'now']) if keyword_opt: cmd.append(keyword_opt) cmd.extend(['-d', dest_path, self.module]) (output, status) = pipe(cmd) if status or output: cmd_failed(cmd, output, status) class SvnRepos: name = 'svn' def __init__(self, url): """Open the Subversion repository at URL.""" # Check if the user supplied an URL or a path if url.find('://') == -1: abspath = os.path.abspath(url) url = 'file://' + (abspath[0] != '/' and '/' or '') + abspath if os.sep != '/': url = url.replace(os.sep, '/') self.url = url # Cache a list of all tags and branches list = self.list('') if 'tags' in list: self.tag_list = self.list('tags') else: self.tag_list = [] if 'branches' in list: self.branch_list = self.list('branches') else: self.branch_list = [] def __str__(self): return self.url.split('/')[-1] def export(self, path, dest_path): """Export PATH to DEST_PATH.""" url = '/'.join([self.url, path]) cmd = [SVN_CMD, 'export', '-q', url, dest_path] (output, status) = pipe(cmd) if status or output: cmd_failed(cmd, output, status) def export_trunk(self, dest_path): """Export trunk to DEST_PATH.""" self.export('trunk', dest_path) def export_tag(self, dest_path, tag): """Export the tag TAG to DEST_PATH.""" self.export('tags/' + tag, dest_path) def export_branch(self, dest_path, branch): """Export the branch BRANCH to DEST_PATH.""" self.export('branches/' + branch, dest_path) def list(self, path): """Return a list of all files and directories in PATH.""" cmd = [SVN_CMD, 'ls', self.url + '/' + path] entries = [] for line in split_output(cmd): if line: entries.append(line.rstrip('/')) return entries def tags(self): """Return a list of all tags in the repository.""" return self.tag_list def branches(self): """Return a list of all branches in the repository.""" return self.branch_list class HgRepos: name = 'hg' def __init__(self, path): self.path = path self.base_cmd = [HG_CMD, '-R', self.path] self._branches = None # cache result of branches() self._have_default = None # so export_trunk() doesn't blow up def __str__(self): return os.path.basename(self.path) def _export(self, dest_path, rev): cmd = self.base_cmd + ['archive', '--type', 'files', '--rev', rev, '--exclude', 're:^\.hg', dest_path] (output, status) = pipe(cmd) if status or output: cmd_failed(cmd, output, status) # If Mercurial has nothing to export, then it doesn't create # dest_path. This breaks tree_compare(), so just check that the # manifest for the chosen revision really is empty, and if so create # the empty dir. if not os.path.exists(dest_path): cmd = self.base_cmd + ['manifest', '--rev', rev] manifest = [fn for fn in split_output(cmd) if not fn.startswith('.hg')] if not manifest: os.mkdir(dest_path) def export_trunk(self, dest_path): self.branches() # ensure _have_default is set if self._have_default: self._export(dest_path, 'default') else: # same as CVS does when exporting empty trunk os.mkdir(dest_path) def export_tag(self, dest_path, tag): self._export(dest_path, tag) def export_branch(self, dest_path, branch): self._export(dest_path, branch) def tags(self): cmd = self.base_cmd + ['tags', '-q'] tags = split_output(cmd) tags.remove('tip') return tags def branches(self): if self._branches is None: cmd = self.base_cmd + ['branches', '-q'] self._branches = branches = split_output(cmd) try: branches.remove('default') self._have_default = True except ValueError: self._have_default = False return self._branches class GitRepos: name = 'git' def __init__(self, path): self.path = path self.repo_cmd = [ GIT_CMD, '--git-dir=' + os.path.join(self.path, '.git'), '--work-tree=' + self.path, ] self._branches = None # cache result of branches() self._have_master = None # so export_trunk() doesn't blow up def __str__(self): return os.path.basename(self.path) def _export(self, dest_path, rev): # clone the repository cmd = [GIT_CMD, 'archive', '--remote=' + self.path, '--format=tar', rev] git_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) if False: # Unfortunately for some git tags the below causes # git_proc.wait() to hang. The git archive process is in a # state and the verify-cvs2svn hangs for good. tar = tarfile.open(mode="r|", fileobj=git_proc.stdout) for tarinfo in tar: tar.extract(tarinfo, dest_path) tar.close() else: os.mkdir(dest_path) tar_proc = subprocess.Popen( ['tar', '-C', dest_path, '-x'], stdin=git_proc.stdout, stdout=subprocess.PIPE, ) output = tar_proc.stdout.read() status = tar_proc.wait() if output or status: raise RuntimeError( 'Git tar extraction of rev %s from repo %s to %s failed (%s)!' % (rev, self.path, dest_path, output) ) status = git_proc.wait() if status: raise RuntimeError( 'Git extract of rev %s from repo %s to %s failed!' % (rev, self.path, dest_path) ) if not os.path.exists(dest_path): raise RuntimeError( 'Git clone of %s to %s failed!' % (self.path, dest_path) ) def export_trunk(self, dest_path): self.branches() # ensure _have_default is set if self._have_master: self._export(dest_path, 'master') else: # same as CVS does when exporting empty trunk os.mkdir(dest_path) def export_tag(self, dest_path, tag): self._export(dest_path, tag) def export_branch(self, dest_path, branch): self._export(dest_path, branch) def tags(self): cmd = self.repo_cmd + ['tag'] tags = split_output(cmd) return tags def branches(self): if self._branches is None: cmd = self.repo_cmd + ['branch'] branches = split_output(cmd) # Remove the two chracters at the start of the branch name for i in range(len(branches)): branches[i] = branches[i][2:] self._branches = branches try: branches.remove('master') self._have_master = True except ValueError: self._have_master = False return self._branches def transform_symbol(ctx, name): """Transform the symbol NAME using the renaming rules specified with --symbol-transform. Return the transformed symbol name.""" for (pattern, replacement) in ctx.symbol_transforms: newname = pattern.sub(replacement, name) if newname != name: print " symbol '%s' transformed to '%s'" % (name, newname) name = newname return name class Failures(object): def __init__(self): self.count = 0 # number of failures seen def __str__(self): return str(self.count) def __repr__(self): return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self.count) def report(self, summary, details=None): self.count += 1 sys.stdout.write(' FAIL: %s\n' % summary) if details: for line in details: sys.stdout.write(' %s\n' % line) def __nonzero__(self): return self.count > 0 def file_compare(failures, base1, base2, run_diff, rel_path): """Compare the mode and contents of two files. The paths are specified as two base paths BASE1 and BASE2, and a path REL_PATH that is relative to the two base paths. Return True iff the file mode and contents are identical.""" ok = True path1 = os.path.join(base1, rel_path) path2 = os.path.join(base2, rel_path) mode1 = os.stat(path1).st_mode & 0700 # only look at owner bits mode2 = os.stat(path2).st_mode & 0700 if mode1 != mode2: failures.report('File modes differ for %s' % rel_path, details=['%s: %o' % (path1, mode1), '%s: %o' % (path2, mode2)]) ok = False file1 = open(path1, 'rb') file2 = open(path2, 'rb') try: while True: data1 = file1.read(8192) data2 = file2.read(8192) if data1 != data2: if run_diff: cmd = ['diff', '-u', path1, path2] (output, status) = pipe(cmd) diff = output.split(os.linesep) else: diff = None failures.report('File contents differ for %s' % rel_path, details=diff) ok = False break if len(data1) == 0: # eof break finally: file1.close() file2.close() return ok def tree_compare(failures, base1, base2, run_diff, rel_path=''): """Compare the contents of two directory trees, including file contents. The paths are specified as two base paths BASE1 and BASE2, and a path REL_PATH that is relative to the two base paths. Return True iff the trees are identical.""" if not rel_path: path1 = base1 path2 = base2 else: path1 = os.path.join(base1, rel_path) path2 = os.path.join(base2, rel_path) if not os.path.exists(path1): failures.report('%s does not exist' % path1) return False if not os.path.exists(path2): failures.report('%s does not exist' % path2) return False if os.path.isfile(path1) and os.path.isfile(path2): return file_compare(failures, base1, base2, run_diff, rel_path) if not (os.path.isdir(path1) and os.path.isdir(path2)): failures.report('Path types differ for %r' % rel_path) return False entries1 = os.listdir(path1) entries1.sort() entries2 = os.listdir(path2) entries2.sort() ok = True missing = filter(lambda x: x not in entries2, entries1) extra = filter(lambda x: x not in entries1, entries2) if missing: failures.report('Directory /%s is missing entries: %s' % (rel_path, ', '.join(missing))) ok = False if extra: failures.report('Directory /%s has extra entries: %s' % (rel_path, ', '.join(extra))) ok = False for entry in entries1: new_rel_path = os.path.join(rel_path, entry) if not tree_compare(failures, base1, base2, run_diff, new_rel_path): ok = False return ok def verify_contents_single(failures, cvsrepos, verifyrepos, kind, label, ctx): """Verify the HEAD revision of a trunk, tag, or branch. Verify that the contents of the HEAD revision of all directories and files in the conversion repository VERIFYREPOS match the ones in the CVS repository CVSREPOS. KIND can be either 'trunk', 'tag' or 'branch'. If KIND is either 'tag' or 'branch', LABEL is used to specify the name of the tag or branch. CTX has the attributes: CTX.tmpdir: specifying the directory for all temporary files. CTX.skip_cleanup: if true, the temporary files are not deleted. CTX.run_diff: if true, run diff on differing files.""" itemname = kind + (kind != 'trunk' and '-' + label or '') cvs_export_dir = os.path.join( ctx.tmpdir, 'cvs-export-%s' % itemname) vrf_export_dir = os.path.join( ctx.tmpdir, '%s-export-%s' % (verifyrepos.name, itemname)) if label: cvslabel = transform_symbol(ctx, label) else: cvslabel = None try: cvsrepos.export(cvs_export_dir, cvslabel, ctx.keyword_opt) if kind == 'trunk': verifyrepos.export_trunk(vrf_export_dir) elif kind == 'tag': verifyrepos.export_tag(vrf_export_dir, label) else: verifyrepos.export_branch(vrf_export_dir, label) if not tree_compare( failures, cvs_export_dir, vrf_export_dir, ctx.run_diff ): return False finally: if not ctx.skip_cleanup: if os.path.exists(cvs_export_dir): shutil.rmtree(cvs_export_dir) if os.path.exists(vrf_export_dir): shutil.rmtree(vrf_export_dir) return True def verify_contents(failures, cvsrepos, verifyrepos, ctx): """Verify that the contents of the HEAD revision of all directories and files in the trunk, all tags and all branches in the conversion repository VERIFYREPOS matches the ones in the CVS repository CVSREPOS. CTX is passed through to verify_contents_single().""" # branches/tags that failed: locations = [] # Verify contents of trunk print 'Verifying trunk' sys.stdout.flush() if not verify_contents_single( failures, cvsrepos, verifyrepos, 'trunk', None, ctx ): locations.append('trunk') # Verify contents of all tags for tag in verifyrepos.tags(): print 'Verifying tag', tag sys.stdout.flush() if not verify_contents_single( failures, cvsrepos, verifyrepos, 'tag', tag, ctx ): locations.append('tag:' + tag) # Verify contents of all branches for branch in verifyrepos.branches(): if branch[:10] == 'unlabeled-': print 'Skipped branch', branch else: print 'Verifying branch', branch if not verify_contents_single( failures, cvsrepos, verifyrepos, 'branch', branch, ctx ): locations.append('branch:' + branch) sys.stdout.flush() assert bool(failures) == bool(locations), \ "failures = %r\nlocations = %r" % (failures, locations) # Show the results if failures: sys.stdout.write('FAIL: %s != %s: %d failure(s) in:\n' % (cvsrepos, verifyrepos, failures.count)) for location in locations: sys.stdout.write(' %s\n' % location) else: sys.stdout.write('PASS: %s == %s\n' % (cvsrepos, verifyrepos)) sys.stdout.flush() class OptionContext: pass def main(argv): parser = optparse.OptionParser( usage='%prog [options] cvs-repos verify-repos') parser.add_option('--branch', help='verify contents of the branch BRANCH only') parser.add_option('--diff', action='store_true', dest='run_diff', help='run diff on differing files') parser.add_option('--tag', help='verify contents of the tag TAG only') parser.add_option('--tmpdir', metavar='PATH', help='path to store temporary files') parser.add_option('--trunk', action='store_true', help='verify contents of trunk only') parser.add_option('--symbol-transform', action='append', metavar='P:S', help='transform symbol names from P to S like cvs2svn, ' 'except transforms SVN symbol to CVS symbol') parser.add_option('--svn', action='store_const', dest='repos_type', const='svn', help='assume verify-repos is svn [default]') parser.add_option('--hg', action='store_const', dest='repos_type', const='hg', help='assume verify-repos is hg') parser.add_option('--git', action='store_const', dest='repos_type', const='git', help='assume verify-repos is git') parser.add_option('--suppress-keywords', action='store_const', dest='keyword_opt', const='-kk', help='suppress CVS keyword expansion ' '(equivalent to --keyword-opt=-kk)') parser.add_option('--keyword-opt', metavar='OPT', help='control CVS keyword expansion by adding OPT to ' 'cvs export command line') parser.set_defaults(run_diff=False, tmpdir='', skip_cleanup=False, symbol_transforms=[], repos_type='svn') (options, args) = parser.parse_args() symbol_transforms = [] for value in options.symbol_transforms: # This is broken! [pattern, replacement] = value.split(":") try: symbol_transforms.append( RegexpSymbolTransform(pattern, replacement)) except re.error: parser.error("'%s' is not a valid regexp." % (pattern,)) def error(msg): """Print an error to sys.stderr.""" sys.stderr.write('Error: ' + str(msg) + '\n') verify_branch = options.branch verify_tag = options.tag verify_trunk = options.trunk # Consistency check for options and arguments. if len(args) != 2: parser.error("wrong number of arguments") cvs_path = args[0] verify_path = args[1] verify_klass = {'svn': SvnRepos, 'hg': HgRepos, 'git': GitRepos}[options.repos_type] failures = Failures() try: # Open the repositories cvsrepos = CvsRepos(cvs_path) verifyrepos = verify_klass(verify_path) # Do our thing... if verify_branch: print 'Verifying branch', verify_branch verify_contents_single( failures, cvsrepos, verifyrepos, 'branch', verify_branch, options ) elif verify_tag: print 'Verifying tag', verify_tag verify_contents_single( failures, cvsrepos, verifyrepos, 'tag', verify_tag, options ) elif verify_trunk: print 'Verifying trunk' verify_contents_single( failures, cvsrepos, verifyrepos, 'trunk', None, options ) else: # Verify trunk, tags and branches verify_contents(failures, cvsrepos, verifyrepos, options) except RuntimeError, e: error(str(e)) except KeyboardInterrupt: pass sys.exit(failures and 1 or 0) if __name__ == '__main__': main(sys.argv) cvs2svn-2.5.0/contrib/cvs2svn_memlog0000775000175100017510000000600012510420534020516 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Run cvs2svn, but logging memory usage. Memory use is logged every MemoryLogger.interval seconds. This script takes the same parameters as cvs2svn. Memory use is determined by reading from the /proc filesystem. This method is not very portable, but should hopefully work on a typical modern Linux.""" import sys import os # Make sure that a supported version of Python is being used. Do this # as early as possible, using only code compatible with Python 1.5.2 # and Python 3.x before the check. if not (0x02040000 <= sys.hexversion < 0x03000000): sys.stderr.write("ERROR: Python 2, version 2.4 or higher required.\n") sys.exit(1) sys.path.insert(0, os.path.dirname(os.path.dirname(sys.argv[0]))) import re import time import optparse import threading from cvs2svn_lib.common import FatalException from cvs2svn_lib.log import logger from cvs2svn_lib.main import main usage = '%prog [--interval=VALUE] [--help|-h] -- CVS2SVN-ARGS' description = """\ Run cvs2svn while logging its memory usage. ('--' is required to separate %(progname)s options from the options and arguments that will be passed through to cvs2svn.) """ rss_re = re.compile(r'^VmRSS\:\s+(?P.*)$') def get_memory_used(): filename = '/proc/%d/status' % (os.getpid(),) f = open(filename) try: for l in f.readlines(): l = l.strip() m = rss_re.match(l) if m: return m.group('mem') finally: f.close() return 'Unknown' class MemoryLogger(threading.Thread): def __init__(self, interval): threading.Thread.__init__(self) self.setDaemon(True) self.start_time = time.time() self.interval = interval def run(self): i = 0 while True: delay = self.start_time + self.interval * i - time.time() if delay > 0: time.sleep(delay) logger.write('Memory used: %s' % (get_memory_used(),)) i += 1 parser = optparse.OptionParser(usage=usage, description=description) parser.set_defaults(interval=1.0) parser.add_option( '--interval', action='store', type='float', help='the time in seconds between memory logs', ) (options, args) = parser.parse_args() MemoryLogger(interval=options.interval).start() try: main(sys.argv[0], args) except FatalException, e: sys.stderr.write(str(e) + '\n') sys.exit(1) cvs2svn-2.5.0/contrib/rcs_file_filter.py0000775000175100017510000001447712510420534021355 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2006-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Filter an RCS file.""" import sys import os import time sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib.rcsparser import Sink from cvs2svn_lib.rcsparser import parse def at_quote(s): return '@' + s.replace('@', '@@') + '@' def format_date(date): date_tuple = time.gmtime(date) year = date_tuple[0] if 1900 <= year <= 1999: year = '%02d' % (year - 1900) else: year = '%04d' % year return year + time.strftime('.%m.%d.%H.%M.%S', date_tuple) class WriteRCSFileSink(Sink): """A Sink that outputs reconstructed RCS file contents.""" def __init__(self, f): """Create a Sink object that will write its output into F. F should be a file-like object.""" self.f = f self.head = None self.principal_branch = None self.accessors = [] self.symbols = [] self.lockers = [] self.locking = None self.comment = None self.expansion = None def set_head_revision(self, revision): self.head = revision def set_principal_branch(self, branch_name): self.principal_branch = branch_name def set_access(self, accessors): self.accessors = accessors def define_tag(self, name, revision): self.symbols.append((name, revision,)) def set_locker(self, revision, locker): self.lockers.append((revision, locker,)) def set_locking(self, mode): self.locking = mode def set_comment(self, comment): self.comment = comment def set_expansion(self, mode): self.expansion = mode def admin_completed(self): self.f.write('head\t%s;\n' % self.head) if self.principal_branch is not None: self.f.write('branch\t%s;\n' % self.principal_branch) self.f.write('access') for accessor in self.accessors: self.f.write('\n\t%s' % accessor) self.f.write(';\n') self.f.write('symbols') for (name, revision) in self.symbols: self.f.write('\n\t%s:%s' % (name, revision)) self.f.write(';\n') self.f.write('locks') for (revision, locker) in self.lockers: self.f.write('\n\t%s:%s' % (locker, revision)) self.f.write(';') if self.locking is not None: self.f.write(' %s;' % self.locking) self.f.write('\n') if self.comment is not None: self.f.write('comment\t%s;\n' % at_quote(self.comment)) if self.expansion is not None: self.f.write('expand\t%s;\n' % at_quote(self.expansion)) self.f.write('\n') def define_revision( self, revision, timestamp, author, state, branches, next ): self.f.write( '\n%s\ndate\t%s;\tauthor %s;\tstate %s;\n' % (revision, format_date(timestamp), author, state,) ) self.f.write('branches') for branch in branches: self.f.write('\n\t%s' % branch) self.f.write(';\n') self.f.write('next\t%s;\n' % (next or '')) def tree_completed(self): pass def set_description(self, description): self.f.write('\n\ndesc\n%s\n' % at_quote(description)) def set_revision_info(self, revision, log, text): self.f.write('\n') self.f.write('\n') self.f.write('%s\n' % revision) self.f.write('log\n%s\n' % at_quote(log)) self.f.write('text\n%s\n' % at_quote(text)) def parse_completed(self): pass class FilterSink(Sink): """A Sink that passes callbacks through to another sink. This is intended for use as a base class for other filter classes that modify the data before passing it through.""" def __init__(self, sink): """Create a Sink object that will write its output into SINK. SINK should be a cvs2svn_rcsparse.Sink.""" self.sink = sink def set_head_revision(self, revision): self.sink.set_head_revision(revision) def set_principal_branch(self, branch_name): self.sink.set_principal_branch(branch_name) def set_access(self, accessors): self.sink.set_access(accessors) def define_tag(self, name, revision): self.sink.define_tag(name, revision) def set_locker(self, revision, locker): self.sink.set_locker(revision, locker) def set_locking(self, mode): self.sink.set_locking(mode) def set_comment(self, comment): self.sink.set_comment(comment) def set_expansion(self, mode): self.sink.set_expansion(mode) def admin_completed(self): self.sink.admin_completed() def define_revision( self, revision, timestamp, author, state, branches, next ): self.sink.define_revision( revision, timestamp, author, state, branches, next ) def tree_completed(self): self.sink.tree_completed() def set_description(self, description): self.sink.set_description(description) def set_revision_info(self, revision, log, text): self.sink.set_revision_info(revision, log, text) def parse_completed(self): self.sink.parse_completed() if __name__ == '__main__': if sys.argv[1:]: for path in sys.argv[1:]: if os.path.isfile(path) and path.endswith(',v'): f = open(path, 'rb') try: parse(f, WriteRCSFileSink(sys.stdout)) finally: f.close() else: sys.stderr.write('%r is being ignored.\n' % path) else: parse(sys.stdin, WriteRCSFileSink(sys.stdout)) cvs2svn-2.5.0/contrib/shrink_test_case.py0000775000175100017510000005370312510420534021545 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2006-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Shrink a test case as much as possible. !!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This script irretrievably destroys the CVS repository that it is !! !! applied to! !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! This script is meant to be used to shrink the size of a CVS repository that is to be used as a test case for cvs2svn. It tries to throw out parts of the repository while preserving the bug. CVSREPO should be the path of a copy of a CVS archive. TEST_COMMAND is a command that should run successfully (i.e., with exit code '0') if the bug is still present, and fail if the bug is absent.""" import sys import os import shutil import optparse from cStringIO import StringIO sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib.key_generator import KeyGenerator from cvs2svn_lib.rcsparser import Sink from cvs2svn_lib.rcsparser import parse from contrib.rcs_file_filter import WriteRCSFileSink from contrib.rcs_file_filter import FilterSink usage = 'USAGE: %prog [options] CVSREPO TEST_COMMAND' description = """\ Simplify a CVS repository while preserving the presence of a bug. ***THE CVS REPOSITORY WILL BE DESTROYED*** CVSREPO is the path to a CVS repository. TEST_COMMAND is a command that runs successfully (i.e., with exit code '0') if the bug is still present, and fails if the bug is absent. """ verbose = 1 tmpdir = 'shrink_test_case-tmp' file_key_generator = KeyGenerator(1) def get_tmp_filename(): return os.path.join(tmpdir, 'f%07d.tmp' % file_key_generator.gen_id()) class CommandFailedException(Exception): pass def command(cmd, *args): if verbose >= 2: sys.stderr.write('Running: %s %s...' % (cmd, ' '.join(args),)) retval = os.spawnlp(os.P_WAIT, cmd, cmd, *args) if retval: if verbose >= 2: sys.stderr.write('failed (%s).\n' % retval) raise CommandFailedException(' '.join([cmd] + list(args))) else: if verbose >= 2: sys.stderr.write('succeeded.\n') class Modification: """A reversible modification that can be made to the repository.""" def get_size(self): """Return the estimated size of this modification. This should be approximately the number of bytes by which the problem will be shrunk if this modification is successful. It is used to choose the order to attempt the modifications.""" raise NotImplementedError() def modify(self): """Modify the repository. Store enough information that the change can be reverted.""" raise NotImplementedError() def revert(self): """Revert this modification.""" raise NotImplementedError() def commit(self): """Make this modification permanent.""" raise NotImplementedError() def try_mod(self, test_command): if verbose >= 1: sys.stdout.write('Testing with the following modifications:\n') self.output(sys.stdout, ' ') self.modify() try: test_command() except CommandFailedException: if verbose >= 1: sys.stdout.write( 'The bug disappeared. Reverting modifications.\n' ) else: sys.stdout.write('Attempted modification unsuccessful.\n') self.revert() return False except KeyboardInterrupt: sys.stderr.write('Interrupted. Reverting last modifications.\n') self.revert() raise except Exception: sys.stderr.write( 'Unexpected exception. Reverting last modifications.\n' ) self.revert() raise else: self.commit() if verbose >= 1: sys.stdout.write('The bug remains. Keeping modifications.\n') else: sys.stdout.write( 'The bug remains after the following modifications:\n' ) self.output(sys.stdout, ' ') return True def get_submodifications(self, success): """Return a generator or iterable of submodifications. Return submodifications that should be tried after this this modification. SUCCESS specifies whether this modification was successful.""" return [] def output(self, f, prefix=''): raise NotImplementedError() def __repr__(self): return str(self) class EmptyModificationListException(Exception): pass class SplitModification(Modification): """Holds two modifications split out of a failing modification. Because the original modification failed, it known that mod1+mod2 can't succeed. So if mod1 succeeds, mod2 need not be attempted (though its submodifications are attempted).""" def __init__(self, mod1, mod2): # Choose mod1 to be the larger modification: if mod2.get_size() > mod1.get_size(): mod1, mod2 = mod2, mod1 self.mod1 = mod1 self.mod2 = mod2 def get_size(self): return self.mod1.get_size() def modify(self): self.mod1.modify() def revert(self): self.mod1.revert() def commit(self): self.mod1.commit() def get_submodifications(self, success): if success: for mod in self.mod2.get_submodifications(False): yield mod else: yield self.mod2 for mod in self.mod1.get_submodifications(success): yield mod def output(self, f, prefix=''): self.mod1.output(f, prefix=prefix) def __str__(self): return 'SplitModification(%s, %s)' % (self.mod1, self.mod2,) class CompoundModification(Modification): def __init__(self, modifications): if not modifications: raise EmptyModificationListException() self.modifications = modifications self.size = sum(mod.get_size() for mod in self.modifications) def get_size(self): return self.size def modify(self): for modification in self.modifications: modification.modify() def revert(self): for modification in self.modifications: modification.revert() def commit(self): for modification in self.modifications: modification.commit() def get_submodifications(self, success): if success: # All modifications were completed successfully; no need # to try subsets: pass elif len(self.modifications) == 1: # Our modification list cannot be subdivided, but maybe # the remaining modification can: for mod in self.modifications[0].get_submodifications(False): yield mod else: # Create subsets of each half of the list and put them in # a SplitModification: n = len(self.modifications) // 2 yield SplitModification( create_modification(self.modifications[:n]), create_modification(self.modifications[n:]) ) def output(self, f, prefix=''): for modification in self.modifications: modification.output(f, prefix=prefix) def __str__(self): return str(self.modifications) def create_modification(mods): """Create and return a Modification based on the iterable MODS. Raise EmptyModificationListException if mods is empty.""" mods = list(mods) if len(mods) == 1: return mods[0] else: return CompoundModification(mods) def compute_dir_size(path): # Add a little bit for the directory itself. size = 100L for filename in os.listdir(path): subpath = os.path.join(path, filename) if os.path.isdir(subpath): size += compute_dir_size(subpath) elif os.path.isfile(subpath): size += os.path.getsize(subpath) return size class DeleteDirectoryModification(Modification): def __init__(self, path): self.path = path self.size = compute_dir_size(self.path) def get_size(self): return self.size def modify(self): self.tempfile = get_tmp_filename() shutil.move(self.path, self.tempfile) def revert(self): shutil.move(self.tempfile, self.path) self.tempfile = None def commit(self): shutil.rmtree(self.tempfile) self.tempfile = None def get_submodifications(self, success): if success: # The whole directory could be deleted; no need to recurse: pass else: # Try deleting subdirectories: mods = [ DeleteDirectoryModification(subdir) for subdir in get_dirs(self.path) ] if mods: yield create_modification(mods) # Try deleting files: mods = [ DeleteFileModification(filename) for filename in get_files(self.path) ] if mods: yield create_modification(mods) def output(self, f, prefix=''): f.write('%sDeleted directory %r\n' % (prefix, self.path,)) def __str__(self): return 'DeleteDirectory(%r)' % self.path class DeleteFileModification(Modification): def __init__(self, path): self.path = path self.size = os.path.getsize(self.path) def get_size(self): return self.size def modify(self): self.tempfile = get_tmp_filename() shutil.move(self.path, self.tempfile) def revert(self): shutil.move(self.tempfile, self.path) self.tempfile = None def commit(self): os.remove(self.tempfile) self.tempfile = None def output(self, f, prefix=''): f.write('%sDeleted file %r\n' % (prefix, self.path,)) def __str__(self): return 'DeleteFile(%r)' % self.path def rev_tuple(revision): retval = [int(s) for s in revision.split('.') if int(s)] if retval[-2] == 0: del retval[-2] return tuple(retval) class RCSFileFilter: def get_size(self): raise NotImplementedError() def get_filter_sink(self, sink): raise NotImplementedError() def filter(self, text): fout = StringIO() sink = WriteRCSFileSink(fout) filter = self.get_filter_sink(sink) parse(StringIO(text), filter) return fout.getvalue() def get_subfilters(self): return [] def output(self, f, prefix=''): raise NotImplementedError() class DeleteTagRCSFileFilter(RCSFileFilter): class Sink(FilterSink): def __init__(self, sink, tagname): FilterSink.__init__(self, sink) self.tagname = tagname def define_tag(self, name, revision): if name != self.tagname: FilterSink.define_tag(self, name, revision) def __init__(self, tagname): self.tagname = tagname def get_size(self): return 50 def get_filter_sink(self, sink): return self.Sink(sink, self.tagname) def output(self, f, prefix=''): f.write('%sDeleted tag %r\n' % (prefix, self.tagname,)) def get_tag_set(path): class TagCollector(Sink): def __init__(self): self.tags = set() # A map { branch_tuple : name } for branches on which no # revisions have yet been seen: self.branches = {} def define_tag(self, name, revision): revtuple = rev_tuple(revision) if len(revtuple) % 2 == 0: # This is a tag (as opposed to branch) self.tags.add(name) else: self.branches[revtuple] = name def define_revision( self, revision, timestamp, author, state, branches, next ): branch = rev_tuple(revision)[:-1] try: del self.branches[branch] except KeyError: pass def get_tags(self): tags = self.tags for branch in self.branches.values(): tags.add(branch) return tags tag_collector = TagCollector() f = open(path, 'rb') try: parse(f, tag_collector) finally: f.close() return tag_collector.get_tags() class DeleteBranchTreeRCSFileFilter(RCSFileFilter): class Sink(FilterSink): def __init__(self, sink, branch_rev): FilterSink.__init__(self, sink) self.branch_rev = branch_rev def is_on_branch(self, revision): revtuple = rev_tuple(revision) return revtuple[:len(self.branch_rev)] == self.branch_rev def define_tag(self, name, revision): if not self.is_on_branch(revision): FilterSink.define_tag(self, name, revision) def define_revision( self, revision, timestamp, author, state, branches, next ): if not self.is_on_branch(revision): branches = [ branch for branch in branches if not self.is_on_branch(branch) ] FilterSink.define_revision( self, revision, timestamp, author, state, branches, next ) def set_revision_info(self, revision, log, text): if not self.is_on_branch(revision): FilterSink.set_revision_info(self, revision, log, text) def __init__(self, branch_rev, subbranch_tree): self.branch_rev = branch_rev self.subbranch_tree = subbranch_tree def get_size(self): return 100 def get_filter_sink(self, sink): return self.Sink(sink, self.branch_rev) def get_subfilters(self): for (branch_rev, subbranch_tree) in self.subbranch_tree: yield DeleteBranchTreeRCSFileFilter(branch_rev, subbranch_tree) def output(self, f, prefix=''): f.write( '%sDeleted branch %s\n' % (prefix, '.'.join([str(s) for s in self.branch_rev]),) ) def get_branch_tree(path): """Return the forest of branches in path. Return [(branch_revision, [sub_branch, ...]), ...], where branch_revision is a revtuple and sub_branch has the same form as the whole return value. """ class BranchCollector(Sink): def __init__(self): self.branches = {} def define_revision( self, revision, timestamp, author, state, branches, next ): parent = rev_tuple(revision)[:-1] if len(parent) == 1: parent = (1,) entry = self.branches.setdefault(parent, []) for branch in branches: entry.append(rev_tuple(branch)[:-1]) def _get_subbranches(self, parent): retval = [] try: branches = self.branches[parent] except KeyError: return [] del self.branches[parent] for branch in branches: subbranches = self._get_subbranches(branch) retval.append((branch, subbranches,)) return retval def get_branches(self): retval = self._get_subbranches((1,)) assert not self.branches return retval branch_collector = BranchCollector() f = open(path, 'rb') try: parse(f, branch_collector) finally: f.close() return branch_collector.get_branches() class RCSFileModification(Modification): """A Modification that involves changing the contents of an RCS file.""" def __init__(self, path, filters): self.path = path self.filters = filters[:] self.size = 0 for filter in self.filters: self.size += filter.get_size() def get_size(self): return self.size def modify(self): self.tempfile = get_tmp_filename() shutil.move(self.path, self.tempfile) f = open(self.tempfile, 'rb') try: text = f.read() finally: f.close() for filter in self.filters: text = filter.filter(text) f = open(self.path, 'wb') try: f.write(text) finally: f.close() def revert(self): shutil.move(self.tempfile, self.path) self.tempfile = None def commit(self): os.remove(self.tempfile) self.tempfile = None def get_submodifications(self, success): if success: # All filters completed successfully; no need to try # subsets: pass elif len(self.filters) == 1: # The last filter failed; see if it has any subfilters: subfilters = list(self.filters[0].get_subfilters()) if subfilters: yield RCSFileModification(self.path, subfilters) else: n = len(self.filters) // 2 yield SplitModification( RCSFileModification(self.path, self.filters[:n]), RCSFileModification(self.path, self.filters[n:]) ) def output(self, f, prefix=''): f.write('%sModified file %r\n' % (prefix, self.path,)) for filter in self.filters: filter.output(f, prefix=(prefix + ' ')) def __str__(self): return 'RCSFileModification(%r)' % (self.filters,) def try_modification_combinations(test_command, mods): """Try MOD and its submodifications. Return True if any modifications were successful.""" # A list of lists of modifications that should still be tried: todo = list(mods) while todo: todo.sort(key=lambda mod: mod.get_size()) mod = todo.pop() success = mod.try_mod(test_command) # Now add possible submodifications to the list of things to try: todo.extend(mod.get_submodifications(success)) def get_dirs(path): filenames = os.listdir(path) filenames.sort() for filename in filenames: subpath = os.path.join(path, filename) if os.path.isdir(subpath): yield subpath def get_files(path, recurse=False): filenames = os.listdir(path) filenames.sort() for filename in filenames: subpath = os.path.join(path, filename) if os.path.isfile(subpath): yield subpath elif recurse and os.path.isdir(subpath): for x in get_files(subpath, recurse=recurse): yield x def shrink_repository(test_command, cvsrepo): try_modification_combinations( test_command, [DeleteDirectoryModification(cvsrepo)] ) # Try deleting branches: mods = [] for path in get_files(cvsrepo, recurse=True): branch_tree = get_branch_tree(path) if branch_tree: filters = [] for (branch_revision, subbranch_tree) in branch_tree: filters.append( DeleteBranchTreeRCSFileFilter( branch_revision, subbranch_tree ) ) mods.append(RCSFileModification(path, filters)) if mods: try_modification_combinations(test_command, mods) # Try deleting tags: mods = [] for path in get_files(cvsrepo, recurse=True): tags = list(get_tag_set(path)) if tags: tags.sort() filters = [DeleteTagRCSFileFilter(tag) for tag in tags] mods.append(RCSFileModification(path, filters)) if mods: try_modification_combinations(test_command, mods) first_fail_message = """\ ERROR! The test command failed with the original repository. The test command should be designed so that it succeeds (indicating that the bug is still present) with the original repository, and fails only after the bug disappears. Please fix your test command and start again. """ class MyHelpFormatter(optparse.IndentedHelpFormatter): """A HelpFormatter for optparse that doesn't reformat the description.""" def format_description(self, description): return description def main(): parser = optparse.OptionParser( usage=usage, description=description, formatter=MyHelpFormatter(), ) parser.set_defaults(skip_initial_test=False) parser.add_option( '--skip-initial-test', action='store_true', default=False, help='skip verifying that the bug exists in the original repository', ) (options, args) = parser.parse_args() cvsrepo = args[0] def test_command(): command(*args[1:]) if not os.path.isdir(tmpdir): os.makedirs(tmpdir) if not options.skip_initial_test: sys.stdout.write('Testing with the original repository.\n') try: test_command() except CommandFailedException, e: sys.stderr.write(first_fail_message) sys.exit(1) sys.stdout.write( 'The bug is confirmed to exist in the initial repository.\n' ) try: try: shrink_repository(test_command, cvsrepo) except KeyboardInterrupt: pass finally: try: os.rmdir(tmpdir) except Exception, e: sys.stderr.write('ERROR: %s (ignored)\n' % (e,)) if __name__ == '__main__': main() cvs2svn-2.5.0/contrib/renumber_branch.py0000775000175100017510000001334412203665123021352 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (C) 2010 Cabot Communications Ltd. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Usage: renumber_branch.py OLDREVNUM NEWREVNUM PATH... WARNING: This modifies RCS files in-place. Make sure you only run it on a _copy_ of your repository. And have backups. Modify RCS files in PATH to renumber a revision and/or branch. Will also renumber any revisions on the branch and any branches from a renumbered revision. E.g. if you ask to renumber branch 1.3.5 to 1.3.99, it will also renumber revision 1.3.5.1 to 1.3.99.1, and renumber branch 1.3.5.1.7 to 1.3.99.1.7. This is usually what you want. Originally written to correct a non-standard vendor branch number, by renumbering the 1.1.2 branch to 1.1.1. This allows cvs2svn to detect that it's a vendor branch. This doesn't enforce all the rules about revision numbers. It is possible to make invalid repositories using this tool. This does try to detect if the specified revision number is already in use, and fail in that case. """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib.rcsparser import parse from rcs_file_filter import WriteRCSFileSink from rcs_file_filter import FilterSink class RenumberingFilter(FilterSink): '''A filter that transforms all revision numbers using a function provided to the constructor.''' def __init__(self, sink, transform_revision_func): '''Constructor. SINK is the object we're wrapping. It must implement the cvs2svn_rcsparse.Sink interface. TRANSFORM_REVISION_FUNC is a function that takes a single CVS revision number, as a string, and returns the possibly-transformed revision number in the same format. ''' FilterSink.__init__(self, sink) self.transform_rev = transform_revision_func def set_head_revision(self, revision): FilterSink.set_head_revision(self, self.transform_rev(revision)) def set_principal_branch(self, branch_name): FilterSink.set_principal_branch(self, self.transform_rev(branch_name)) def define_tag(self, name, revision): FilterSink.define_tag(self, name, self.transform_rev(revision)) def define_revision( self, revision, timestamp, author, state, branches, next ): revision = self.transform_rev(revision) branches = [self.transform_rev(b) for b in branches] if next is not None: next = self.transform_rev(next) FilterSink.define_revision( self, revision, timestamp, author, state, branches, next ) def set_revision_info(self, revision, log, text): FilterSink.set_revision_info(self, self.transform_rev(revision), log, text) def get_transform_func(rev_from, rev_to, force): rev_from_z = '%s.0.%s' % tuple(rev_from.rsplit('.', 1)) rev_to_z = '%s.0.%s' % tuple(rev_to.rsplit('.', 1)) def transform_revision(revision): if revision == rev_from or revision.startswith(rev_from + '.'): revision = rev_to + revision[len(rev_from):] elif revision == rev_from_z: revision = rev_to_z elif not force and (revision == rev_to or revision == rev_to_z or revision.startswith(rev_to + '.')): raise Exception('Target branch already exists') return revision return transform_revision def process_file(filename, rev_from, rev_to, force): func = get_transform_func(rev_from, rev_to, force) tmp_filename = filename + '.tmp' infp = open(filename, 'rb') outfp = open(tmp_filename, 'wb') try: writer = WriteRCSFileSink(outfp) revfilter = RenumberingFilter(writer, func) parse(infp, revfilter) finally: outfp.close() infp.close() os.rename(tmp_filename, filename) def iter_files_in_dir(top_path): for (dirpath, dirnames, filenames) in os.walk(top_path): for name in filenames: yield os.path.join(dirpath, name) def iter_rcs_files(list_of_starting_paths, verbose=False): for base_path in list_of_starting_paths: if os.path.isfile(base_path) and base_path.endswith(',v'): yield base_path elif os.path.isdir(base_path): for file_path in iter_files_in_dir(base_path): if file_path.endswith(',v'): yield file_path elif verbose: sys.stdout.write('File %s is being ignored.\n' % file_path) elif verbose: sys.stdout.write('PATH %s is being ignored.\n' % base_path) def main(): if len(sys.argv) < 4 or '.' not in sys.argv[1] or '.' not in sys.argv[2]: sys.stderr.write('Usage: %s OLDREVNUM NEWREVNUM PATH...\n' % (sys.argv[0],)) sys.exit(1) rev_from = sys.argv[1] rev_to = sys.argv[2] force = False for path in iter_rcs_files(sys.argv[3:], verbose=True): sys.stdout.write('Processing %s...' % path) process_file(path, rev_from, rev_to, force) sys.stdout.write('done.\n') if __name__ == '__main__': main() cvs2svn-2.5.0/contrib/destroy_repository.py0000775000175100017510000004046712510420534022210 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2006-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Usage: destroy_repository.py OPTION... PATH... Strip the text content out of RCS-format files. *** This script irretrievably destroys any RCS files that it is applied to! This script attempts to strip the file text, log messages, and author names out of RCS files, in addition to renaming RCS files and directories. (This is useful to make test cases smaller and to remove much of the proprietary information that is stored in a repository.) Note that this script does NOT obliterate other information that might also be considered proprietary, such as 'CVSROOT' directories and their contents, commit dates, etc. In fact, it's not guaranteed even to obliterate all of the file text, or to do anything else for that matter. The following OPTIONs are recognized: --all destroy all data (this is the default if no options are given) --data destroy revision data (file contents) only --metadata destroy revision metadata (author, log message, description) only --symbols destroy symbol names (branch/tag names) only --filenames destroy the filenames of RCS files --basenames destroy basenames only (keep filename extensions, such as '.txt') (--filenames overrides --basenames) --dirnames destroy directory names within given PATH. PATH itself (if a directory) is not destroyed. --cvsroot delete files within 'CVSROOT' directories, instead of leaving them untouched. The 'CVSROOT' directory itself is preserved. --no- where is one of the above options negates the meaning of that option. Each PATH that is a *,v file will be stripped. Each PATH that is a directory will be traversed and all of its *,v files stripped. Other PATHs will be ignored. Examples of usage: destroy_repository.py PATH destroys all data in PATH destroy_repository.py --all PATH same as above destroy_repository.py --data PATH destroys only revision data destroy_repository.py --no-data PATH destroys everything but revision data destroy_repository.py --data --metadata PATH destroys revision data and metadata only ---->8---- The *,v files must be writable by the user running the script. Typically CVS repositories are read-only, so you might have to run something like $ chmod -R ug+w my/repo/path before running this script. Most cvs2svn behavior is completely independent of the text contained in an RCS file. (The text is not even looked at until OutputPass.) The idea is to use this script when preparing test cases for problems that you experience with cvs2svn. Instead of sending us your whole CVS repository, you should: 1. Make a copy of the original repository 2. Run this script on the copy (NEVER ON THE ORIGINAL!!!) 3. Verify that the problem still exists when you use cvs2svn to convert the 'destroyed' copy 4. Send us the 'destroyed' copy along with the exact cvs2svn version that you used, the exact command line that you used to start the conversion, and the options file if you used one. Please also consider using shrink_test_case.py to localize the problem even further. """ import sys import os import shutil import re sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib.key_generator import KeyGenerator from cvs2svn_lib.rcsparser import parse from rcs_file_filter import WriteRCSFileSink from rcs_file_filter import FilterSink # Which components to be destroyed. Default to all. destroy = { 'data': True, 'metadata': True, 'symbols': True, 'filenames': True, 'basenames': True, 'dirnames': True, 'cvsroot': True, } tmpdir = 'destroy_repository-tmp' file_key_generator = KeyGenerator(1) def get_tmp_filename(): return os.path.join(tmpdir, 'f%07d.tmp' % file_key_generator.gen_id()) # Mapping from "real" symbol name to rewritten symbol name symbol_map = {} def rewrite_symbol(name): if name not in symbol_map: symbol_map[name] = "symbol%05d" % (len(symbol_map)) return symbol_map[name] # Mapping from "real" filename to rewritten filename filename_map = { # Empty filename should always map to empty filename. This is useful when # preserving the last component of filenames with only one component. '': '', } # Set the following to true if we should not destroy the last filename # component (aka. filename extension) keep_last_filename_component = False def rewrite_filename(pathname): if not destroy['filenames']: return pathname (dirname, filename) = os.path.split(pathname) extra = '' # Strip trailing ',v' now, and re-append it to the rewritten filename if filename.endswith(',v'): extra += ',v' filename = filename[:-2] if keep_last_filename_component: (filename, extension) = os.path.splitext(filename) if not extension: # filename has no extension. Do not rewrite this filename # at all. return pathname extra = extension + extra # Rewrite filename try: return os.path.join(dirname, filename_map[filename] + extra) except KeyError: # filename_map[filename] does not exist. Generate automatically: num = len(filename_map) while True: filename_map[filename] = "file%03d" % (num) retval = os.path.join(dirname, filename_map[filename] + extra) if not os.path.exists(retval): return retval num += 1 # List of directory names to be renamed. This list is filled while we walk # the directory structure, and then processed afterwards, in order to not # mess up the directory structure while it is being walked. rename_dir_list = [] def rename_dirs(): """Rename all directories occuring in rename_dir_list""" # Make sure we rename subdirs _before_ renaming their parents rename_dir_list.reverse() rename_map = {} num = 0 for d in rename_dir_list: (parent, name) = os.path.split(d) # Skip rewriting 'Attic' directories if name == "Attic": continue if name not in rename_map: while True: num += 1 rename_map[name] = "dir%03d" % (num) if not os.path.exists(os.path.join(parent, rename_map[name])): break new_d = os.path.join(parent, rename_map[name]) assert not os.path.exists(new_d) shutil.move(d, new_d) class Substituter: def __init__(self, template): self.template = template self.key_generator = KeyGenerator(1) # A map from old values to new ones. self.substitutions = {} def get_substitution(self, s): r = self.substitutions.get(s) if r == None: r = self.template % self.key_generator.gen_id() self.substitutions[s] = r return r class LogSubstituter(Substituter): # If a log messages matches any of these regular expressions, it # is passed through untouched. untouchable_log_res = [ re.compile(r'^Initial revision\n$'), re.compile(r'^file (?P.+) was initially added' r' on branch (?P.+)\.\n$'), re.compile(r'^\*\*\* empty log message \*\*\*\n$'), re.compile(r'^initial checkin$'), ] def __init__(self): Substituter.__init__(self, 'log %d') def get_substitution(self, log): keep_log = '' for untouchable_log_re in self.untouchable_log_res: m = untouchable_log_re.search(log) if m: # We have matched one of the above regexps # Keep log message keep_log = log # Check if we matched a regexp with a named subgroup groups = m.groupdict() if 'symbol' in groups and destroy['symbols']: # Need to rewrite symbol name symbol = groups['symbol'] keep_log = keep_log.replace(symbol, rewrite_symbol(symbol)) if 'filename' in groups and destroy['filenames']: # Need to rewrite filename filename = groups['filename'] keep_log = keep_log.replace( filename, rewrite_filename(filename) ) if keep_log: return keep_log if destroy['metadata']: return Substituter.get_substitution(self, log) return log class DestroyerFilterSink(FilterSink): def __init__(self, author_substituter, log_substituter, sink): FilterSink.__init__(self, sink) self.author_substituter = author_substituter self.log_substituter = log_substituter def set_head_revision(self, revision): self.head_revision = revision FilterSink.set_head_revision(self, revision) def define_tag(self, name, revision): if destroy['symbols']: name = rewrite_symbol(name) FilterSink.define_tag(self, name, revision) def define_revision( self, revision, timestamp, author, state, branches, next ): if destroy['metadata']: author = self.author_substituter.get_substitution(author) FilterSink.define_revision( self, revision, timestamp, author, state, branches, next ) def set_description(self, description): if destroy['metadata']: description = '' FilterSink.set_description(self, description) def set_revision_info(self, revision, log, text): if destroy['data']: if revision == self.head_revision: # Set the HEAD text unconditionally. (It could be # that revision HEAD-1 has an empty deltatext, in # which case the HEAD text was actually committed in # an earlier commit.) text = ( 'This text was last seen in HEAD (revision %s)\n' ) % (revision,) elif text == '': # This is a no-op revision; preserve that fact. (It # might be relied on by cvs2svn). pass else: # Otherwise, replace the data. if revision.count('.') == 1: # On trunk, it could be that revision N-1 has an # empty deltatext, in which case text for revision # N was actually committed in an earlier commit. text = ( 'd1 1\n' 'a1 1\n' 'This text was last seen in revision %s\n' ) % (revision,) else: # On a branch, we know that the text was changed # in revision N (even though the same text might # also be kept across later revisions N+1 etc.) text = ( 'd1 1\n' 'a1 1\n' 'This text was committed in revision %s\n' ) % (revision,) if destroy['metadata'] or destroy['symbols'] or destroy['filenames']: log = self.log_substituter.get_substitution(log) FilterSink.set_revision_info(self, revision, log, text) class FileDestroyer: def __init__(self): self.log_substituter = LogSubstituter() self.author_substituter = Substituter('author%d') def destroy_file(self, filename): tmp_filename = get_tmp_filename() f = open(tmp_filename, 'wb') new_filename = rewrite_filename(filename) oldf = open(filename, 'rb') parse( oldf, DestroyerFilterSink( self.author_substituter, self.log_substituter, WriteRCSFileSink(f), ) ) oldf.close() f.close() # Replace the original file with the new one: assert filename == new_filename or not os.path.exists(new_filename) os.remove(filename) shutil.move(tmp_filename, new_filename) def visit(self, dirname, names): # Special handling of CVSROOT directories if "CVSROOT" in names: path = os.path.join(dirname, "CVSROOT") if destroy['cvsroot']: # Remove all contents within CVSROOT sys.stderr.write('Deleting %s contents...' % path) shutil.rmtree(path) os.mkdir(path) else: # Leave CVSROOT alone sys.stderr.write('Skipping %s...' % path) del names[names.index("CVSROOT")] sys.stderr.write('done.\n') for name in names: path = os.path.join(dirname, name) if os.path.isfile(path) and path.endswith(',v'): sys.stderr.write('Destroying %s...' % path) self.destroy_file(path) sys.stderr.write('done.\n') elif os.path.isdir(path): if destroy['dirnames']: rename_dir_list.append(path) # Subdirectories are traversed automatically pass else: sys.stderr.write('File %s is being ignored.\n' % path) def destroy_dir(self, path): os.path.walk(path, FileDestroyer.visit, self) def usage_abort(msg): if msg: print >>sys.stderr, "ERROR:", msg print >>sys.stderr # Use this file's docstring as a usage string, but only the first part print __doc__.split('\n---->8----', 1)[0] sys.exit(1) if __name__ == '__main__': if not os.path.isdir(tmpdir): os.makedirs(tmpdir) # Paths to be destroyed paths = [] # Command-line argument processing first_option = True for arg in sys.argv[1:]: if arg.startswith("--"): # Option processing option = arg[2:].lower() value = True if option.startswith("no-"): value = False option = option[3:] if first_option: # Use the first option on the command-line to determine the # default actions. If the first option is negated (i.e. --no-X) # the default action should be to destroy everything. # Otherwise, the default action should be to destroy nothing. # This makes both positive and negative options work # intuitively (e.g. "--data" will destroy only data, while # "--no-data" will destroy everything BUT data). for d in destroy.keys(): destroy[d] = not value first_option = False if option in destroy: destroy[option] = value elif option == "all": for d in destroy.keys(): destroy[d] = value else: usage_abort("Unknown OPTION '%s'" % arg) else: # Path argument paths.append(arg) # If --basenames if given (and not also --filenames), we shall destroy # filenames, up to, but not including the last component. if destroy['basenames'] and not destroy['filenames']: destroy['filenames'] = True keep_last_filename_component = True if not paths: usage_abort("No PATH given") # Destroy given PATHs file_destroyer = FileDestroyer() for path in paths: if os.path.isfile(path) and path.endswith(',v'): file_destroyer.destroy_file(path) elif os.path.isdir(path): file_destroyer.destroy_dir(path) else: sys.stderr.write('PATH %s is being ignored.\n' % path) if destroy['dirnames']: rename_dirs() cvs2svn-2.5.0/contrib/__init__.py0000664000175100017510000000143312203665123017746 0ustar mhaggermhagger00000000000000# (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2007 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Allow this directory to be imported as a module.""" cvs2svn-2.5.0/contrib/cvsVsvn.pl0000775000175100017510000001212212203665123017642 0ustar mhaggermhagger00000000000000#!/usr/bin/perl -w # # (C) 2005 The Measurement Factory http://www.measurement-factory.com/ # This software is distributed under Apache License, version 2.0. # # The cvsVsvn.pl script compares CVS and Subversion projects. The # user is notified if project files maintained by CVS differ from # those maintained by Subversion: # # $ CVSROOT=/my/cvsrepo ./cvsVsvn project1 svn+ssh://host/path/projects/p1 # collecting CVS tags... # found 34 CVS tags # comparing tagged snapshots... # HEAD snapshots appear to be the same # ... # CVS and SVN repositories differ because cvs_foo and svn_tags_foo # export directories differ in cvsVsvn.tmp # # The comparison is done for every CVS tag and branch (including # HEAD), by exporting corresponding CVS and Subversion snapshots and # running 'diff' against the two resulting directories. One can edit # the script or use the environment variable DIFF_OPTIONS to alter # 'diff' behavior (e.g., ignore differences in some files). # Commit logs are not compared, unfortunately. This script is also # confused by files that differ due to different keyword expansion by # CVS and SVN. use strict; # cvsVsvn exports a user-specified module from CVS and Subversion # repositories and compares the two exported directories using the # 'diff' tool. The procedure is performed for all CVS tags (including # HEAD and branches). die(&usage()) unless @ARGV == 2; my ($CvsModule, $SvnModule) = @ARGV; my $TmpDir = 'cvsVsvn.tmp'; # directory to store temporary files my @Tags = &collectTags(); print(STDERR "comparing tagged snapshots...\n"); foreach my $tagPair (@Tags) { &compareTags($tagPair->{cvs}, $tagPair->{svn}); } print(STDERR "CVS and Subversion repositories appear to be the same\n"); exit(0); sub collectTags { print(STDERR "collecting CVS tags...\n"); my @tags = ( { cvs => 'HEAD', svn => 'trunk' } ); # get CVS log headers with symbolic tags my %names = (); my $inNames; my $cmd = sprintf('cvs rlog -h %s', $CvsModule); open(IF, "$cmd|") or die("cannot execute $cmd: $!, stopped"); while () { if ($inNames) { my ($name, $version) = /\s+(\S+):\s*(\d\S*)/; if ($inNames = defined $version) { my @nums = split(/\./, $version); my $isBranch = (2*int(@nums/2) != @nums) || (@nums > 2 && $nums[$#nums-1] == 0); my $status = $isBranch ? 'branches' : 'tags'; my $oldStatus = $names{$name}; next if $oldStatus && $oldStatus eq $status; die("change in $name tag status, stopped") if $oldStatus; $names{$name} = $status; } } else { $inNames = /^symbolic names:/; } } close(IF); while (my ($name, $status) = each %names) { my $tagPair = { cvs => $name, svn => sprintf('%s/%s', $status, $name) }; push (@tags, $tagPair); } printf(STDERR "found %d CVS tags\n", scalar @tags); return @tags; } sub compareTags { my ($cvsTag, $svnTag) = @_; &prepDirs(); &cvsExport($cvsTag); &svnExport($svnTag); &diffDir($cvsTag, $svnTag); # identical directories, clean up &cleanDirs(); } sub diffDir { my ($cvsTag, $svnTag) = @_; my $cvsDir = &cvsDir($cvsTag); my $svnDir = &svnDir($svnTag); my $same = systemf('diff --brief -b -B -r "%s" "%s"', $cvsDir, $svnDir) == 0; die("CVS and SVN repositories differ because ". "$cvsDir and $svnDir export directories differ in $TmpDir; stopped") unless $same; print(STDERR "$cvsTag snapshots appear to be the same\n"); return 0; } sub makeDir { my $dir = shift; &systemf('mkdir %s', $dir) == 0 or die("cannot create $dir: $!, stopped"); } sub prepDirs { &makeDir($TmpDir); chdir($TmpDir) or die($!); } sub cleanDirs { chdir('..') or die($!); &systemf('rm -irf %s', $TmpDir) == 0 or die("cannot delete $TmpDir: $!, stopped"); } sub cvsExport { my ($cvsTag) = @_; my $dir = &cvsDir($cvsTag); &makeDir($dir); &systemf('cvs -Q export -r %s -d %s %s', $cvsTag, $dir, $CvsModule) == 0 or die("cannot export $cvsTag of CVS module '$CvsModule', stopped"); } sub svnExport { my ($svnTag) = @_; my $dir = &svnDir($svnTag); my $cvsOk = &systemf('svn list %s/%s > /dev/null', $SvnModule, $svnTag) == 0 && &systemf('svn -q export %s/%s %s', $SvnModule, $svnTag, $dir) == 0; die("cannot export $svnTag of svn module '$SvnModule', stopped") unless $cvsOk && -d $dir; } sub tag2dir { my ($category, $tag) = @_; my $dir = sprintf('%s_%s', $category, $tag); # remove dangerous chars $dir =~ s/[^A-z0-9_\.\-]+/_/g; return $dir; } sub cvsDir { return &tag2dir('cvs', @_); } sub svnDir { return &tag2dir('svn', @_); } sub systemf { my ($fmt, @params) = @_; my $cmd = sprintf($fmt, (@params)); #print(STDERR "$cmd\n"); return system($cmd); } sub usage { return "usage: $0 \n"; } cvs2svn-2.5.0/contrib/show_db.py0000775000175100017510000001351712203665123017645 0ustar mhaggermhagger00000000000000#!/usr/bin/env python import anydbm import marshal import sys import os import getopt import cPickle as pickle from cStringIO import StringIO sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib import config from cvs2svn_lib.context import Ctx from cvs2svn_lib.common import DB_OPEN_READ from cvs2svn_lib.artifact_manager import artifact_manager def usage(): cmd = sys.argv[0] sys.stderr.write('Usage: %s OPTION [DIRECTORY]\n\n' % os.path.basename(cmd)) sys.stderr.write( 'Show the contents of the temporary database files created by cvs2svn\n' 'in a structured human-readable way.\n' '\n' 'OPTION is one of:\n' ' -R SVNRepositoryMirror revisions table\n' ' -N SVNRepositoryMirror nodes table\n' ' -r rev SVNRepositoryMirror node tree for specific revision\n' ' -m MetadataDatabase\n' ' -f CVSPathDatabase\n' ' -c PersistenceManager SVNCommit table\n' ' -C PersistenceManager cvs-revs-to-svn-revnums table\n' ' -i CVSItemDatabase (normal)\n' ' -I CVSItemDatabase (filtered)\n' ' -p file Show the given file, assuming it contains a pickle.\n' '\n' 'DIRECTORY is the directory containing the temporary database files.\n' 'If omitted, the current directory is assumed.\n') sys.exit(1) def print_node_tree(db, key="0", name="", prefix=""): print "%s%s (%s)" % (prefix, name, key) if name[:1] != "/": dict = marshal.loads(db[key]) items = dict.items() items.sort() for entry in items: print_node_tree(db, entry[1], entry[0], prefix + " ") def show_int2str_db(fname): db = anydbm.open(fname, 'r') k = map(int, db.keys()) k.sort() for i in k: print "%6d: %s" % (i, db[str(i)]) def show_str2marshal_db(fname): db = anydbm.open(fname, 'r') k = db.keys() k.sort() for i in k: print "%6s: %s" % (i, marshal.loads(db[i])) def show_str2pickle_db(fname): db = anydbm.open(fname, 'r') k = db.keys() k.sort() for i in k: o = pickle.loads(db[i]) print "%6s: %r" % (i, o) print " %s" % (o,) def show_str2ppickle_db(fname): db = anydbm.open(fname, 'r') k = db.keys() k.remove('_') k.sort(key=lambda s: int(s, 16)) u1 = pickle.Unpickler(StringIO(db['_'])) u1.load() for i in k: u2 = pickle.Unpickler(StringIO(db[i])) u2.memo = u1.memo.copy() o = u2.load() print "%6s: %r" % (i, o) print " %s" % (o,) def show_cvsitemstore(): for cvs_file_items in Ctx()._cvs_items_db.iter_cvs_file_items(): items = cvs_file_items.values() items.sort(key=lambda i: i.id) for item in items: print "%6x: %r" % (item.id, item,) def show_filtered_cvs_item_store(): from cvs2svn_lib.cvs_item_database import IndexedCVSItemStore db = IndexedCVSItemStore( artifact_manager.get_temp_file(config.CVS_ITEMS_FILTERED_STORE), artifact_manager.get_temp_file(config.CVS_ITEMS_FILTERED_INDEX_TABLE), DB_OPEN_READ) ids = list(db.iterkeys()) ids.sort() for id in ids: cvs_item = db[id] print "%6x: %r" % (cvs_item.id, cvs_item,) class ProjectList: """A mock project-list that can be assigned to Ctx()._projects.""" def __init__(self): self.projects = {} def __getitem__(self, i): return self.projects.setdefault(i, 'Project%d' % i) def prime_ctx(): def rf(filename): artifact_manager.register_temp_file(filename, None) from cvs2svn_lib.common import DB_OPEN_READ from cvs2svn_lib.symbol_database import SymbolDatabase from cvs2svn_lib.cvs_path_database import CVSPathDatabase rf(config.CVS_PATHS_DB) rf(config.SYMBOL_DB) from cvs2svn_lib.cvs_item_database import OldCVSItemStore from cvs2svn_lib.metadata_database import MetadataDatabase rf(config.METADATA_DB) rf(config.CVS_ITEMS_STORE) rf(config.CVS_ITEMS_FILTERED_STORE) rf(config.CVS_ITEMS_FILTERED_INDEX_TABLE) artifact_manager.pass_started(None) Ctx()._projects = ProjectList() Ctx()._symbol_db = SymbolDatabase() Ctx()._cvs_path_db = CVSPathDatabase(DB_OPEN_READ) Ctx()._cvs_items_db = OldCVSItemStore( artifact_manager.get_temp_file(config.CVS_ITEMS_STORE) ) Ctx()._metadata_db = MetadataDatabase(DB_OPEN_READ) def main(): try: opts, args = getopt.getopt(sys.argv[1:], "RNr:mlfcCiIp:") except getopt.GetoptError: usage() if len(args) > 1 or len(opts) != 1: usage() if len(args) == 1: Ctx().tmpdir = args[0] for o, a in opts: if o == "-R": show_int2str_db(config.SVN_MIRROR_REVISIONS_TABLE) elif o == "-N": show_str2marshal_db( config.SVN_MIRROR_NODES_STORE, config.SVN_MIRROR_NODES_INDEX_TABLE ) elif o == "-r": try: revnum = int(a) except ValueError: sys.stderr.write('Option -r requires a valid revision number\n') sys.exit(1) db = anydbm.open(config.SVN_MIRROR_REVISIONS_TABLE, 'r') key = db[str(revnum)] db.close() db = anydbm.open(config.SVN_MIRROR_NODES_STORE, 'r') print_node_tree(db, key, "Revision %d" % revnum) elif o == "-m": show_str2marshal_db(config.METADATA_DB) elif o == "-f": prime_ctx() cvs_files = list(Ctx()._cvs_path_db.itervalues()) cvs_files.sort() for cvs_file in cvs_files: print '%6x: %s' % (cvs_file.id, cvs_file,) elif o == "-c": prime_ctx() show_str2ppickle_db( config.SVN_COMMITS_INDEX_TABLE, config.SVN_COMMITS_STORE ) elif o == "-C": show_str2marshal_db(config.CVS_REVS_TO_SVN_REVNUMS) elif o == "-i": prime_ctx() show_cvsitemstore() elif o == "-I": prime_ctx() show_filtered_cvs_item_store() elif o == "-p": obj = pickle.load(open(a)) print repr(obj) print obj else: usage() sys.exit(2) if __name__ == '__main__': main() cvs2svn-2.5.0/contrib/git-move-refs.py0000775000175100017510000001224113206445076020704 0ustar mhaggermhagger00000000000000#!/usr/bin/python """Remove redundant fixup commits from a cvs2svn-converted git repository. Process each head ref and/or tag in a git repository. If the associated commit is tree-wise identical with another commit, the head or tag is moved to point at the other commit (i.e., refs pointing at identical content will all point at a single fixup commit). Furthermore, if one of the parents of the fixup commit is identical to the fixup commit itself, then the head or tag is moved to the parent. The script is meant to be run against a repository converted by cvs2svn, since cvs2svn creates empty commits for some tags and head refs (branches). """ usage = 'USAGE: %prog [options]' import sys import optparse from subprocess import Popen, PIPE, call # Cache trees we have already seen, and that are suitable targets for # moved refs tree_cache = {} # tree SHA1 -> commit SHA1 # Cache parent commit -> parent tree mapping parent_cache = {} # commit SHA1 -> tree SHA1 def resolve_commit(commit): """Return the tree object associated with the given commit.""" get_tree_cmd = ["git", "rev-parse", commit + "^{tree}"] tree = Popen(get_tree_cmd, stdout = PIPE).communicate()[0].strip() return tree def move_ref(ref, from_commit, to_commit, ref_type): """Move the given head to the given commit. ref_type is either "tags" or "heads" """ if from_commit != to_commit: print "Moving ref %s from %s to %s..." % (ref, from_commit, to_commit), if ref_type == "tags": command = "tag" else: command = "branch" retcode = call(["git", command, "-f", ref, to_commit]) if retcode == 0: print "done" else: print "FAILED" def try_to_move_ref(ref, commit, tree, parents, ref_type): """Try to move the given ref to a separate commit (with identical tree).""" if tree in tree_cache: # We have already found a suitable commit for this tree move_ref(ref, commit, tree_cache[tree], ref_type) return # Try to move this ref to one of its commit's parents for p in parents: if p not in parent_cache: # Not in cache parent_cache[p] = resolve_commit(p) p_tree = parent_cache[p] if tree == p_tree: # We can move ref to parent p move_ref(ref, commit, p, ref_type) commit = p break # Register the resulting commit object in the tree_cache assert tree not in tree_cache # Sanity check tree_cache[tree] = commit def process_refs(ref_type): tree_cache.clear() parent_cache.clear() # Command for retrieving refs and associated metadata # See 'git for-each-ref' manual page for --format details get_ref_info_cmd = [ "git", "for-each-ref", "--format=%(refname)%00%(objecttype)%00%(subject)%00" "%(objectname)%00%(tree)%00%(parent)%00" "%(*objectname)%00%(*tree)%00%(*parent)", "refs/%s" % (ref_type,), ] get_ref_info = Popen(get_ref_info_cmd, stdout = PIPE) while True: # While get_ref_info process is still running for line in get_ref_info.stdout: line = line.strip() (ref, objtype, subject, commit, tree, parents, commit_alt, tree_alt, parents_alt) = line.split(chr(0)) if objtype == "tag": commit = commit_alt tree = tree_alt parents = parents_alt elif objtype != "commit": continue if subject.startswith("This commit was manufactured by cvs2") \ or not subject: # We shall try to move this ref, if possible parent_list = [] if parents: parent_list = parents.split(" ") for p in parent_list: assert len(p) == 40 ref_prefix = "refs/%s/" % (ref_type,) assert ref.startswith(ref_prefix) try_to_move_ref( ref[len(ref_prefix):], commit, tree, parent_list, ref_type ) else: # We shall not move this ref, but it is a possible target # for other refs that we _do_ want to move tree_cache.setdefault(tree, commit) if get_ref_info.poll() is not None: # Break if no longer running: break assert get_ref_info.returncode == 0 def main(args): parser = optparse.OptionParser(usage=usage, description=__doc__) parser.add_option( '--tags', '-t', action='store_true', default=False, help='process tags', ) parser.add_option( '--branches', '-b', action='store_true', default=False, help='process branches', ) (options, args) = parser.parse_args(args=args) if args: parser.error('Unexpected command-line arguments') if not (options.tags or options.branches): # By default, process tags but not branches: options.tags = True if options.tags: process_refs("tags") if options.branches: process_refs("heads") main(sys.argv[1:]) cvs2svn-2.5.0/contrib/profile-repos.py0000775000175100017510000000646012203665123021005 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # ==================================================================== # Copyright (c) 2000-2006 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """ Report information about CVS revisions, tags, and branches in a CVS repository by examining the temporary files output by pass 1 of cvs2svn on that repository. NOTE: You have to run the conversion pass yourself! """ import sys, os, os.path from cvs2svn_lib.common import DB_OPEN_READ from cvs2svn_lib.config import CVS_PATHS_DB from cvs2svn_lib.config import CVS_ITEMS_DB from cvs2svn_lib.config import CVS_ITEMS_ALL_DATAFILE from cvs2svn_lib.cvs_path_database import CVSPathDatabase from cvs2svn_lib.cvs_item_database import CVSItemDatabase def do_it(): cvs_path_db = CVSPathDatabase(CVS_PATHS_DB, DB_OPEN_READ) cvs_items_db = CVSItemDatabase(cvs_path_db, CVS_ITEMS_DB, DB_OPEN_READ) fp = open(CVS_ITEMS_ALL_DATAFILE, 'r') tags = { } branches = { } max_tags = 0 max_branches = 0 line_count = 0 total_tags = 0 total_branches = 0 while 1: line_count = line_count + 1 line = fp.readline() if not line: break cvs_rev_key = line.strip() cvs_rev = cvs_items_db[cvs_rev_key] # Handle tags num_tags = len(cvs_rev.tags) max_tags = (num_tags > max_tags) \ and num_tags or max_tags total_tags = total_tags + num_tags for tag in cvs_rev.tags: tags[tag] = None # Handle branches num_branches = len(cvs_rev.branches) max_branches = (num_branches > max_branches) \ and num_branches or max_branches total_branches = total_branches + num_branches for branch in cvs_rev.branches: branches[branch] = None symbols = {} symbols.update(tags) symbols.update(branches) num_symbols = len(symbols.keys()) num_tags = len(tags.keys()) num_branches = len(branches.keys()) avg_tags = total_tags * 1.0 / line_count avg_branches = total_branches * 1.0 / line_count print ' Total CVS Revisions: %d\n' \ ' Total Unique Tags: %d\n' \ ' Peak Revision Tags: %d\n' \ ' Avg. Tags/Revision: %2.1f\n' \ ' Total Unique Branches: %d\n' \ 'Peak Revision Branches: %d\n' \ 'Avg. Branches/Revision: %2.1f\n' \ ' Total Unique Symbols: %d%s\n' \ % (line_count, num_tags, max_tags, avg_tags, num_branches, max_branches, avg_branches, num_symbols, num_symbols == num_tags + num_branches and ' ' or ' (!)', ) if __name__ == "__main__": argc = len(sys.argv) if argc < 2: print 'Usage: %s /path/to/cvs2svn-temporary-directory' \ % (os.path.basename(sys.argv[0])) print __doc__ sys.exit(0) os.chdir(sys.argv[1]) do_it() cvs2svn-2.5.0/contrib/find_illegal_filenames.py0000775000175100017510000000321612203665123022647 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2006 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Search a directory for files whose names contain illegal characters. Usage: find_illegal_filenames.py PATH ... PATH should be a directory. It will be traversed looking for filenames that contain characters that are not allowed in paths in an SVN archive.""" import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from cvs2svn_lib.common import FatalError from cvs2svn_lib.output_option import OutputOption def visit_directory(unused, dirname, files): for file in files: path = os.path.join(dirname, file) try: OutputOption().verify_filename_legal(file) except FatalError: sys.stderr.write('File %r contains illegal characters!\n' % path) if not sys.argv[1:]: sys.stderr.write('usage: %s PATH ...\n' % sys.argv[0]) sys.exit(1) for path in sys.argv[1:]: os.path.walk(path, visit_directory, None) cvs2svn-2.5.0/cvs2bzr-example.options0000664000175100017510000006325112203665123020644 0ustar mhaggermhagger00000000000000# (Be in -*- mode: python; coding: utf-8 -*- mode.) # # ==================================================================== # Copyright (c) 2006-2009 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== # ##################### # ## PLEASE READ ME! ## # ##################### # # This is a template for an options file that can be used to configure # cvs2bzr to convert to Bazaar rather than to Subversion. See # www/cvs2bzr.html and www/cvs2svn.html for general information, and # see the comments in this file for information about what options are # available and how they can be set. # # The program that is run to convert from CVS to Bazaar is called # cvs2bzr. Run it with the --options option, passing it this file # like this: # # cvs2bzr --options=cvs2bzr-example.options # # The output of cvs2bzr is a dump file that can be loaded into Bazaar # using the "bzr fast-import" command. Please read www/cvs2bzr.html # for more information. # # Many options do not have defaults, so it is easier to copy this file # and modify what you need rather than creating a new options file # from scratch. This file is in Python syntax, but you don't need to # know Python to modify it. But if you *do* know Python, then you # will be happy to know that you can use arbitary Python constructs to # do fancy configuration tricks. # # But please be aware of the following: # # * In many places, leading whitespace is significant in Python (it is # used instead of curly braces to group statements together). # Therefore, if you don't know what you are doing, it is best to # leave the whitespace as it is. # # * In normal strings, Python treats a backslash ("\") as an escape # character. Therefore, if you want to specify a string that # contains a backslash, you need either to escape the backslash with # another backslash ("\\"), or use a "raw string", as in one if the # following equivalent examples: # # cvs_executable = 'c:\\windows\\system32\\cvs.exe' # cvs_executable = r'c:\windows\system32\cvs.exe' # # See http://docs.python.org/tutorial/introduction.html#strings for # more information. # # Two identifiers will have been defined before this file is executed, # and can be used freely within this file: # # ctx -- a Ctx object (see cvs2svn_lib/context.py), which holds # many configuration options # # run_options -- an instance of the BzrRunOptions class (see # cvs2svn_lib/bzr_run_options.py), which holds some variables # governing how cvs2bzr is run # Import some modules that are used in setting the options: import os from cvs2svn_lib import config from cvs2svn_lib import changeset_database from cvs2svn_lib.common import CVSTextDecoder from cvs2svn_lib.log import logger from cvs2svn_lib.git_output_option import GitRevisionInlineWriter from cvs2svn_lib.bzr_output_option import BzrOutputOption from cvs2svn_lib.dvcs_common import KeywordHandlingPropertySetter from cvs2svn_lib.revision_manager import NullRevisionCollector from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader from cvs2svn_lib.symbol_strategy import AllBranchRule from cvs2svn_lib.symbol_strategy import AllTagRule from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule from cvs2svn_lib.symbol_strategy import ExcludeVendorBranchRule from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform from cvs2svn_lib.symbol_transform import RegexpSymbolTransform from cvs2svn_lib.symbol_transform import IgnoreSymbolTransform from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform from cvs2svn_lib.property_setters import AutoPropsPropertySetter from cvs2svn_lib.property_setters import ConditionalPropertySetter from cvs2svn_lib.property_setters import cvs_file_is_binary from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter from cvs2svn_lib.property_setters import DefaultEOLStyleSetter from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter from cvs2svn_lib.property_setters import ExecutablePropertySetter from cvs2svn_lib.property_setters import KeywordsPropertySetter from cvs2svn_lib.property_setters import MimeMapper from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter # To choose the level of logging output, uncomment one of the # following lines: #logger.log_level = logger.WARN #logger.log_level = logger.QUIET logger.log_level = logger.NORMAL #logger.log_level = logger.VERBOSE #logger.log_level = logger.DEBUG # The directory to use for temporary files: ctx.tmpdir = r'cvs2bzr-tmp' # cvs2bzr does not need to keep track of what revisions will be # excluded, so leave this option unchanged: ctx.revision_collector = NullRevisionCollector() # cvs2bzr's revision reader is set via the BzrOutputOption constructor, # so leave this option set to None. ctx.revision_reader = None # Change the following line to True if the conversion should only # include the trunk of the repository (i.e., all branches and tags # should be omitted from the conversion): ctx.trunk_only = False # How to convert CVS author names, log messages, and filenames to # Unicode. The first argument to CVSTextDecoder is a list of encoders # that are tried in order in 'strict' mode until one of them succeeds. # If none of those succeeds, then fallback_encoder (if it is # specified) is used in lossy 'replace' mode. Setting a fallback # encoder ensures that the encoder always succeeds, but it can cause # information loss. ctx.cvs_author_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii' ) ctx.cvs_log_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii', eol_fix='\n', ) # You might want to be especially strict when converting filenames to # Unicode (e.g., maybe not specify a fallback_encoding). ctx.cvs_filename_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii' ) # Template for the commit message to be used for initial project # commits. ctx.initial_project_commit_message = ( 'Standard project directories initialized by cvs2bzr.' ) # Template for the commit message to be used for post commits, in # which modifications to a vendor branch are copied back to trunk. # This message can use '%(revnum)d' to include the SVN revision number # of the revision that included the change to the vendor branch # (admittedly rather pointless in a cvs2bzr conversion). ctx.post_commit_message = ( 'This commit was generated by cvs2bzr to track changes on a CVS ' 'vendor branch.' ) # Template for the commit message to be used for commits in which # symbols are created. This message can use '%(symbol_type)s' to # include the type of the symbol ('branch' or 'tag') or # '%(symbol_name)s' to include the name of the symbol. ctx.symbol_commit_message = ( "This commit was manufactured by cvs2bzr to create %(symbol_type)s " "'%(symbol_name)s'." ) # Template for the commit message to be used for commits in which # tags are pseudo-merged back to their source branch. This message can # use '%(symbol_name)s' to include the name of the symbol. # (Not used by default unless you enable tie_tag_fixup_branches on # GitOutputOption.) ctx.tie_tag_ancestry_message = ( "This commit was manufactured by cvs2bzr to tie ancestry for " "tag '%(symbol_name)s' back to the source branch." ) # Some CVS clients for MacOS store resource fork data into CVS along # with the file contents itself by wrapping it all up in a container # format called "AppleSingle". Subversion currently does not support # MacOS resource forks. Nevertheless, sometimes the resource fork # information is not necessary and can be discarded. Set the # following option to True if you would like cvs2bzr to identify files # whose contents are encoded in AppleSingle format, and discard all # but the data fork for such files before committing them to # Subversion. (Please note that AppleSingle contents are identified # by the AppleSingle magic number as the first four bytes of the file. # This check is not failproof, so only set this option if you think # you need it.) ctx.decode_apple_single = False # This option can be set to the name of a filename to which are stored # statistics and conversion decisions about the CVS symbols. ctx.symbol_info_filename = None #ctx.symbol_info_filename = 'symbol-info.txt' # cvs2bzr uses "symbol strategy rules" to help decide how to handle # CVS symbols. The rules in a project's symbol_strategy_rules are # applied in order, and each rule is allowed to modify the symbol. # The result (after each of the rules has been applied) is used for # the conversion. # # 1. A CVS symbol might be used as a tag in one file and as a branch # in another file. cvs2bzr has to decide whether to convert such a # symbol as a tag or as a branch. cvs2bzr uses a series of # heuristic rules to decide how to convert a symbol. The user can # override the default rules for specific symbols or symbols # matching regular expressions. # # 2. cvs2bzr is also capable of excluding symbols from the conversion # (provided no other symbols depend on them. # # 3. CVS does not record unambiguously the line of development from # which a symbol sprouted. cvs2bzr uses a heuristic to choose a # symbol's "preferred parents". # # The standard branch/tag/exclude StrategyRules do not change a symbol # that has already been processed by an earlier rule, so in effect the # first matching rule is the one that is used. global_symbol_strategy_rules = [ # It is possible to specify manually exactly how symbols should be # converted and what line of development should be used as the # preferred parent. To do so, create a file containing the symbol # hints and enable the following option. # # The format of the hints file is described in the documentation # for the --symbol-hints command-line option. The file output by # the --write-symbol-info (i.e., ctx.symbol_info_filename) option # is in the same format. The simplest way to use this option is # to run the conversion through CollateSymbolsPass with # --write-symbol-info option, copy the symbol info and edit it to # create a hints file, then re-start the conversion at # CollateSymbolsPass with this option enabled. #SymbolHintsFileRule('symbol-hints.txt'), # To force all symbols matching a regular expression to be # converted as branches, add rules like the following: #ForceBranchRegexpStrategyRule(r'branch.*'), # To force all symbols matching a regular expression to be # converted as tags, add rules like the following: #ForceTagRegexpStrategyRule(r'tag.*'), # To force all symbols matching a regular expression to be # excluded from the conversion, add rules like the following: #ExcludeRegexpStrategyRule(r'unknown-.*'), # Sometimes people use "cvs import" to get their own source code # into CVS. This practice creates a vendor branch 1.1.1 and # imports the code onto the vendor branch as 1.1.1.1, then copies # the same content to the trunk as version 1.1. Normally, such # vendor branches are useless and they complicate the SVN history # unnecessarily. The following rule excludes any branches that # only existed as a vendor branch with a single import (leaving # only the 1.1 revision). If you want to retain such branches, # comment out the following line. (Please note that this rule # does not exclude vendor *tags*, as they are not so easy to # identify.) ExcludeTrivialImportBranchRule(), # To exclude all vendor branches (branches that had "cvs import"s # on them but no other kinds of commits), uncomment the following # line: #ExcludeVendorBranchRule(), # Usually you want this rule, to convert unambiguous symbols # (symbols that were only ever used as tags or only ever used as # branches in CVS) the same way they were used in CVS: UnambiguousUsageRule(), # If there was ever a commit on a symbol, then it cannot be # converted as a tag. This rule causes all such symbols to be # converted as branches. If you would like to resolve such # ambiguities manually, comment out the following line: BranchIfCommitsRule(), # Last in the list can be a catch-all rule that is used for # symbols that were not matched by any of the more specific rules # above. (Assuming that BranchIfCommitsRule() was included above, # then the symbols that are still indeterminate at this point can # sensibly be converted as branches or tags.) Include at most one # of these lines. If none of these catch-all rules are included, # then the presence of any ambiguous symbols (that haven't been # disambiguated above) is an error: # Convert ambiguous symbols based on whether they were used more # often as branches or as tags: HeuristicStrategyRule(), # Convert all ambiguous symbols as branches: #AllBranchRule(), # Convert all ambiguous symbols as tags: #AllTagRule(), # The last rule is here to choose the preferred parent of branches # and tags, that is, the line of development from which the symbol # sprouts. HeuristicPreferredParentRule(), ] # Specify a username to be used for commits for which CVS doesn't # record the original author (for example, the creation of a branch). # This should be a simple (unix-style) username, but it can be # translated into a Bazaar-style name by the author_transforms map. ctx.username = 'cvs2bzr' # ctx.file_property_setters and ctx.revision_property_setters contain # rules used to set the svn properties on files in the converted # archive. For each file, the rules are tried one by one. Any rule # can add or suppress one or more svn properties. Typically the rules # will not overwrite properties set by a previous rule (though they # are free to do so). ctx.file_property_setters should be used for # properties that remain the same for the life of the file; these # should implement FilePropertySetter. ctx.revision_property_setters # should be used for properties that are allowed to vary from revision # to revision; these should implement RevisionPropertySetter. # # Obviously, SVN properties per se are not interesting for a cvs2bzr # conversion, but some of these properties have side-effects that do # affect the Bazaar output. FIXME: Document this in more detail. ctx.file_property_setters.extend([ # To read auto-props rules from a file, uncomment the following line # and specify a filename. The boolean argument specifies whether # case should be ignored when matching filenames to the filename # patterns found in the auto-props file: #AutoPropsPropertySetter( # r'/home/username/.subversion/config', # ignore_case=True, # ), # To read mime types from a file and use them to set svn:mime-type # based on the filename extensions, uncomment the following line # and specify a filename (see # http://en.wikipedia.org/wiki/Mime.types for information about # mime.types files): #MimeMapper(r'/etc/mime.types', ignore_case=False), # Omit the svn:eol-style property from any files that are listed # as binary (i.e., mode '-kb') in CVS: CVSBinaryFileEOLStyleSetter(), # If the file is binary and its svn:mime-type property is not yet # set, set svn:mime-type to 'application/octet-stream'. CVSBinaryFileDefaultMimeTypeSetter(), # To try to determine the eol-style from the mime type, uncomment # the following line: #EOLStyleFromMimeTypeSetter(), # Choose one of the following lines to set the default # svn:eol-style if none of the above rules applied. The argument # is the svn:eol-style that should be applied, or None if no # svn:eol-style should be set (i.e., the file should be treated as # binary). # # The default is to treat all files as binary unless one of the # previous rules has determined otherwise, because this is the # safest approach. However, if you have been diligent about # marking binary files with -kb in CVS and/or you have used the # above rules to definitely mark binary files as binary, then you # might prefer to use 'native' as the default, as it is usually # the most convenient setting for text files. Other possible # options: 'CRLF', 'CR', 'LF'. DefaultEOLStyleSetter(None), #DefaultEOLStyleSetter('native'), # Prevent svn:keywords from being set on files that have # svn:eol-style unset. SVNBinaryFileKeywordsPropertySetter(), # If svn:keywords has not been set yet, set it based on the file's # CVS mode: KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE), # Set the svn:executable flag on any files that are marked in CVS as # being executable: ExecutablePropertySetter(), # The following causes keywords to be untouched in binary files and # collapsed in all text to be committed: ConditionalPropertySetter( cvs_file_is_binary, KeywordHandlingPropertySetter('untouched'), ), KeywordHandlingPropertySetter('collapsed'), ]) ctx.revision_property_setters.extend([ ]) # To skip the cleanup of temporary files, uncomment the following # option: #ctx.skip_cleanup = True # In CVS, it is perfectly possible to make a single commit that # affects more than one project or more than one branch of a single # project. Subversion also allows such commits. Therefore, by # default, when cvs2bzr sees what looks like a cross-project or # cross-branch CVS commit, it converts it into a # cross-project/cross-branch Subversion commit. # # However, other tools and SCMs have trouble representing # cross-project or cross-branch commits. (For example, Trac's Revtree # plugin, http://www.trac-hacks.org/wiki/RevtreePlugin is confused by # such commits.) Therefore, we provide the following two options to # allow cross-project/cross-branch commits to be suppressed. # cvs2bzr only supports single-project conversions (multiple-project # conversions wouldn't really make sense for Bazaar anyway). So this # option must be set to False: ctx.cross_project_commits = False # Bazaar itself doesn't allow commits that affect more than one branch, # so this option must be set to False: ctx.cross_branch_commits = False # cvs2bzr does not yet handle translating .cvsignore files into # .bzrignore content, so by default, the .cvsignore files are included # in the conversion output. If you would like to omit the .cvsignore # files from the output, set this option to False: ctx.keep_cvsignore = True # By default, it is a fatal error for a CVS ",v" file to appear both # inside and outside of an "Attic" subdirectory (this should never # happen, but frequently occurs due to botched repository # administration). If you would like to retain both versions of such # files, change the following option to True, and the attic version of # the file will be written to a subdirectory called "Attic" in the # output repository: ctx.retain_conflicting_attic_files = False # CVS uses unix login names as author names whereas Bazaar requires # author names to be of the form "foo ". The default is to set # the Bazaar author to "cvsauthor ". author_transforms can # be used to map cvsauthor names (e.g., "jrandom") to a true name and # email address (e.g., "J. Random " for the # example shown). All strings should be either Unicode strings (i.e., # with "u" as a prefix) or 8-bit strings in the utf-8 encoding. The # values can either be strings in the form "name " or tuples # (name, email). Please substitute your own project's usernames here # to use with the author_transforms option of BzrOutputOption below. author_transforms={ 'jrandom' : ('J. Random', 'jrandom@example.com'), 'mhagger' : 'Michael Haggerty ', 'brane' : (u'Branko Čibej', 'brane@xbc.nu'), 'ringstrom' : 'Tobias Ringström ', 'dionisos' : (u'Erik Hülsmann', 'e.huelsmann@gmx.net'), # This one will be used for commits for which CVS doesn't record # the original author, as explained above. 'cvs2bzr' : 'cvs2bzr ', } # This is the main option that causes cvs2bzr to output to a # "fastimport"-format dumpfile rather than to Subversion: ctx.output_option = BzrOutputOption( # The file in which to write the "fastimport" stream: os.path.join(ctx.tmpdir, 'dumpfile.fi'), # Write the file contents inline in the "fastimport" stream, # rather than using a separate blobs file (which "bzr fastimport" # can't handle as easily). revision_writer=GitRevisionInlineWriter( # cvs2bzr uses either RCS's "co" command or CVS's "cvs co -p" to # extract the content of file revisions. Here you can choose # whether to use RCS (faster, but fails in some rare # circumstances) or CVS (much slower, but more reliable). #RCSRevisionReader(co_executable=r'co') CVSRevisionReader(cvs_executable=r'cvs') ), # Optional map from CVS author names to Bazaar author names: author_transforms=author_transforms, ) # Change this option to True to turn on profiling of cvs2bzr (for # debugging purposes): run_options.profiling = False # Should CVSItem -> Changeset database files be memory mapped? In # some tests, using memory mapping speeded up the overall conversion # by about 5%. But this option can cause the conversion to fail with # an out of memory error if the conversion computer runs out of # virtual address space (e.g., when running a very large conversion on # a 32-bit operating system). Therefore it is disabled by default. # Uncomment the following line to allow these database files to be # memory mapped. #changeset_database.use_mmap_for_cvs_item_to_changeset_table = True # Now set the project to be converted to Bazaar. cvs2bzr only supports # single-project conversions, so this method must only be called # once: run_options.set_project( # The filesystem path to the part of the CVS repository (*not* a # CVS working copy) that should be converted. This may be a # subdirectory (i.e., a module) within a larger CVS repository. r'test-data/main-cvsrepos', # A list of symbol transformations that can be used to rename # symbols in this project. symbol_transforms=[ # Use IgnoreSymbolTransforms like the following to completely # ignore symbols matching a regular expression when parsing # the CVS repository, for example to avoid warnings about # branches with two names and to choose the preferred name. # It is *not* recommended to use this instead of # ExcludeRegexpStrategyRule; though more efficient, # IgnoreSymbolTransforms are less flexible and don't exclude # branches correctly. The argument is a Python-style regular # expression that has to match the *whole* CVS symbol name: #IgnoreSymbolTransform(r'nightly-build-tag-.*') # RegexpSymbolTransforms transform symbols textually using a # regular expression. The first argument is a Python regular # expression pattern and the second is a replacement pattern. # The pattern is matched against each symbol name. If it # matches the whole symbol name, then the symbol name is # replaced with the corresponding replacement text. The # replacement can include substitution patterns (e.g., r'\1' # or r'\g'). Typically you will want to use raw strings # (strings with a preceding 'r', like shown in the examples) # for the regexp and its replacement to avoid backslash # substitution within those strings. #RegexpSymbolTransform(r'release-(\d+)_(\d+)', # r'release-\1.\2'), #RegexpSymbolTransform(r'release-(\d+)_(\d+)_(\d+)', # r'release-\1.\2.\3'), # Simple 1:1 character replacements can also be done. The # following transform, which converts backslashes into forward # slashes, should usually be included: ReplaceSubstringsSymbolTransform('\\','/'), # This last rule eliminates leading, trailing, and repeated # slashes within the output symbol names: NormalizePathsSymbolTransform(), ], # See the definition of global_symbol_strategy_rules above for a # description of this option: symbol_strategy_rules=global_symbol_strategy_rules, # Exclude paths from the conversion. Should be relative to # repository path and use forward slashes: #exclude_paths=['file-to-exclude.txt,v', 'dir/to/exclude'], ) cvs2svn-2.5.0/cvs2bzr0000775000175100017510000000464212203665123015523 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2009 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== import sys # Make sure that a supported version of Python is being used. Do this # as early as possible, using only code compatible with Python 1.5.2 # and Python 3.x before the check. Remember: # # Python 1.5.2 doesn't have sys.version_info or ''.join(). # Python 3.0 doesn't have string.join(). # There are plans to start deprecating the string formatting '%' # operator in Python 3.1 (but we use it here anyway). version_error = """\ ERROR: cvs2bzr requires Python 2, version 2.4 or later; it does not work with Python 3. You are currently using""" version_advice = """\ Please restart cvs2bzr using a different version of the Python interpreter. Visit http://www.python.org or consult your local system administrator if you need help. HINT: If you already have a usable Python version installed, it might be possible to invoke cvs2bzr with the correct Python interpreter by typing something like 'python2.5 """ + sys.argv[0] + """ [...]'. """ try: version = sys.version_info except AttributeError: # This is probably a pre-2.0 version of Python. sys.stderr.write(version_error + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(sys.version + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(version_advice) sys.exit(1) if not ((2,4) <= version < (3,0)): sys.stderr.write( version_error + ' version %d.%d.%d.\n' % (version[0], version[1], version[2],) ) sys.stderr.write(version_advice) sys.exit(1) import os from cvs2svn_lib.common import FatalException from cvs2svn_lib.main import bzr_main try: bzr_main(os.path.basename(sys.argv[0]), sys.argv[1:]) except FatalException, e: sys.stderr.write(str(e) + '\n') sys.exit(1) cvs2svn-2.5.0/cvs2git0000775000175100017510000000464212203665123015511 0ustar mhaggermhagger00000000000000#!/usr/bin/env python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2008 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== import sys # Make sure that a supported version of Python is being used. Do this # as early as possible, using only code compatible with Python 1.5.2 # and Python 3.x before the check. Remember: # # Python 1.5.2 doesn't have sys.version_info or ''.join(). # Python 3.0 doesn't have string.join(). # There are plans to start deprecating the string formatting '%' # operator in Python 3.1 (but we use it here anyway). version_error = """\ ERROR: cvs2git requires Python 2, version 2.4 or later; it does not work with Python 3. You are currently using""" version_advice = """\ Please restart cvs2git using a different version of the Python interpreter. Visit http://www.python.org or consult your local system administrator if you need help. HINT: If you already have a usable Python version installed, it might be possible to invoke cvs2git with the correct Python interpreter by typing something like 'python2.5 """ + sys.argv[0] + """ [...]'. """ try: version = sys.version_info except AttributeError: # This is probably a pre-2.0 version of Python. sys.stderr.write(version_error + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(sys.version + '\n') sys.stderr.write('-'*70 + '\n') sys.stderr.write(version_advice) sys.exit(1) if not ((2,4) <= version < (3,0)): sys.stderr.write( version_error + ' version %d.%d.%d.\n' % (version[0], version[1], version[2],) ) sys.stderr.write(version_advice) sys.exit(1) import os from cvs2svn_lib.common import FatalException from cvs2svn_lib.main import git_main try: git_main(os.path.basename(sys.argv[0]), sys.argv[1:]) except FatalException, e: sys.stderr.write(str(e) + '\n') sys.exit(1) cvs2svn-2.5.0/dist.sh0000775000175100017510000000146012203665123015477 0ustar mhaggermhagger00000000000000#!/bin/sh set -e # Build a cvs2svn distribution. VERSION=`python cvs2svn_lib/version.py` echo "Building cvs2svn ${VERSION}" WC_REV=`svnversion -n .` DIST_BASE=cvs2svn-${VERSION} DIST_FULL=${DIST_BASE}.tar.gz if echo ${WC_REV} | grep -q -e '[^0-9]'; then echo "Packaging requires a single-revision, pristine working copy." echo "" echo "Run 'svn update' to get a working copy without mixed revisions," echo "and make sure there are no local modifications." exit 1 fi # Clean up anything that might have been left from a previous run. rm -rf dist MANIFEST ${DIST_FULL} make clean # Build the dist, Python's way. ./setup.py sdist mv dist/${DIST_FULL} . # Clean up after this run. rm -rf dist MANIFEST # We're outta here. echo "" echo "Done:" echo "" ls -l ${DIST_FULL} md5sum ${DIST_FULL} echo "" cvs2svn-2.5.0/HACKING0000664000175100017510000000475613206445076015206 0ustar mhaggermhagger00000000000000 -*-text-*- =========================== Hacker's Guide To cvs2svn =========================== This project tends to use the same social and technical guidelines (where applicable) as Subversion itself. You can view them online at http://subversion.apache.org/docs/community-guide/conventions.html. * The source code is accessible from two places: * The primary repository for cvs2svn is held in Subversion under the following URL: http://cvs2svn.tigris.org/svn/cvs2svn The repository is readable by anybody using username="guest", password="" (i.e., just press return). * Michael Haggerty maintains a Git mirror of the trunk and some other branches at GitHub: https://github.com/mhagger/cvs2svn The branches in this repository are subject to being rebased and/or rewritten, though I'll try to avoid doing so with "master" and release branches. Feel free to fork this repository and push your patches to your fork, but please *also* email the dev mailing list with any discussion and a link to the patches. The mailing list remains the main forum for discussing changes to cvs2svn. * Read the files under doc/, especially: * doc/design-notes.txt gives a high-level description of the algorithm used by cvs2svn to make sense of the CVS history. * doc/symbol-notes.txt describes how CVS symbols are handled. * doc/making-releases.txt describes the procedure for making a new release of cvs2svn. * Read the files under www/, especially: * www/features.html describes abstractly many of the CVS peculiarities that cvs2svn attempts to deal with. Please note that changes committed to the trunk version of www/ are automatically deployed to the cvs2svn project website. * Read the class and method docstrings. * Adhere to the code formatting conventions of the rest of the project (e.g., limit line length to 79 characters). * We no longer require the exhaustive commit messages required by the Subversion project. But please include commit messages that: * Describe the *reason* for the change. * Attribute changes to their original author using lines like Patch by: Joe Schmo * Please put a new test in run-tests.py when you fix a bug. * Use 2 spaces between sentences in comments and docstrings. (This helps sentence-motion commands in some editors.) Happy hacking! cvs2svn-2.5.0/cvs2git-example.options0000664000175100017510000006726113206445076020646 0ustar mhaggermhagger00000000000000# (Be in -*- mode: python; coding: utf-8 -*- mode.) # # ==================================================================== # Copyright (c) 2006-2010 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== # ##################### # ## PLEASE READ ME! ## # ##################### # # This is a template for an options file that can be used to configure # cvs2git to convert to git rather than to Subversion. See # www/cvs2git.html and www/cvs2svn.html for general information, and # see the comments in this file for information about what options are # available and how they can be set. # # The program that is run to convert from CVS to git is called # cvs2git. Run it with the --options option, passing it this file # like this: # # cvs2git --options=cvs2git-example.options # # The output of cvs2git is a blob file and a dump file that can be # loaded into git using the "git fast-import" command. Please read # www/cvs2git.html for more information. # # Many options do not have defaults, so it is easier to copy this file # and modify what you need rather than creating a new options file # from scratch. This file is in Python syntax, but you don't need to # know Python to modify it. But if you *do* know Python, then you # will be happy to know that you can use arbitary Python constructs to # do fancy configuration tricks. # # But please be aware of the following: # # * In many places, leading whitespace is significant in Python (it is # used instead of curly braces to group statements together). # Therefore, if you don't know what you are doing, it is best to # leave the whitespace as it is. # # * In normal strings, Python treats a backslash ("\") as an escape # character. Therefore, if you want to specify a string that # contains a backslash, you need either to escape the backslash with # another backslash ("\\"), or use a "raw string", as in one if the # following equivalent examples: # # cvs_executable = 'c:\\windows\\system32\\cvs.exe' # cvs_executable = r'c:\windows\system32\cvs.exe' # # See http://docs.python.org/tutorial/introduction.html#strings for # more information. # # Two identifiers will have been defined before this file is executed, # and can be used freely within this file: # # ctx -- a Ctx object (see cvs2svn_lib/context.py), which holds # many configuration options # # run_options -- an instance of the GitRunOptions class (see # cvs2svn_lib/git_run_options.py), which holds some variables # governing how cvs2git is run # Import some modules that are used in setting the options: import os from cvs2svn_lib import config from cvs2svn_lib import changeset_database from cvs2svn_lib.common import CVSTextDecoder from cvs2svn_lib.log import logger from cvs2svn_lib.git_revision_collector import GitRevisionCollector from cvs2svn_lib.external_blob_generator import ExternalBlobGenerator from cvs2svn_lib.git_output_option import GitRevisionMarkWriter from cvs2svn_lib.git_output_option import GitOutputOption from cvs2svn_lib.dvcs_common import KeywordHandlingPropertySetter from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader from cvs2svn_lib.symbol_strategy import AllBranchRule from cvs2svn_lib.symbol_strategy import AllTagRule from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule from cvs2svn_lib.symbol_strategy import ExcludeVendorBranchRule from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform from cvs2svn_lib.symbol_transform import RegexpSymbolTransform from cvs2svn_lib.symbol_transform import IgnoreSymbolTransform from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform from cvs2svn_lib.property_setters import AutoPropsPropertySetter from cvs2svn_lib.property_setters import ConditionalPropertySetter from cvs2svn_lib.property_setters import cvs_file_is_binary from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter from cvs2svn_lib.property_setters import DefaultEOLStyleSetter from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter from cvs2svn_lib.property_setters import ExecutablePropertySetter from cvs2svn_lib.property_setters import KeywordsPropertySetter from cvs2svn_lib.property_setters import MimeMapper from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter # To choose the level of logging output, uncomment one of the # following lines: #logger.log_level = logger.WARN #logger.log_level = logger.QUIET logger.log_level = logger.NORMAL #logger.log_level = logger.VERBOSE #logger.log_level = logger.DEBUG # The directory to use for temporary files: ctx.tmpdir = r'cvs2git-tmp' # During FilterSymbolsPass, cvs2git records the contents of file # revisions into a "blob" file in git-fast-import format. The # ctx.revision_collector option configures that process. Choose one # of the two versions and customize its options. # This first alternative is much slower but is better tested and has a # chance of working with CVSNT repositories. It invokes CVS or RCS to # reconstuct the contents of CVS file revisions: ctx.revision_collector = GitRevisionCollector( # The following option specifies how the revision contents of the # RCS files should be read. # # RCSRevisionReader uses RCS's "co" program to extract the # revision contents of the RCS files during CollectRevsPass. The # constructor argument specifies how to invoke the "co" # executable. # # CVSRevisionReader uses the "cvs" program to extract the revision # contents out of the RCS files during OutputPass. This option is # considerably slower than RCSRevisionReader because "cvs" is # considerably slower than "co". However, it works in some # situations where RCSRevisionReader fails; see the HTML # documentation of the "--use-cvs" option for details. The # constructor argument specifies how to invoke the "co" # executable. It is also possible to pass a global_options # parameter to CVSRevisionReader to specify which options should # be passed to the cvs command. By default the correct options # are usually chosen, but for CVSNT you might want to add # global_options=['-q', '-N', '-f']. # # Uncomment one of the two following lines: #RCSRevisionReader(co_executable=r'co'), CVSRevisionReader(cvs_executable=r'cvs'), # The file in which to write the git-fast-import stream that # contains the file revision contents. If None, it will be # written to a temporary file then streamed to stdout in # OutputPass: blob_filename=os.path.join(ctx.tmpdir, 'git-blob.dat'), ) # This second alternative is vastly faster than the version above. It # uses an external Python program to reconstruct the contents of CVS # file revisions and write it to the specified file. If blob_filename # is None, the blobs will be written to a temporary file then streamed # to stdout in OutputPass: #ctx.revision_collector = ExternalBlobGenerator( # blob_filename=os.path.join(ctx.tmpdir, 'git-blob.dat'), # ) # cvs2git doesn't need a revision reader because OutputPass only # refers to blobs that were output during CollectRevsPass, so leave # this option set to None. ctx.revision_reader = None # Change the following line to True if the conversion should only # include the trunk of the repository (i.e., all branches and tags # should be omitted from the conversion): ctx.trunk_only = False # How to convert CVS author names, log messages, and filenames to # Unicode. The first argument to CVSTextDecoder is a list of encoders # that are tried in order in 'strict' mode until one of them succeeds. # If none of those succeeds, then fallback_encoder (if it is # specified) is used in lossy 'replace' mode. Setting a fallback # encoder ensures that the encoder always succeeds, but it can cause # information loss. ctx.cvs_author_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii' ) ctx.cvs_log_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii', eol_fix='\n', ) # You might want to be especially strict when converting filenames to # Unicode (e.g., maybe not specify a fallback_encoding). ctx.cvs_filename_decoder = CVSTextDecoder( [ #'utf8', #'latin1', 'ascii', ], #fallback_encoding='ascii' ) # Template for the commit message to be used for initial project # commits. ctx.initial_project_commit_message = ( 'Standard project directories initialized by cvs2git.' ) # Template for the commit message to be used for post commits, in # which modifications to a vendor branch are copied back to trunk. # This message can use '%(revnum)d' to include the SVN revision number # of the revision that included the change to the vendor branch # (admittedly rather pointless in a cvs2git conversion). ctx.post_commit_message = ( 'This commit was generated by cvs2git to track changes on a CVS ' 'vendor branch.' ) # Template for the commit message to be used for commits in which # symbols are created. This message can use '%(symbol_type)s' to # include the type of the symbol ('branch' or 'tag') or # '%(symbol_name)s' to include the name of the symbol. ctx.symbol_commit_message = ( "This commit was manufactured by cvs2git to create %(symbol_type)s " "'%(symbol_name)s'." ) # Template for the commit message to be used for commits in which # tags are pseudo-merged back to their source branch. This message can # use '%(symbol_name)s' to include the name of the symbol. # (Not used by default unless you enable tie_tag_fixup_branches on # GitOutputOption.) ctx.tie_tag_ancestry_message = ( "This commit was manufactured by cvs2git to tie ancestry for " "tag '%(symbol_name)s' back to the source branch." ) # Some CVS clients for MacOS store resource fork data into CVS along # with the file contents itself by wrapping it all up in a container # format called "AppleSingle". Subversion currently does not support # MacOS resource forks. Nevertheless, sometimes the resource fork # information is not necessary and can be discarded. Set the # following option to True if you would like cvs2git to identify files # whose contents are encoded in AppleSingle format, and discard all # but the data fork for such files before committing them to # Subversion. (Please note that AppleSingle contents are identified # by the AppleSingle magic number as the first four bytes of the file. # This check is not failproof, so only set this option if you think # you need it.) ctx.decode_apple_single = False # This option can be set to the name of a filename to which are stored # statistics and conversion decisions about the CVS symbols. ctx.symbol_info_filename = None #ctx.symbol_info_filename = 'symbol-info.txt' # cvs2git uses "symbol strategy rules" to help decide how to handle # CVS symbols. The rules in a project's symbol_strategy_rules are # applied in order, and each rule is allowed to modify the symbol. # The result (after each of the rules has been applied) is used for # the conversion. # # 1. A CVS symbol might be used as a tag in one file and as a branch # in another file. cvs2git has to decide whether to convert such a # symbol as a tag or as a branch. cvs2git uses a series of # heuristic rules to decide how to convert a symbol. The user can # override the default rules for specific symbols or symbols # matching regular expressions. # # 2. cvs2git is also capable of excluding symbols from the conversion # (provided no other symbols depend on them. # # 3. CVS does not record unambiguously the line of development from # which a symbol sprouted. cvs2git uses a heuristic to choose a # symbol's "preferred parents". # # The standard branch/tag/exclude StrategyRules do not change a symbol # that has already been processed by an earlier rule, so in effect the # first matching rule is the one that is used. global_symbol_strategy_rules = [ # It is possible to specify manually exactly how symbols should be # converted and what line of development should be used as the # preferred parent. To do so, create a file containing the symbol # hints and enable the following option. # # The format of the hints file is described in the documentation # for the --symbol-hints command-line option. The file output by # the --write-symbol-info (i.e., ctx.symbol_info_filename) option # is in the same format. The simplest way to use this option is # to run the conversion through CollateSymbolsPass with # --write-symbol-info option, copy the symbol info and edit it to # create a hints file, then re-start the conversion at # CollateSymbolsPass with this option enabled. #SymbolHintsFileRule('symbol-hints.txt'), # To force all symbols matching a regular expression to be # converted as branches, add rules like the following: #ForceBranchRegexpStrategyRule(r'branch.*'), # To force all symbols matching a regular expression to be # converted as tags, add rules like the following: #ForceTagRegexpStrategyRule(r'tag.*'), # To force all symbols matching a regular expression to be # excluded from the conversion, add rules like the following: #ExcludeRegexpStrategyRule(r'unknown-.*'), # Sometimes people use "cvs import" to get their own source code # into CVS. This practice creates a vendor branch 1.1.1 and # imports the code onto the vendor branch as 1.1.1.1, then copies # the same content to the trunk as version 1.1. Normally, such # vendor branches are useless and they complicate the SVN history # unnecessarily. The following rule excludes any branches that # only existed as a vendor branch with a single import (leaving # only the 1.1 revision). If you want to retain such branches, # comment out the following line. (Please note that this rule # does not exclude vendor *tags*, as they are not so easy to # identify.) ExcludeTrivialImportBranchRule(), # To exclude all vendor branches (branches that had "cvs import"s # on them but no other kinds of commits), uncomment the following # line: #ExcludeVendorBranchRule(), # Usually you want this rule, to convert unambiguous symbols # (symbols that were only ever used as tags or only ever used as # branches in CVS) the same way they were used in CVS: UnambiguousUsageRule(), # If there was ever a commit on a symbol, then it cannot be # converted as a tag. This rule causes all such symbols to be # converted as branches. If you would like to resolve such # ambiguities manually, comment out the following line: BranchIfCommitsRule(), # Last in the list can be a catch-all rule that is used for # symbols that were not matched by any of the more specific rules # above. (Assuming that BranchIfCommitsRule() was included above, # then the symbols that are still indeterminate at this point can # sensibly be converted as branches or tags.) Include at most one # of these lines. If none of these catch-all rules are included, # then the presence of any ambiguous symbols (that haven't been # disambiguated above) is an error: # Convert ambiguous symbols based on whether they were used more # often as branches or as tags: HeuristicStrategyRule(), # Convert all ambiguous symbols as branches: #AllBranchRule(), # Convert all ambiguous symbols as tags: #AllTagRule(), # The last rule is here to choose the preferred parent of branches # and tags, that is, the line of development from which the symbol # sprouts. HeuristicPreferredParentRule(), ] # Specify a username to be used for commits for which CVS doesn't # record the original author (for example, the creation of a branch). # This should be a simple (unix-style) username, but it can be # translated into a git-style name by the author_transforms map. ctx.username = 'cvs2git' # ctx.file_property_setters and ctx.revision_property_setters contain # rules used to set the svn properties on files in the converted # archive. For each file, the rules are tried one by one. Any rule # can add or suppress one or more svn properties. Typically the rules # will not overwrite properties set by a previous rule (though they # are free to do so). ctx.file_property_setters should be used for # properties that remain the same for the life of the file; these # should implement FilePropertySetter. ctx.revision_property_setters # should be used for properties that are allowed to vary from revision # to revision; these should implement RevisionPropertySetter. # # Obviously, SVN properties per se are not interesting for a cvs2git # conversion, but some of these properties have side-effects that do # affect the git output. FIXME: Document this in more detail. ctx.file_property_setters.extend([ # To read auto-props rules from a file, uncomment the following line # and specify a filename. The boolean argument specifies whether # case should be ignored when matching filenames to the filename # patterns found in the auto-props file: #AutoPropsPropertySetter( # r'/home/username/.subversion/config', # ignore_case=True, # ), # To read mime types from a file and use them to set svn:mime-type # based on the filename extensions, uncomment the following line # and specify a filename (see # http://en.wikipedia.org/wiki/Mime.types for information about # mime.types files): #MimeMapper(r'/etc/mime.types', ignore_case=False), # Omit the svn:eol-style property from any files that are listed # as binary (i.e., mode '-kb') in CVS: CVSBinaryFileEOLStyleSetter(), # If the file is binary and its svn:mime-type property is not yet # set, set svn:mime-type to 'application/octet-stream'. CVSBinaryFileDefaultMimeTypeSetter(), # To try to determine the eol-style from the mime type, uncomment # the following line: #EOLStyleFromMimeTypeSetter(), # Choose one of the following lines to set the default # svn:eol-style if none of the above rules applied. The argument # is the svn:eol-style that should be applied, or None if no # svn:eol-style should be set (i.e., the file should be treated as # binary). # # The default is to treat all files as binary unless one of the # previous rules has determined otherwise, because this is the # safest approach. However, if you have been diligent about # marking binary files with -kb in CVS and/or you have used the # above rules to definitely mark binary files as binary, then you # might prefer to use 'native' as the default, as it is usually # the most convenient setting for text files. Other possible # options: 'CRLF', 'CR', 'LF'. DefaultEOLStyleSetter(None), #DefaultEOLStyleSetter('native'), # Prevent svn:keywords from being set on files that have # svn:eol-style unset. SVNBinaryFileKeywordsPropertySetter(), # If svn:keywords has not been set yet, set it based on the file's # CVS mode: KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE), # Set the svn:executable flag on any files that are marked in CVS as # being executable: ExecutablePropertySetter(), # The following causes keywords to be untouched in binary files and # collapsed in all text to be committed: ConditionalPropertySetter( cvs_file_is_binary, KeywordHandlingPropertySetter('untouched'), ), KeywordHandlingPropertySetter('collapsed'), ]) ctx.revision_property_setters.extend([ ]) # To skip the cleanup of temporary files, uncomment the following # option: #ctx.skip_cleanup = True # In CVS, it is perfectly possible to make a single commit that # affects more than one project or more than one branch of a single # project. Subversion also allows such commits. Therefore, by # default, when cvs2git sees what looks like a cross-project or # cross-branch CVS commit, it converts it into a # cross-project/cross-branch Subversion commit. # # However, other tools and SCMs have trouble representing # cross-project or cross-branch commits. (For example, Trac's Revtree # plugin, http://www.trac-hacks.org/wiki/RevtreePlugin is confused by # such commits.) Therefore, we provide the following two options to # allow cross-project/cross-branch commits to be suppressed. # cvs2git only supports single-project conversions (multiple-project # conversions wouldn't really make sense for git anyway). So this # option must be set to False: ctx.cross_project_commits = False # git itself doesn't allow commits that affect more than one branch, # so this option must be set to False: ctx.cross_branch_commits = False # cvs2git does not yet handle translating .cvsignore files into # .gitignore files, so by default, the .cvsignore files are included # in the conversion output. If you would like to omit the .cvsignore # files from the output, set this option to False: ctx.keep_cvsignore = True # By default, it is a fatal error for a CVS ",v" file to appear both # inside and outside of an "Attic" subdirectory (this should never # happen, but frequently occurs due to botched repository # administration). If you would like to retain both versions of such # files, change the following option to True, and the attic version of # the file will be written to a subdirectory called "Attic" in the # output repository: ctx.retain_conflicting_attic_files = False # CVS uses unix login names as author names whereas git requires # author names to be of the form "foo ". The default is to set # the git author to "cvsauthor ". author_transforms can be # used to map cvsauthor names (e.g., "jrandom") to a true name and # email address (e.g., "J. Random " for the # example shown). All strings should be either Unicode strings (i.e., # with "u" as a prefix) or 8-bit strings in the utf-8 encoding. The # values can either be strings in the form "name " or tuples # (name, email). Please substitute your own project's usernames here # to use with the author_transforms option of GitOutputOption below. author_transforms={ 'jrandom' : ('J. Random', 'jrandom@example.com'), 'mhagger' : 'Michael Haggerty ', 'brane' : (u'Branko Čibej', 'brane@xbc.nu'), 'ringstrom' : 'Tobias Ringström ', 'dionisos' : (u'Erik Hülsmann', 'e.huelsmann@gmx.net'), # This one will be used for commits for which CVS doesn't record # the original author, as explained above. 'cvs2git' : 'cvs2git ', } # This is the main option that causes cvs2git to output to a # "fastimport"-format dumpfile rather than to Subversion: ctx.output_option = GitOutputOption( # The blobs will be written via the revision recorder, so in # OutputPass we only have to emit references to the blob marks: GitRevisionMarkWriter(), # The file in which to write the git-fast-import stream that # contains the changesets and branch/tag information, or None # to write it to stdout: dump_filename=os.path.join(ctx.tmpdir, 'git-dump.dat'), # Optional map from CVS author names to git author names: author_transforms=author_transforms, ) # Change this option to True to turn on profiling of cvs2git (for # debugging purposes): run_options.profiling = False # Should CVSItem -> Changeset database files be memory mapped? In # some tests, using memory mapping speeded up the overall conversion # by about 5%. But this option can cause the conversion to fail with # an out of memory error if the conversion computer runs out of # virtual address space (e.g., when running a very large conversion on # a 32-bit operating system). Therefore it is disabled by default. # Uncomment the following line to allow these database files to be # memory mapped. #changeset_database.use_mmap_for_cvs_item_to_changeset_table = True # Now set the project to be converted to git. cvs2git only supports # single-project conversions, so this method must only be called # once: run_options.set_project( # The filesystem path to the part of the CVS repository (*not* a # CVS working copy) that should be converted. This may be a # subdirectory (i.e., a module) within a larger CVS repository. r'test-data/main-cvsrepos', # A list of symbol transformations that can be used to rename # symbols in this project. symbol_transforms=[ # Use IgnoreSymbolTransforms like the following to completely # ignore symbols matching a regular expression when parsing # the CVS repository, for example to avoid warnings about # branches with two names and to choose the preferred name. # It is *not* recommended to use this instead of # ExcludeRegexpStrategyRule; though more efficient, # IgnoreSymbolTransforms are less flexible and don't exclude # branches correctly. The argument is a Python-style regular # expression that has to match the *whole* CVS symbol name: #IgnoreSymbolTransform(r'nightly-build-tag-.*') # RegexpSymbolTransforms transform symbols textually using a # regular expression. The first argument is a Python regular # expression pattern and the second is a replacement pattern. # The pattern is matched against each symbol name. If it # matches the whole symbol name, then the symbol name is # replaced with the corresponding replacement text. The # replacement can include substitution patterns (e.g., r'\1' # or r'\g'). Typically you will want to use raw strings # (strings with a preceding 'r', like shown in the examples) # for the regexp and its replacement to avoid backslash # substitution within those strings. #RegexpSymbolTransform(r'release-(\d+)_(\d+)', # r'release-\1.\2'), #RegexpSymbolTransform(r'release-(\d+)_(\d+)_(\d+)', # r'release-\1.\2.\3'), # Simple 1:1 character replacements can also be done. The # following transform, which converts backslashes into forward # slashes, should usually be included: ReplaceSubstringsSymbolTransform('\\','/'), # This last rule eliminates leading, trailing, and repeated # slashes within the output symbol names: NormalizePathsSymbolTransform(), ], # See the definition of global_symbol_strategy_rules above for a # description of this option: symbol_strategy_rules=global_symbol_strategy_rules, # Exclude paths from the conversion. Should be relative to # repository path and use forward slashes: #exclude_paths=['file-to-exclude.txt,v', 'dir/to/exclude'], ) cvs2svn-2.5.0/cvs2svn_rcsparse/0000775000175100017510000000000013206450463017505 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/cvs2svn_rcsparse/run-tests.py0000775000175100017510000000425012203665123022024 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2007 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://viewvc.tigris.org/. # ==================================================================== """Run tests of rcsparse code.""" import sys import os import glob from cStringIO import StringIO from difflib import Differ # Since there is nontrivial logic in __init__.py, we have to import # parse() via that file. First make sure that the directory # containing this script is in the path: script_dir = os.path.dirname(sys.argv[0]) sys.path.insert(0, script_dir) from __init__ import parse from parse_rcs_file import LoggingSink test_dir = os.path.join(script_dir, 'test-data') filelist = glob.glob(os.path.join(test_dir, '*,v')) filelist.sort() all_tests_ok = 1 for filename in filelist: sys.stderr.write('%s: ' % (filename,)) f = StringIO() try: parse(open(filename, 'rb'), LoggingSink(f)) except Exception, e: sys.stderr.write('Error parsing file: %s!\n' % (e,)) all_tests_ok = 0 else: output = f.getvalue() expected_output_filename = filename[:-2] + '.out' expected_output = open(expected_output_filename, 'rb').read() if output == expected_output: sys.stderr.write('OK\n') else: sys.stderr.write('Output does not match expected output!\n') differ = Differ() for diffline in differ.compare( expected_output.splitlines(1), output.splitlines(1) ): sys.stderr.write(diffline) all_tests_ok = 0 if all_tests_ok: sys.exit(0) else: sys.exit(1) cvs2svn-2.5.0/cvs2svn_rcsparse/default.py0000664000175100017510000001146613206445076021517 0ustar mhaggermhagger00000000000000# -*-python-*- # # Copyright (C) 1999-2014 The ViewCVS Group. All Rights Reserved. # # By using this file, you agree to the terms and conditions set forth in # the LICENSE.html file which can be found at the top level of the ViewVC # distribution or at http://viewvc.org/license-1.html. # # For more information, visit http://viewvc.org/ # # ----------------------------------------------------------------------- # # This file was originally based on portions of the blame.py script by # Curt Hagenlocher. # # ----------------------------------------------------------------------- import string import common class _TokenStream: token_term = string.whitespace + ";:" try: token_term = frozenset(token_term) except NameError: pass # the algorithm is about the same speed for any CHUNK_SIZE chosen. # grab a good-sized chunk, but not too large to overwhelm memory. # note: we use a multiple of a standard block size CHUNK_SIZE = 192 * 512 # about 100k # CHUNK_SIZE = 5 # for debugging, make the function grind... def __init__(self, file): self.rcsfile = file self.idx = 0 self.buf = self.rcsfile.read(self.CHUNK_SIZE) if self.buf == '': raise RuntimeError, 'EOF' def get(self): "Get the next token from the RCS file." # Note: we can afford to loop within Python, examining individual # characters. For the whitespace and tokens, the number of iterations # is typically quite small. Thus, a simple iterative loop will beat # out more complex solutions. buf = self.buf lbuf = len(buf) idx = self.idx while 1: if idx == lbuf: buf = self.rcsfile.read(self.CHUNK_SIZE) if buf == '': # signal EOF by returning None as the token del self.buf # so we fail if get() is called again return None lbuf = len(buf) idx = 0 if buf[idx] not in string.whitespace: break idx = idx + 1 if buf[idx] in ';:': self.buf = buf self.idx = idx + 1 return buf[idx] if buf[idx] != '@': end = idx + 1 token = '' while 1: # find token characters in the current buffer while end < lbuf and buf[end] not in self.token_term: end = end + 1 token = token + buf[idx:end] if end < lbuf: # we stopped before the end, so we have a full token idx = end break # we stopped at the end of the buffer, so we may have a partial token buf = self.rcsfile.read(self.CHUNK_SIZE) if buf == '': # signal EOF by returning None as the token del self.buf # so we fail if get() is called again return None lbuf = len(buf) idx = end = 0 self.buf = buf self.idx = idx return token # a "string" which starts with the "@" character. we'll skip it when we # search for content. idx = idx + 1 chunks = [ ] while 1: if idx == lbuf: idx = 0 buf = self.rcsfile.read(self.CHUNK_SIZE) if buf == '': raise RuntimeError, 'EOF' lbuf = len(buf) i = string.find(buf, '@', idx) if i == -1: chunks.append(buf[idx:]) idx = lbuf continue if i == lbuf - 1: chunks.append(buf[idx:i]) idx = 0 buf = '@' + self.rcsfile.read(self.CHUNK_SIZE) if buf == '@': raise RuntimeError, 'EOF' lbuf = len(buf) continue if buf[i + 1] == '@': chunks.append(buf[idx:i+1]) idx = i + 2 continue chunks.append(buf[idx:i]) self.buf = buf self.idx = i + 1 return string.join(chunks, '') # _get = get # def get(self): token = self._get() print 'T:', `token` return token def match(self, match): "Try to match the next token from the input buffer." token = self.get() if token != match: raise common.RCSExpected(token, match) def unget(self, token): "Put this token back, for the next get() to return." # Override the class' .get method with a function which clears the # overridden method then returns the pushed token. Since this function # will not be looked up via the class mechanism, it should be a "normal" # function, meaning it won't have "self" automatically inserted. # Therefore, we need to pass both self and the token thru via defaults. # note: we don't put this into the input buffer because it may have been # @-unescaped already. def give_it_back(self=self, token=token): del self.get return token self.get = give_it_back def mget(self, count): "Return multiple tokens. 'next' is at the end." result = [ ] for i in range(count): result.append(self.get()) result.reverse() return result class Parser(common._Parser): stream_class = _TokenStream cvs2svn-2.5.0/cvs2svn_rcsparse/common.py0000664000175100017510000003612513206445076021362 0ustar mhaggermhagger00000000000000# -*-python-*- # # Copyright (C) 1999-2014 The ViewCVS Group. All Rights Reserved. # # By using this file, you agree to the terms and conditions set forth in # the LICENSE.html file which can be found at the top level of the ViewVC # distribution or at http://viewvc.org/license-1.html. # # For more information, visit http://viewvc.org/ # # ----------------------------------------------------------------------- """common.py: common classes and functions for the RCS parsing tools.""" import calendar import string class Sink: """Interface to be implemented by clients. The RCS parser calls this as it parses the RCS file. All these methods have stub implementations that do nothing, so you only have to override the callbacks that you care about. """ def set_head_revision(self, revision): """Reports the head revision for this RCS file. This is the value of the 'head' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameter: REVISION is a string containing a revision number. This is an actual revision number, not a branch number. """ pass def set_principal_branch(self, branch_name): """Reports the principal branch for this RCS file. This is only called if the principal branch is not trunk. This is the value of the 'branch' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameter: BRANCH_NAME is a string containing a branch number. If this function is called, the parameter is typically "1.1.1", indicating the vendor branch. """ pass def set_access(self, accessors): """Reports the access control list for this RCS file. This function is only called if the ACL is set. If this function is not called then there is no ACL and all users are allowed access. This is the value of the 'access' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameter: ACCESSORS is a list of strings. Each string is a username. The user is allowed access if and only if their username is in the list, OR the user owns the RCS file on disk, OR the user is root. Note that CVS typically doesn't use this field. """ pass def define_tag(self, name, revision): """Reports a tag or branch definition. This function will be called once for each tag or branch. This is taken from the 'symbols' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameters: NAME is a string containing the tag or branch name. REVISION is a string containing a revision number. This may be an actual revision number (for a tag) or a branch number. The revision number consists of a number of decimal components separated by dots. There are three common forms. If there are an odd number of components, it's a branch. Otherwise, if the next-to-last component is zero, it's a branch (and the next-to-last component is an artifact of CVS and should not be shown to the user). Otherwise, it's a tag. This function is called in the order that the tags appear in the RCS file header. For CVS, this appears to be in reverse chronological order of tag/branch creation. """ pass def set_locker(self, revision, locker): """Reports a lock on this RCS file. This function will be called once for each lock. This is taken from the 'locks' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameters: REVISION is a string containing a revision number. This is an actual revision number, not a branch number. LOCKER is a string containing a username. """ pass def set_locking(self, mode): """Signals strict locking mode. This function will be called if and only if the RCS file is in strict locking mode. This is taken from the 'strict' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameters: MODE is always the string 'strict'. """ pass def set_comment(self, comment): """Reports the comment for this RCS file. This is the value of the 'comment' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameter: COMMENT is a string containing the comment. This may be multi-line. This field does not seem to be used by CVS. """ pass def set_expansion(self, mode): """Reports the keyword expansion mode for this RCS file. This is the value of the 'expand' header in the admin section of the RCS file. This function can only be called before admin_completed(). Parameter: MODE is a string containing the keyword expansion mode. Possible values include 'o' and 'b', amongst others. """ pass def admin_completed(self): """Reports that the initial RCS header has been parsed. This function is called exactly once. """ pass def define_revision(self, revision, timestamp, author, state, branches, next): """Reports metadata about a single revision. This function is called for each revision. It is called later than admin_completed() and earlier than tree_completed(). Parameter: REVISION is a revision number, as a string. This is an actual revision number, not a branch number. TIMESTAMP is the date and time that the revision was created, as an integer number of seconds since the epoch. (I.e. "UNIX time" format). AUTHOR is the author name, as a string. STATE is the state of the revision, as a string. Common values are "Exp" and "dead". BRANCHES is a list of strings, with each string being an actual revision number (not a branch number). For each branch which is based on this revision and has commits, the revision number of the first branch commit is listed here. NEXT is either None or a string representing an actual revision number (not a branch number). When on trunk, NEXT points to what humans might consider to be the 'previous' revision number. For example, 1.3's NEXT is 1.2. However, on a branch, NEXT really does point to what humans would consider to be the 'next' revision number. For example, 1.1.2.1's NEXT would be 1.1.2.2. In other words, NEXT always means "where to find the next deltatext that you need this revision to retrieve". """ pass def tree_completed(self): """Reports that the RCS revision tree has been parsed. This function is called exactly once. This function will be called later than admin_completed(). """ pass def set_description(self, description): """Reports the description from the RCS file. This is set using the "-m" flag to "cvs add". However, many CVS users don't use that option, so this is often empty. This function is called once, after tree_completed(). Parameter: DESCRIPTION is a string containing the description. This may be multi-line. """ pass def set_revision_info(self, revision, log, text): """Reports the log message and contents of a CVS revision. This function is called for each revision. It is called later than set_description(). Parameters: REVISION is a string containing the actual revision number. LOG is a string containing the log message. This may be multi-line. TEXT is the contents of the file in this revision, either as full-text or as a diff. This is usually multi-line, and often quite large and/or binary. """ pass def parse_completed(self): """Reports that parsing an RCS file is complete. This function is called once. After it is called, no more calls will be made via this interface. """ pass # -------------------------------------------------------------------------- # # EXCEPTIONS USED BY RCSPARSE # class RCSParseError(Exception): pass class RCSIllegalCharacter(RCSParseError): pass class RCSExpected(RCSParseError): def __init__(self, got, wanted): RCSParseError.__init__( self, 'Unexpected parsing error in RCS file.\n' 'Expected token: %s, but saw: %s' % (wanted, got) ) class RCSStopParser(Exception): pass # -------------------------------------------------------------------------- # # STANDARD TOKEN STREAM-BASED PARSER # class _Parser: stream_class = None # subclasses need to define this def _read_until_semicolon(self): """Read all tokens up to and including the next semicolon token. Return the tokens (not including the semicolon) as a list.""" tokens = [] while 1: token = self.ts.get() if token == ';': break tokens.append(token) return tokens def _parse_admin_head(self, token): rev = self.ts.get() if rev == ';': # The head revision is not specified. Just drop the semicolon # on the floor. pass else: self.sink.set_head_revision(rev) self.ts.match(';') def _parse_admin_branch(self, token): branch = self.ts.get() if branch != ';': self.sink.set_principal_branch(branch) self.ts.match(';') def _parse_admin_access(self, token): accessors = self._read_until_semicolon() if accessors: self.sink.set_access(accessors) def _parse_admin_symbols(self, token): while 1: tag_name = self.ts.get() if tag_name == ';': break self.ts.match(':') tag_rev = self.ts.get() self.sink.define_tag(tag_name, tag_rev) def _parse_admin_locks(self, token): while 1: locker = self.ts.get() if locker == ';': break self.ts.match(':') rev = self.ts.get() self.sink.set_locker(rev, locker) def _parse_admin_strict(self, token): self.sink.set_locking("strict") self.ts.match(';') def _parse_admin_comment(self, token): self.sink.set_comment(self.ts.get()) self.ts.match(';') def _parse_admin_expand(self, token): expand_mode = self.ts.get() self.sink.set_expansion(expand_mode) self.ts.match(';') admin_token_map = { 'head' : _parse_admin_head, 'branch' : _parse_admin_branch, 'access' : _parse_admin_access, 'symbols' : _parse_admin_symbols, 'locks' : _parse_admin_locks, 'strict' : _parse_admin_strict, 'comment' : _parse_admin_comment, 'expand' : _parse_admin_expand, 'desc' : None, } def parse_rcs_admin(self): while 1: # Read initial token at beginning of line token = self.ts.get() try: f = self.admin_token_map[token] except KeyError: # We're done once we reach the description of the RCS tree if token[0] in string.digits: self.ts.unget(token) return else: # Chew up "newphrase" # warn("Unexpected RCS token: $token\n") while self.ts.get() != ';': pass else: if f is None: self.ts.unget(token) return else: f(self, token) def _parse_rcs_tree_entry(self, revision): # Parse date self.ts.match('date') date = self.ts.get() self.ts.match(';') # Convert date into standard UNIX time format (seconds since epoch) date_fields = string.split(date, '.') # According to rcsfile(5): the year "contains just the last two # digits of the year for years from 1900 through 1999, and all the # digits of years thereafter". if len(date_fields[0]) == 2: date_fields[0] = '19' + date_fields[0] date_fields = map(string.atoi, date_fields) EPOCH = 1970 if date_fields[0] < EPOCH: raise ValueError, 'invalid year for revision %s' % (revision,) try: timestamp = calendar.timegm(tuple(date_fields) + (0, 0, 0,)) except ValueError, e: raise ValueError, 'invalid date for revision %s: %s' % (revision, e,) # Parse author ### NOTE: authors containing whitespace are violations of the ### RCS specification. We are making an allowance here because ### CVSNT is known to produce these sorts of authors. self.ts.match('author') author = ' '.join(self._read_until_semicolon()) # Parse state self.ts.match('state') state = '' while 1: token = self.ts.get() if token == ';': break state = state + token + ' ' state = state[:-1] # toss the trailing space # Parse branches self.ts.match('branches') branches = self._read_until_semicolon() # Parse revision of next delta in chain self.ts.match('next') next = self.ts.get() if next == ';': next = None else: self.ts.match(';') # there are some files with extra tags in them. for example: # owner 640; # group 15; # permissions 644; # hardlinks @configure.in@; # commitid mLiHw3bulRjnTDGr; # this is "newphrase" in RCSFILE(5). we just want to skip over these. while 1: token = self.ts.get() if token == 'desc' or token[0] in string.digits: self.ts.unget(token) break # consume everything up to the semicolon self._read_until_semicolon() self.sink.define_revision(revision, timestamp, author, state, branches, next) def parse_rcs_tree(self): while 1: revision = self.ts.get() # End of RCS tree description ? if revision == 'desc': self.ts.unget(revision) return self._parse_rcs_tree_entry(revision) def parse_rcs_description(self): self.ts.match('desc') self.sink.set_description(self.ts.get()) def parse_rcs_deltatext(self): while 1: revision = self.ts.get() if revision is None: # EOF break text, sym2, log, sym1 = self.ts.mget(4) if sym1 != 'log': print `text[:100], sym2[:100], log[:100], sym1[:100]` raise RCSExpected(sym1, 'log') if sym2 != 'text': raise RCSExpected(sym2, 'text') ### need to add code to chew up "newphrase" self.sink.set_revision_info(revision, log, text) def parse(self, file, sink): """Parse an RCS file. Parameters: FILE is the file object to parse. (I.e. an object of the built-in Python type "file", usually created using Python's built-in "open()" function). SINK is an instance of (some subclass of) Sink. It's methods will be called as the file is parsed; see the definition of Sink for the details. """ self.ts = self.stream_class(file) self.sink = sink self.parse_rcs_admin() # let sink know when the admin section has been completed self.sink.admin_completed() self.parse_rcs_tree() # many sinks want to know when the tree has been completed so they can # do some work to prep for the arrival of the deltatext self.sink.tree_completed() self.parse_rcs_description() self.parse_rcs_deltatext() # easiest for us to tell the sink it is done, rather than worry about # higher level software doing it. self.sink.parse_completed() self.ts = self.sink = None # -------------------------------------------------------------------------- cvs2svn-2.5.0/cvs2svn_rcsparse/parse_rcs_file.py0000775000175100017510000000430612203665123023042 0ustar mhaggermhagger00000000000000#! /usr/bin/python # (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2006-2007 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """Parse an RCS file, showing the rcsparse callbacks that are called. This program is useful to see whether an RCS file has a problem (in the sense of not being parseable by rcsparse) and also to illuminate the correspondence between RCS file contents and rcsparse callbacks. The output of this program can also be considered to be a kind of 'canonical' format for RCS files, at least in so far as rcsparse returns all relevant information in the file and provided that the order of callbacks is always the same.""" import sys import os class Logger: def __init__(self, f, name): self.f = f self.name = name def __call__(self, *args): self.f.write( '%s(%s)\n' % (self.name, ', '.join(['%r' % arg for arg in args]),) ) class LoggingSink: def __init__(self, f): self.f = f def __getattr__(self, name): return Logger(self.f, name) if __name__ == '__main__': # Since there is nontrivial logic in __init__.py, we have to import # parse() via that file. First make sure that the directory # containing this script is in the path: sys.path.insert(0, os.path.dirname(sys.argv[0])) from __init__ import parse if sys.argv[1:]: for path in sys.argv[1:]: if os.path.isfile(path) and path.endswith(',v'): parse( open(path, 'rb'), LoggingSink(sys.stdout) ) else: sys.stderr.write('%r is being ignored.\n' % path) else: parse(sys.stdin, LoggingSink(sys.stdout)) cvs2svn-2.5.0/cvs2svn_rcsparse/texttools.py0000664000175100017510000002437013206445076022136 0ustar mhaggermhagger00000000000000# -*-python-*- # # Copyright (C) 1999-2014 The ViewCVS Group. All Rights Reserved. # # By using this file, you agree to the terms and conditions set forth in # the LICENSE.html file which can be found at the top level of the ViewVC # distribution or at http://viewvc.org/license-1.html. # # For more information, visit http://viewvc.org/ # # ----------------------------------------------------------------------- import string # note: this will raise an ImportError if it isn't available. the rcsparse # package will recognize this and switch over to the default parser. from mx import TextTools import common # for convenience _tt = TextTools _idchar_list = map(chr, range(33, 127)) + map(chr, range(160, 256)) _idchar_list.remove('$') _idchar_list.remove(',') #_idchar_list.remove('.') # leave as part of 'num' symbol _idchar_list.remove(':') _idchar_list.remove(';') _idchar_list.remove('@') _idchar = string.join(_idchar_list, '') _idchar_set = _tt.set(_idchar) _onechar_token_set = _tt.set(':;') _not_at_set = _tt.invset('@') _T_TOKEN = 30 _T_STRING_START = 40 _T_STRING_SPAN = 60 _T_STRING_END = 70 _E_COMPLETE = 100 # ended on a complete token _E_TOKEN = 110 # ended mid-token _E_STRING_SPAN = 130 # ended within a string _E_STRING_END = 140 # ended with string-end ('@') (could be mid-@@) _SUCCESS = +100 _EOF = 'EOF' _CONTINUE = 'CONTINUE' _UNUSED = 'UNUSED' # continuation of a token over a chunk boundary _c_token_table = ( (_T_TOKEN, _tt.AllInSet, _idchar_set), ) class _mxTokenStream: # the algorithm is about the same speed for any CHUNK_SIZE chosen. # grab a good-sized chunk, but not too large to overwhelm memory. # note: we use a multiple of a standard block size CHUNK_SIZE = 192 * 512 # about 100k # CHUNK_SIZE = 5 # for debugging, make the function grind... def __init__(self, file): self.rcsfile = file self.tokens = [ ] self.partial = None self.string_end = None def _parse_chunk(self, buf, start=0): "Get the next token from the RCS file." buflen = len(buf) assert start < buflen # construct a tag table which refers to the buffer we need to parse. table = ( #1: ignore whitespace. with or without whitespace, move to the next rule. (None, _tt.AllInSet, _tt.whitespace_set, +1), #2 (_E_COMPLETE, _tt.EOF + _tt.AppendTagobj, _tt.Here, +1, _SUCCESS), #3: accumulate token text and exit, or move to the next rule. (_UNUSED, _tt.AllInSet + _tt.AppendMatch, _idchar_set, +2), #4 (_E_TOKEN, _tt.EOF + _tt.AppendTagobj, _tt.Here, -3, _SUCCESS), #5: single character tokens exit immediately, or move to the next rule (_UNUSED, _tt.IsInSet + _tt.AppendMatch, _onechar_token_set, +2), #6 (_E_COMPLETE, _tt.EOF + _tt.AppendTagobj, _tt.Here, -5, _SUCCESS), #7: if this isn't an '@' symbol, then we have a syntax error (go to a # negative index to indicate that condition). otherwise, suck it up # and move to the next rule. (_T_STRING_START, _tt.Is + _tt.AppendTagobj, '@'), #8 (None, _tt.Is, '@', +4, +1), #9 (buf, _tt.Is, '@', +1, -1), #10 (_T_STRING_END, _tt.Skip + _tt.AppendTagobj, 0, 0, +1), #11 (_E_STRING_END, _tt.EOF + _tt.AppendTagobj, _tt.Here, -10, _SUCCESS), #12 (_E_STRING_SPAN, _tt.EOF + _tt.AppendTagobj, _tt.Here, +1, _SUCCESS), #13: suck up everything that isn't an AT. go to next rule to look for EOF (buf, _tt.AllInSet, _not_at_set, 0, +1), #14: go back to look for double AT if we aren't at the end of the string (_E_STRING_SPAN, _tt.EOF + _tt.AppendTagobj, _tt.Here, -6, _SUCCESS), ) # Fast, texttools may be, but it's somewhat lacking in clarity. # Here's an attempt to document the logic encoded in the table above: # # Flowchart: # _____ # / /\ # 1 -> 2 -> 3 -> 5 -> 7 -> 8 -> 9 -> 10 -> 11 # | \/ \/ \/ /\ \/ # \ 4 6 12 14 / # \_______/_____/ \ / / # \ 13 / # \__________________________________________/ # # #1: Skip over any whitespace. # #2: If now EOF, exit with code _E_COMPLETE. # #3: If we have a series of characters in _idchar_set, then: # #4: Output them as a token, and go back to #1. # #5: If we have a character in _onechar_token_set, then: # #6: Output it as a token, and go back to #1. # #7: If we do not have an '@', then error. # If we do, then log a _T_STRING_START and continue. # #8: If we have another '@', continue on to #9. Otherwise: # #12: If now EOF, exit with code _E_STRING_SPAN. # #13: Record the slice up to the next '@' (or EOF). # #14: If now EOF, exit with code _E_STRING_SPAN. # Otherwise, go back to #8. # #9: If we have another '@', then we've just seen an escaped # (by doubling) '@' within an @-string. Record a slice including # just one '@' character, and jump back to #8. # Otherwise, we've *either* seen the terminating '@' of an @-string, # *or* we've seen one half of an escaped @@ sequence that just # happened to be split over a chunk boundary - in either case, # we continue on to #10. # #10: Log a _T_STRING_END. # #11: If now EOF, exit with _E_STRING_END. Otherwise, go back to #1. success, taglist, idx = _tt.tag(buf, table, start) if not success: ### need a better way to report this error raise common.RCSIllegalCharacter() assert idx == buflen # pop off the last item last_which = taglist.pop() i = 0 tlen = len(taglist) while i < tlen: if taglist[i] == _T_STRING_START: j = i + 1 while j < tlen: if taglist[j] == _T_STRING_END: s = _tt.join(taglist, '', i+1, j) del taglist[i:j] tlen = len(taglist) taglist[i] = s break j = j + 1 else: assert last_which == _E_STRING_SPAN s = _tt.join(taglist, '', i+1) del taglist[i:] self.partial = (_T_STRING_SPAN, [ s ]) break i = i + 1 # figure out whether we have a partial last-token if last_which == _E_TOKEN: self.partial = (_T_TOKEN, [ taglist.pop() ]) elif last_which == _E_COMPLETE: pass elif last_which == _E_STRING_SPAN: assert self.partial else: assert last_which == _E_STRING_END self.partial = (_T_STRING_END, [ taglist.pop() ]) taglist.reverse() taglist.extend(self.tokens) self.tokens = taglist def _set_end(self, taglist, text, l, r, subtags): self.string_end = l def _handle_partial(self, buf): which, chunks = self.partial if which == _T_TOKEN: success, taglist, idx = _tt.tag(buf, _c_token_table) if not success: # The start of this buffer was not a token. So the end of the # prior buffer was a complete token. self.tokens.insert(0, string.join(chunks, '')) else: assert len(taglist) == 1 and taglist[0][0] == _T_TOKEN \ and taglist[0][1] == 0 and taglist[0][2] == idx if idx == len(buf): # # The whole buffer was one huge token, so we may have a # partial token again. # # Note: this modifies the list of chunks in self.partial # chunks.append(buf) # consumed the whole buffer return len(buf) # got the rest of the token. chunks.append(buf[:idx]) self.tokens.insert(0, string.join(chunks, '')) # no more partial token self.partial = None return idx if which == _T_STRING_END: if buf[0] != '@': self.tokens.insert(0, string.join(chunks, '')) return 0 chunks.append('@') start = 1 else: start = 0 self.string_end = None string_table = ( (None, _tt.Is, '@', +3, +1), (_UNUSED, _tt.Is + _tt.AppendMatch, '@', +1, -1), (self._set_end, _tt.Skip + _tt.CallTag, 0, 0, _SUCCESS), (None, _tt.EOF, _tt.Here, +1, _SUCCESS), # suck up everything that isn't an AT. move to next rule to look # for EOF (_UNUSED, _tt.AllInSet + _tt.AppendMatch, _not_at_set, 0, +1), # go back to look for double AT if we aren't at the end of the string (None, _tt.EOF, _tt.Here, -5, _SUCCESS), ) success, unused, idx = _tt.tag(buf, string_table, start, len(buf), chunks) # must have matched at least one item assert success if self.string_end is None: assert idx == len(buf) self.partial = (_T_STRING_SPAN, chunks) elif self.string_end < len(buf): self.partial = None self.tokens.insert(0, string.join(chunks, '')) else: self.partial = (_T_STRING_END, chunks) return idx def _parse_more(self): buf = self.rcsfile.read(self.CHUNK_SIZE) if not buf: return _EOF if self.partial: idx = self._handle_partial(buf) if idx is None: return _CONTINUE if idx < len(buf): self._parse_chunk(buf, idx) else: self._parse_chunk(buf) return _CONTINUE def get(self): try: return self.tokens.pop() except IndexError: pass while not self.tokens: action = self._parse_more() if action == _EOF: return None return self.tokens.pop() # _get = get # def get(self): token = self._get() print 'T:', `token` return token def match(self, match): if self.tokens: token = self.tokens.pop() else: token = self.get() if token != match: raise common.RCSExpected(token, match) def unget(self, token): self.tokens.append(token) def mget(self, count): "Return multiple tokens. 'next' is at the end." while len(self.tokens) < count: action = self._parse_more() if action == _EOF: ### fix this raise RuntimeError, 'EOF hit while expecting tokens' result = self.tokens[-count:] del self.tokens[-count:] return result class Parser(common._Parser): stream_class = _mxTokenStream cvs2svn-2.5.0/cvs2svn_rcsparse/debug.py0000664000175100017510000000631113206445076021152 0ustar mhaggermhagger00000000000000# -*-python-*- # # Copyright (C) 1999-2014 The ViewCVS Group. All Rights Reserved. # # By using this file, you agree to the terms and conditions set forth in # the LICENSE.html file which can be found at the top level of the ViewVC # distribution or at http://viewvc.org/license-1.html. # # For more information, visit http://viewvc.org/ # # ----------------------------------------------------------------------- """debug.py: various debugging tools for the rcsparse package.""" import time from __init__ import parse import common class DebugSink(common.Sink): def set_head_revision(self, revision): print 'head:', revision def set_principal_branch(self, branch_name): print 'branch:', branch_name def define_tag(self, name, revision): print 'tag:', name, '=', revision def set_comment(self, comment): print 'comment:', comment def set_description(self, description): print 'description:', description def define_revision(self, revision, timestamp, author, state, branches, next): print 'revision:', revision print ' timestamp:', timestamp print ' author:', author print ' state:', state print ' branches:', branches print ' next:', next def set_revision_info(self, revision, log, text): print 'revision:', revision print ' log:', log print ' text:', text[:100], '...' class DumpSink(common.Sink): """Dump all the parse information directly to stdout. The output is relatively unformatted and untagged. It is intended as a raw dump of the data in the RCS file. A copy can be saved, then changes made to the parsing engine, then a comparison of the new output against the old output. """ def __init__(self): global sha import sha def set_head_revision(self, revision): print revision def set_principal_branch(self, branch_name): print branch_name def define_tag(self, name, revision): print name, revision def set_comment(self, comment): print comment def set_description(self, description): print description def define_revision(self, revision, timestamp, author, state, branches, next): print revision, timestamp, author, state, branches, next def set_revision_info(self, revision, log, text): print revision, sha.new(log).hexdigest(), sha.new(text).hexdigest() def tree_completed(self): print 'tree_completed' def parse_completed(self): print 'parse_completed' def dump_file(fname): parse(open(fname, 'rb'), DumpSink()) def time_file(fname): f = open(fname, 'rb') s = common.Sink() t = time.time() parse(f, s) t = time.time() - t print t def _usage(): print 'This is normally a module for importing, but it has a couple' print 'features for testing as an executable script.' print 'USAGE: %s COMMAND filename,v' % sys.argv[0] print ' where COMMAND is one of:' print ' dump: filename is "dumped" to stdout' print ' time: filename is parsed with the time written to stdout' sys.exit(1) if __name__ == '__main__': import sys if len(sys.argv) != 3: _usage() if sys.argv[1] == 'dump': dump_file(sys.argv[2]) elif sys.argv[1] == 'time': time_file(sys.argv[2]) else: _usage() cvs2svn-2.5.0/cvs2svn_rcsparse/__init__.py0000664000175100017510000000030112203665123021605 0ustar mhaggermhagger00000000000000# This file causes Python to treat its containing directory it as a # package. Please note that the contents of this file differ from # those of the __init__.py file distributed with ViewVC. cvs2svn-2.5.0/svntest/0000775000175100017510000000000013206450463015705 5ustar mhaggermhagger00000000000000cvs2svn-2.5.0/svntest/tree.py0000664000175100017510000007254212203665123017225 0ustar mhaggermhagger00000000000000# # tree.py: tools for comparing directory trees # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import re import os import sys if sys.version_info[0] >= 3: # Python >=3.0 from io import StringIO else: # Python <3.0 from cStringIO import StringIO from xml.dom.minidom import parseString import base64 import logging import svntest logger = logging.getLogger() # Tree Exceptions. # All tree exceptions should inherit from SVNTreeError class SVNTreeError(svntest.Failure): "Exception raised if you screw up in the tree module." pass class SVNTreeUnequal(SVNTreeError): "Exception raised if two trees are unequal." pass class SVNTypeMismatch(SVNTreeError): "Exception raised if one node is file and other is dir" pass #======================================================================== # ===> Overview of our Datastructures <=== # The general idea here is that many, many things can be represented by # a tree structure: # - a working copy's structure and contents # - the output of 'svn status' # - the output of 'svn checkout/update' # - the output of 'svn commit' # The idea is that a test function creates a "expected" tree of some # kind, and is then able to compare it to an "actual" tree that comes # from running the Subversion client. This is what makes a test # automated; if an actual and expected tree match exactly, then the test # has passed. (See compare_trees() below.) # The SVNTreeNode class is the fundamental data type used to build tree # structures. The class contains a method for "dropping" a new node # into an ever-growing tree structure. (See also create_from_path()). # We have four parsers in this file for the four use cases listed above: # each parser examines some kind of input and returns a tree of # SVNTreeNode objects. (See build_tree_from_checkout(), # build_tree_from_commit(), build_tree_from_status(), and # build_tree_from_wc()). These trees are the "actual" trees that result # from running the Subversion client. # Also necessary, of course, is a convenient way for a test to create an # "expected" tree. The test *could* manually construct and link a bunch # of SVNTreeNodes, certainly. But instead, all the tests are using the # build_generic_tree() routine instead. # build_generic_tree() takes a specially-formatted list of lists as # input, and returns a tree of SVNTreeNodes. The list of lists has this # structure: # [ ['/full/path/to/item', 'text contents', {prop-hash}, {att-hash}], # [...], # [...], # ... ] # You can see that each item in the list essentially defines an # SVNTreeNode. build_generic_tree() instantiates a SVNTreeNode for each # item, and then drops it into a tree by parsing each item's full path. # So a typical test routine spends most of its time preparing lists of # this format and sending them to build_generic_tree(), rather than # building the "expected" trees directly. # ### Note: in the future, we'd like to remove this extra layer of # ### abstraction. We'd like the SVNTreeNode class to be more # ### directly programmer-friendly, providing a number of accessor # ### routines, so that tests can construct trees directly. # The first three fields of each list-item are self-explanatory. It's # the fourth field, the "attribute" hash, that needs some explanation. # The att-hash is used to place extra information about the node itself, # depending on the parsing context: # - in the 'svn co/up' use-case, each line of output starts with two # characters from the set of (A, D, G, U, C, _) or 'Restored'. The # status code is stored in a attribute named 'status'. In the case # of a restored file, the word 'Restored' is stored in an attribute # named 'verb'. # - in the 'svn ci/im' use-case, each line of output starts with one # of the words (Adding, Deleting, Sending). This verb is stored in # an attribute named 'verb'. # - in the 'svn status' use-case (which is always run with the -v # (--verbose) flag), each line of output contains a working revision # number and a two-letter status code similar to the 'svn co/up' # case. This information is stored in attributes named 'wc_rev' # and 'status'. The repository revision is also printed, but it # is ignored. # - in the working-copy use-case, the att-hash is ignored. # Finally, one last explanation: the file 'actions.py' contain a number # of helper routines named 'run_and_verify_FOO'. These routines take # one or more "expected" trees as input, then run some svn subcommand, # then push the output through an appropriate parser to derive an # "actual" tree. Then it runs compare_trees() and raises an exception # on failure. This is why most tests typically end with a call to # run_and_verify_FOO(). #======================================================================== # A node in a tree. # # If CHILDREN is None, then the node is a file. Otherwise, CHILDREN # is a list of the nodes making up that directory's children. # # NAME is simply the name of the file or directory. CONTENTS is a # string that contains the file's contents (if a file), PROPS are # properties attached to files or dirs, and ATTS is a dictionary of # other metadata attached to the node. class SVNTreeNode: def __init__(self, name, children=None, contents=None, props={}, atts={}): self.name = name self.children = children self.contents = contents self.props = props self.atts = atts self.path = name # TODO: Check to make sure contents and children are mutually exclusive def add_child(self, newchild): child_already_exists = 0 if self.children is None: # if you're a file, self.children = [] # become an empty dir. else: for a in self.children: if a.name == newchild.name: child_already_exists = 1 break if child_already_exists: if newchild.children is None: # this is the 'end' of the chain, so copy any content here. a.contents = newchild.contents a.props = newchild.props a.atts = newchild.atts a.path = os.path.join(self.path, newchild.name) else: # try to add dangling children to your matching node for i in newchild.children: a.add_child(i) else: self.children.append(newchild) newchild.path = os.path.join(self.path, newchild.name) def pprint(self, stream = sys.stdout): "Pretty-print the meta data for this node to STREAM." stream.write(" * Node name: %s\n" % self.name) stream.write(" Path: %s\n" % self.path) mime_type = self.props.get("svn:mime-type") if not mime_type or mime_type.startswith("text/"): if self.children is not None: stream.write(" Contents: N/A (node is a directory)\n") else: stream.write(" Contents: %s\n" % self.contents) else: stream.write(" Contents: %d bytes (binary)\n" % len(self.contents)) stream.write(" Properties: %s\n" % self.props) stream.write(" Attributes: %s\n" % self.atts) ### FIXME: I'd like to be able to tell the difference between ### self.children is None (file) and self.children == [] (empty ### directory), but it seems that most places that construct ### SVNTreeNode objects don't even try to do that. --xbc ### ### See issue #1611 about this problem. -kfogel if self.children is not None: stream.write(" Children: %s\n" % len(self.children)) else: stream.write(" Children: None (node is probably a file)\n") stream.flush() def get_printable_path(self): """Remove some occurrences of root_node_name = "__SVN_ROOT_NODE", it is in the way when matching for a subtree, and looks bad.""" path = self.path if path.startswith(root_node_name + os.sep): path = path[len(root_node_name + os.sep):] return path def print_script(self, stream = sys.stdout, subtree = "", prepend="\n ", drop_empties = True): """Python-script-print the meta data for this node to STREAM. Print only those nodes whose path string starts with the string SUBTREE, and print only the part of the path string that remains after SUBTREE. PREPEND is a string prepended to each node printout (does the line feed if desired, don't include a comma in PREPEND). If DROP_EMPTIES is true, all dir nodes that have no data set in them (no props, no atts) and that have children (so they are included implicitly anyway) are not printed. Return 1 if this node was printed, 0 otherwise (added up by dump_tree_script())""" # figure out if this node would be obsolete to print. if drop_empties and len(self.props) < 1 and len(self.atts) < 1 and \ self.contents is None and self.children is not None: return 0 path = self.get_printable_path() # remove the subtree path, skip this node if necessary. if path.startswith(subtree): path = path[len(subtree):] else: return 0 if path.startswith(os.sep): path = path[1:] line = prepend line += "%-20s: Item(" % ("'%s'" % path.replace(os.sep, '/')) comma = False mime_type = self.props.get("svn:mime-type") if not mime_type or mime_type.startswith("text/"): if self.contents is not None: # Escape some characters for nicer script and readability. # (This is error output. I guess speed is no consideration here.) line += "contents=\"%s\"" % (self.contents .replace('\n','\\n') .replace('"','\\"') .replace('\r','\\r') .replace('\t','\\t')) comma = True else: line += 'content is binary data' comma = True if self.props: if comma: line += ", " line += "props={" comma = False for name in self.props: if comma: line += ", " line += "'%s':'%s'" % (name, self.props[name]) comma = True line += "}" comma = True for name in self.atts: if comma: line += ", " line += "%s='%s'" % (name, self.atts[name]) comma = True line += ")," stream.write("%s" % line) stream.flush() return 1 def __str__(self): s = StringIO() self.pprint(s) return s.getvalue() def __cmp__(self, other): """Define a simple ordering of two nodes without regard to their full path (i.e. position in the tree). This can be used for sorting the children within a directory.""" return cmp(self.name, other.name) def as_state(self, prefix=None): """Return an svntest.wc.State instance that is equivalent to this tree.""" root = self if self.path == root_node_name: assert prefix is None wc_dir = '' while True: if root is not self: # don't prepend ROOT_NODE_NAME wc_dir = os.path.join(wc_dir, root.name) if root.contents or root.props or root.atts: break if not root.children or len(root.children) != 1: break root = root.children[0] state = svntest.wc.State(wc_dir, { }) if root.contents or root.props or root.atts: state.add({'': root.as_item()}) prefix = wc_dir else: assert prefix is not None path = self.path if path.startswith(root_node_name): path = path[len(root_node_name)+1:] # prefix should only be set on a recursion, which means a child, # which means this path better not be the same as the prefix. assert path != prefix, 'not processing a child of the root' l = len(prefix) if l > 0: assert path[:l] == prefix, \ '"%s" is not a prefix of "%s"' % (prefix, path) # return the portion after the separator path = path[l+1:].replace(os.sep, '/') state = svntest.wc.State('', { path: self.as_item() }) if root.children: for child in root.children: state.add_state('', child.as_state(prefix)) return state def as_item(self): return svntest.wc.StateItem(self.contents, self.props, self.atts.get('status'), self.atts.get('verb'), self.atts.get('wc_rev'), self.atts.get('locked'), self.atts.get('copied'), self.atts.get('switched'), self.atts.get('writelocked'), self.atts.get('treeconflict')) def recurse(self, function): results = [] results += [ function(self) ] if self.children: for child in self.children: results += child.recurse(function) return results def find_node(self, path): if self.get_printable_path() == path: return self if self.children: for child in self.children: result = child.find_node(path) if result: return result return None # reserved name of the root of the tree root_node_name = "__SVN_ROOT_NODE" # helper func def add_elements_as_path(top_node, element_list): """Add the elements in ELEMENT_LIST as if they were a single path below TOP_NODE.""" # The idea of this function is to take a list like so: # ['A', 'B', 'C'] and a top node, say 'Z', and generate a tree # like this: # # Z -> A -> B -> C # # where 1 -> 2 means 2 is a child of 1. # prev_node = top_node for i in element_list: new_node = SVNTreeNode(i, None) prev_node.add_child(new_node) prev_node = new_node # Helper for compare_trees def compare_file_nodes(a, b): """Compare two nodes, A (actual) and B (expected). Compare their names, contents, properties and attributes, ignoring children. Return 0 if the same, 1 otherwise.""" if a.name != b.name: return 1 if a.contents != b.contents: return 1 if a.props != b.props: return 1 if a.atts == b.atts: # No fixes necessary return 0 # Fix a pre-WC-NG assumptions in our testsuite if (b.atts == {'status': 'A ', 'wc_rev': '0'}) \ and (a.atts == {'status': 'A ', 'wc_rev': '-'}): return 0 return 1 # Helper for compare_trees def compare_dir_nodes(a, b): """Compare two nodes, A (actual) and B (expected). Compare their names, properties and attributes, ignoring children. Return 0 if the same, 1 otherwise.""" if a.name != b.name: return 1 if (a.props != b.props): return 1 if (a.atts == b.atts): # No fixes necessary return 0 # Fix a pre-WC-NG assumptions in our testsuite if (b.atts == {'status': 'A ', 'wc_rev': '0'}) \ and (a.atts == {'status': 'A ', 'wc_rev': '-'}): return 0 return 1 # Internal utility used by most build_tree_from_foo() routines. # # (Take the output and .add_child() it to a root node.) def create_from_path(path, contents=None, props={}, atts={}): """Create and return a linked list of treenodes, given a PATH representing a single entry into that tree. CONTENTS and PROPS are optional arguments that will be deposited in the tail node.""" # get a list of all the names in the path # each of these will be a child of the former if os.sep != "/": path = path.replace(os.sep, "/") elements = path.split("/") if len(elements) == 0: ### we should raise a less generic error here. which? raise SVNTreeError root_node = None # if this is Windows: if the path contains a drive name (X:), make it # the root node. if os.name == 'nt': m = re.match("([a-zA-Z]:)(.+)", elements[0]) if m: root_node = SVNTreeNode(m.group(1), None) elements[0] = m.group(2) add_elements_as_path(root_node, elements[0:]) if not root_node: root_node = SVNTreeNode(elements[0], None) add_elements_as_path(root_node, elements[1:]) # deposit contents in the very last node. node = root_node while True: if node.children is None: node.contents = contents node.props = props node.atts = atts break node = node.children[0] return root_node eol_re = re.compile(r'(\r\n|\r)') # helper for build_tree_from_wc() def get_props(paths): """Return a hash of hashes of props for PATHS, using the svn client. Convert each embedded end-of-line to a single LF character.""" # It's not kosher to look inside .svn/ and try to read the internal # property storage format. Instead, we use 'svn proplist'. After # all, this is the only way the user can retrieve them, so we're # respecting the black-box paradigm. files = {} exit_code, output, errput = svntest.main.run_svn(1, "proplist", "--verbose", "--xml", *paths) output = (line for line in output if not line.startswith('DBG:')) dom = parseString(''.join(output)) target_nodes = dom.getElementsByTagName('target') for target_node in target_nodes: filename = target_node.attributes['path'].nodeValue file_props = {} for property_node in target_node.getElementsByTagName('property'): name = property_node.attributes['name'].nodeValue if property_node.hasChildNodes(): text_node = property_node.firstChild value = text_node.nodeValue else: value = '' try: encoding = property_node.attributes['encoding'].nodeValue if encoding == 'base64': value = base64.b64decode(value) else: raise Exception("Unknown encoding '%s' for file '%s' property '%s'" % (encoding, filename, name,)) except KeyError: pass # If the property value contained a CR, or if under Windows an # "svn:*" property contains a newline, then the XML output # contains a CR character XML-encoded as ' '. The XML # parser converts it back into a CR character. So again convert # all end-of-line variants into a single LF: value = eol_re.sub('\n', value) file_props[name] = value files[filename] = file_props dom.unlink() return files ### ridiculous function. callers should do this one line themselves. def get_text(path): "Return a string with the textual contents of a file at PATH." # sanity check if not os.path.isfile(path): return None return open(path, 'r').read() def get_child(node, name): """If SVNTreeNode NODE contains a child named NAME, return child; else, return None. If SVNTreeNode is not a directory, exit completely.""" if node.children == None: logger.error("Foolish call to get_child.") sys.exit(1) for n in node.children: if name == n.name: return n return None # Helper for compare_trees def default_singleton_handler(node, description): """Print SVNTreeNode NODE's name, describing it with the string DESCRIPTION, then raise SVNTreeUnequal.""" logger.warn("Couldn't find node '%s' in %s tree" % (node.name, description)) logger.warn(str(node)) raise SVNTreeUnequal # A test helper function implementing the singleton_handler_a API. def detect_conflict_files(node, extra_files): """NODE has been discovered, an extra file on disk. Verify that it matches one of the regular expressions in the EXTRA_FILES list. If it matches, remove the match from the list. If it doesn't match, raise an exception.""" for pattern in extra_files: mo = re.match(pattern, node.name) if mo: extra_files.pop(extra_files.index(pattern)) # delete pattern from list break else: msg = "Encountered unexpected disk path '" + node.name + "'" logger.warn(msg) logger.warn(str(node)) raise SVNTreeUnequal(msg) ########################################################################### ########################################################################### # EXPORTED ROUTINES ARE BELOW # Main tree comparison routine! def compare_trees(label, a, b, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None): """Compare SVNTreeNodes A (actual) and B (expected), expressing differences using FUNC_A and FUNC_B. FUNC_A and FUNC_B are functions of two arguments (a SVNTreeNode and a context baton), and may raise exception SVNTreeUnequal, in which case they use the string LABEL to describe the error (their return value is ignored). LABEL is typically "output", "disk", "status", or some other word that labels the trees being compared. If A and B are both files, then return if their contents, properties, and names are all the same; else raise a SVNTreeUnequal. If A is a file and B is a directory, raise a SVNTreeUnequal; same vice-versa. If both are directories, then for each entry that exists in both, call compare_trees on the two entries; otherwise, if the entry exists only in A, invoke FUNC_A on it, and likewise for B with FUNC_B.""" def display_nodes(a, b): 'Display two nodes, expected and actual.' o = StringIO() o.write("=============================================================\n") o.write("Expected '%s' and actual '%s' in %s tree are different!\n" % (b.name, a.name, label)) o.write("=============================================================\n") o.write("EXPECTED NODE TO BE:\n") o.write("=============================================================\n") b.pprint(o) o.write("=============================================================\n") o.write("ACTUAL NODE FOUND:\n") o.write("=============================================================\n") a.pprint(o) logger.warn(o.getvalue()) o.close() # Setup singleton handlers if singleton_handler_a is None: singleton_handler_a = default_singleton_handler a_baton = "expected " + label if singleton_handler_b is None: singleton_handler_b = default_singleton_handler b_baton = "actual " + label try: # A and B are both files. if (a.children is None) and (b.children is None): if compare_file_nodes(a, b): display_nodes(a, b) raise SVNTreeUnequal # One is a file, one is a directory. elif (((a.children is None) and (b.children is not None)) or ((a.children is not None) and (b.children is None))): display_nodes(a, b) raise SVNTypeMismatch # They're both directories. else: if compare_dir_nodes(a, b): display_nodes(a, b) raise SVNTreeUnequal accounted_for = [] # For each child of A, check and see if it's in B. If so, run # compare_trees on the two children and add b's child to # accounted_for. If not, run FUNC_A on the child. Next, for each # child of B, check and see if it's in accounted_for. If it is, # do nothing. If not, run FUNC_B on it. for a_child in a.children: b_child = get_child(b, a_child.name) if b_child: accounted_for.append(b_child) compare_trees(label, a_child, b_child, singleton_handler_a, a_baton, singleton_handler_b, b_baton) else: singleton_handler_a(a_child, a_baton) for b_child in b.children: if b_child not in accounted_for: singleton_handler_b(b_child, b_baton) except SVNTypeMismatch: logger.warn('Unequal Types: one Node is a file, the other is a directory') raise SVNTreeUnequal except IndexError: logger.warn("Error: unequal number of children") raise SVNTreeUnequal except SVNTreeUnequal: if a.name != root_node_name: logger.warn("Unequal at node %s" % a.name) raise # Visually show a tree's structure def _dump_tree(n,indent="",stream=sys.stdout): """Print out a nice representation of the structure of the tree in the SVNTreeNode N. Prefix each line with the string INDENT.""" # Code partially stolen from Dave Beazley tmp_children = sorted(n.children or []) if n.name == root_node_name: stream.write("%s%s\n" % (indent, "ROOT")) else: stream.write("%s%s\n" % (indent, n.name)) indent = indent.replace("-", " ") indent = indent.replace("+", " ") for i in range(len(tmp_children)): c = tmp_children[i] if i == len(tmp_children)-1: _dump_tree(c,indent + " +-- ",stream) else: _dump_tree(c,indent + " |-- ",stream) def dump_tree(n): output = StringIO() _dump_tree(n,stream=output) logger.warn(output.getvalue()) output.close() def dump_tree_script__crawler(n, subtree="", stream=sys.stdout): "Helper for dump_tree_script. See that comment." count = 0 # skip printing the root node. if n.name != root_node_name: count += n.print_script(stream, subtree) for child in n.children or []: count += dump_tree_script__crawler(child, subtree, stream) return count def dump_tree_script(n, subtree="", stream=sys.stdout, wc_varname='wc_dir'): """Print out a python script representation of the structure of the tree in the SVNTreeNode N. Print only those nodes whose path string starts with the string SUBTREE, and print only the part of the path string that remains after SUBTREE. The result is printed to STREAM. The WC_VARNAME is inserted in the svntest.wc.State(wc_dir,{}) call that is printed out (this is used by factory.py).""" stream.write("svntest.wc.State(" + wc_varname + ", {") count = dump_tree_script__crawler(n, subtree, stream) if count > 0: stream.write('\n') stream.write("})") ################################################################### ################################################################### # PARSERS that return trees made of SVNTreeNodes.... ################################################################### # Build an "expected" static tree from a list of lists # Create a list of lists, of the form: # # [ [path, contents, props, atts], ... ] # # and run it through this parser. PATH is a string, a path to the # object. CONTENTS is either a string or None, and PROPS and ATTS are # populated dictionaries or {}. Each CONTENTS/PROPS/ATTS will be # attached to the basename-node of the associated PATH. def build_generic_tree(nodelist): "Given a list of lists of a specific format, return a tree." root = SVNTreeNode(root_node_name) for list in nodelist: new_branch = create_from_path(list[0], list[1], list[2], list[3]) root.add_child(new_branch) return root #################################################################### # Build trees from different kinds of subcommand output. # Parse co/up output into a tree. # # Tree nodes will contain no contents, a 'status' att, and a # 'treeconflict' att. def build_tree_from_checkout(lines, include_skipped=True): "Return a tree derived by parsing the output LINES from 'co' or 'up'." return svntest.wc.State.from_checkout(lines, include_skipped).old_tree() # Parse ci/im output into a tree. # # Tree nodes will contain no contents, and only one 'verb' att. def build_tree_from_commit(lines): "Return a tree derived by parsing the output LINES from 'ci' or 'im'." return svntest.wc.State.from_commit(lines).old_tree() # Parse status output into a tree. # # Tree nodes will contain no contents, and these atts: # # 'status', 'wc_rev', # ... and possibly 'locked', 'copied', 'switched', # 'writelocked' and 'treeconflict', # IFF columns non-empty. # def build_tree_from_status(lines): "Return a tree derived by parsing the output LINES from 'st -vuq'." return svntest.wc.State.from_status(lines).old_tree() # Parse merge "skipped" output def build_tree_from_skipped(lines): return svntest.wc.State.from_skipped(lines).old_tree() def build_tree_from_diff_summarize(lines): "Build a tree from output of diff --summarize" return svntest.wc.State.from_summarize(lines).old_tree() #################################################################### # Build trees by looking at the working copy # The reason the 'load_props' flag is off by default is because it # creates a drastic slowdown -- we spawn a new 'svn proplist' # process for every file and dir in the working copy! def build_tree_from_wc(wc_path, load_props=0, ignore_svn=1): """Takes WC_PATH as the path to a working copy. Walks the tree below that path, and creates the tree based on the actual found files. If IGNORE_SVN is true, then exclude SVN admin dirs from the tree. If LOAD_PROPS is true, the props will be added to the tree.""" return svntest.wc.State.from_wc(wc_path, load_props, ignore_svn).old_tree() cvs2svn-2.5.0/svntest/wc.py0000664000175100017510000007411712203665123016677 0ustar mhaggermhagger00000000000000# # wc.py: functions for interacting with a Subversion working copy # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import os import sys import re import urllib import logging import pprint if sys.version_info[0] >= 3: # Python >=3.0 from io import StringIO else: # Python <3.0 from cStringIO import StringIO import svntest logger = logging.getLogger() # # 'status -v' output looks like this: # # "%c%c%c%c%c%c%c %c %6s %6s %-12s %s\n" # # (Taken from 'print_status' in subversion/svn/status.c.) # # Here are the parameters. The middle number or string in parens is the # match.group(), followed by a brief description of the field: # # - text status (1) (single letter) # - prop status (1) (single letter) # - wc-lockedness flag (2) (single letter: "L" or " ") # - copied flag (3) (single letter: "+" or " ") # - switched flag (4) (single letter: "S", "X" or " ") # - repos lock status (5) (single letter: "K", "O", "B", "T", " ") # - tree conflict flag (6) (single letter: "C" or " ") # # [one space] # # - out-of-date flag (7) (single letter: "*" or " ") # # [three spaces] # # - working revision ('wc_rev') (either digits or "-", "?" or " ") # # [one space] # # - last-changed revision (either digits or "?" or " ") # # [one space] # # - last author (optional string of non-whitespace # characters) # # [spaces] # # - path ('path') (string of characters until newline) # # Working revision, last-changed revision, and last author are whitespace # only if the item is missing. # _re_parse_status = re.compile('^([?!MACDRUGXI_~ ][MACDRUG_ ])' '([L ])' '([+ ])' '([SX ])' '([KOBT ])' '([C ]) ' '([* ]) +' '((?P\d+|-|\?) +(\d|-|\?)+ +(\S+) +)?' '(?P.+)$') _re_parse_skipped = re.compile("^Skipped[^']* '(.+)'( --.*)?\n") _re_parse_summarize = re.compile("^([MAD ][M ]) (.+)\n") _re_parse_checkout = re.compile('^([RMAGCUDE_ B][MAGCUDE_ ])' '([B ])' '([CAUD ])\s+' '(.+)') _re_parse_co_skipped = re.compile('^(Restored|Skipped|Removed external)' '\s+\'(.+)\'(( --|: ).*)?') _re_parse_co_restored = re.compile('^(Restored)\s+\'(.+)\'') # Lines typically have a verb followed by whitespace then a path. _re_parse_commit_ext = re.compile('^(([A-Za-z]+( [a-z]+)*)) \'(.+)\'( --.*)?') _re_parse_commit = re.compile('^(\w+( \(bin\))?)\s+(.+)') class State: """Describes an existing or expected state of a working copy. The primary metaphor here is a dictionary of paths mapping to instances of StateItem, which describe each item in a working copy. Note: the paths should be *relative* to the root of the working copy, using '/' for the separator (see to_relpath()), and the root of the working copy is identified by the empty path: ''. """ def __init__(self, wc_dir, desc): "Create a State using the specified description." assert isinstance(desc, dict) self.wc_dir = wc_dir self.desc = desc # dictionary: path -> StateItem def add(self, more_desc): "Add more state items into the State." assert isinstance(more_desc, dict) self.desc.update(more_desc) def add_state(self, parent, state): "Import state items from a State object, reparent the items to PARENT." assert isinstance(state, State) if parent and parent[-1] != '/': parent += '/' for path, item in state.desc.items(): path = parent + path self.desc[path] = item def remove(self, *paths): "Remove PATHS from the state (the paths must exist)." for path in paths: del self.desc[to_relpath(path)] def remove_subtree(self, *paths): "Remove PATHS recursively from the state (the paths must exist)." for subtree_path in paths: subtree_path = to_relpath(subtree_path) for path, item in self.desc.items(): if path == subtree_path or path[:len(subtree_path) + 1] == subtree_path + '/': del self.desc[path] def copy(self, new_root=None): """Make a deep copy of self. If NEW_ROOT is not None, then set the copy's wc_dir NEW_ROOT instead of to self's wc_dir.""" desc = { } for path, item in self.desc.items(): desc[path] = item.copy() if new_root is None: new_root = self.wc_dir return State(new_root, desc) def tweak(self, *args, **kw): """Tweak the items' values. Each argument in ARGS is the path of a StateItem that already exists in this State. Each keyword argument in KW is a modifiable property of StateItem. The general form of this method is .tweak([paths...,] key=value...). If one or more paths are provided, then those items' values are modified. If no paths are given, then all items are modified. """ if args: for path in args: try: path_ref = self.desc[to_relpath(path)] except KeyError, e: e.args = ["Path '%s' not present in WC state descriptor" % path] raise path_ref.tweak(**kw) else: for item in self.desc.values(): item.tweak(**kw) def tweak_some(self, filter, **kw): "Tweak the items for which the filter returns true." for path, item in self.desc.items(): if list(filter(path, item)): item.tweak(**kw) def subtree(self, subtree_path): """Return a State object which is a deep copy of the sub-tree beneath SUBTREE_PATH (which is assumed to be rooted at the tree of this State object's WC_DIR). Exclude SUBTREE_PATH itself.""" desc = { } for path, item in self.desc.items(): if path[:len(subtree_path) + 1] == subtree_path + '/': desc[path[len(subtree_path) + 1:]] = item.copy() return State(self.wc_dir, desc) def write_to_disk(self, target_dir): """Construct a directory structure on disk, matching our state. WARNING: any StateItem that does not have contents (.contents is None) is assumed to be a directory. """ if not os.path.exists(target_dir): os.makedirs(target_dir) for path, item in self.desc.items(): fullpath = os.path.join(target_dir, path) if item.contents is None: # a directory if not os.path.exists(fullpath): os.makedirs(fullpath) else: # a file # ensure its directory exists dirpath = os.path.dirname(fullpath) if not os.path.exists(dirpath): os.makedirs(dirpath) # write out the file contents now open(fullpath, 'wb').write(item.contents) def normalize(self): """Return a "normalized" version of self. A normalized version has the following characteristics: * wc_dir == '' * paths use forward slashes * paths are relative If self is already normalized, then it is returned. Otherwise, a new State is constructed with (shallow) references to self's StateItem instances. If the caller needs a fully disjoint State, then use .copy() on the result. """ if self.wc_dir == '': return self base = to_relpath(os.path.normpath(self.wc_dir)) desc = dict([(repos_join(base, path), item) for path, item in self.desc.items()]) return State('', desc) def compare(self, other): """Compare this State against an OTHER State. Three new set objects will be returned: CHANGED, UNIQUE_SELF, and UNIQUE_OTHER. These contain paths of StateItems that are different between SELF and OTHER, paths of items unique to SELF, and paths of item that are unique to OTHER, respectively. """ assert isinstance(other, State) norm_self = self.normalize() norm_other = other.normalize() # fast-path the easy case if norm_self == norm_other: fs = frozenset() return fs, fs, fs paths_self = set(norm_self.desc.keys()) paths_other = set(norm_other.desc.keys()) changed = set() for path in paths_self.intersection(paths_other): if norm_self.desc[path] != norm_other.desc[path]: changed.add(path) return changed, paths_self - paths_other, paths_other - paths_self def compare_and_display(self, label, other): """Compare this State against an OTHER State, and display differences. Information will be written to stdout, displaying any differences between the two states. LABEL will be used in the display. SELF is the "expected" state, and OTHER is the "actual" state. If any changes are detected/displayed, then SVNTreeUnequal is raised. """ norm_self = self.normalize() norm_other = other.normalize() changed, unique_self, unique_other = norm_self.compare(norm_other) if not changed and not unique_self and not unique_other: return # Use the shortest path as a way to find the "root-most" affected node. def _shortest_path(path_set): shortest = None for path in path_set: if shortest is None or len(path) < len(shortest): shortest = path return shortest if changed: path = _shortest_path(changed) display_nodes(label, path, norm_self.desc[path], norm_other.desc[path]) elif unique_self: path = _shortest_path(unique_self) default_singleton_handler('actual ' + label, path, norm_self.desc[path]) elif unique_other: path = _shortest_path(unique_other) default_singleton_handler('expected ' + label, path, norm_other.desc[path]) raise svntest.tree.SVNTreeUnequal def tweak_for_entries_compare(self): for path, item in self.desc.copy().items(): if item.status: # If this is an unversioned tree-conflict, remove it. # These are only in their parents' THIS_DIR, they don't have entries. if item.status[0] in '!?' and item.treeconflict == 'C': del self.desc[path] else: # when reading the entry structures, we don't examine for text or # property mods, so clear those flags. we also do not examine the # filesystem, so we cannot detect missing or obstructed files. if item.status[0] in 'M!~': item.status = ' ' + item.status[1] if item.status[1] == 'M': item.status = item.status[0] + ' ' # under wc-ng terms, we may report a different revision than the # backwards-compatible code should report. if there is a special # value for compatibility, then use it. if item.entry_rev is not None: item.wc_rev = item.entry_rev item.entry_rev = None # status might vary as well, e.g. when a directory is missing if item.entry_status is not None: item.status = item.entry_status item.entry_status = None if item.writelocked: # we don't contact the repository, so our only information is what # is in the working copy. 'K' means we have one and it matches the # repos. 'O' means we don't have one but the repos says the item # is locked by us, elsewhere. 'T' means we have one, and the repos # has one, but it is now owned by somebody else. 'B' means we have # one, but the repos does not. # # for each case of "we have one", set the writelocked state to 'K', # and clear it to None for the others. this will match what is # generated when we examine our working copy state. if item.writelocked in 'TB': item.writelocked = 'K' elif item.writelocked == 'O': item.writelocked = None def old_tree(self): "Return an old-style tree (for compatibility purposes)." nodelist = [ ] for path, item in self.desc.items(): nodelist.append(item.as_node_tuple(os.path.join(self.wc_dir, path))) tree = svntest.tree.build_generic_tree(nodelist) if 0: check = tree.as_state() if self != check: logger.warn(pprint.pformat(self.desc)) logger.warn(pprint.pformat(check.desc)) # STATE -> TREE -> STATE is lossy. # In many cases, TREE -> STATE -> TREE is not. # Even though our conversion from a TREE has lost some information, we # may be able to verify that our lesser-STATE produces the same TREE. svntest.tree.compare_trees('mismatch', tree, check.old_tree()) return tree def __str__(self): return str(self.old_tree()) def __eq__(self, other): if not isinstance(other, State): return False norm_self = self.normalize() norm_other = other.normalize() return norm_self.desc == norm_other.desc def __ne__(self, other): return not self.__eq__(other) @classmethod def from_status(cls, lines): """Create a State object from 'svn status' output.""" def not_space(value): if value and value != ' ': return value return None desc = { } for line in lines: if line.startswith('DBG:'): continue # Quit when we hit an externals status announcement. ### someday we can fix the externals tests to expect the additional ### flood of externals status data. if line.startswith('Performing'): break match = _re_parse_status.search(line) if not match or match.group(10) == '-': # ignore non-matching lines, or items that only exist on repos continue item = StateItem(status=match.group(1), locked=not_space(match.group(2)), copied=not_space(match.group(3)), switched=not_space(match.group(4)), writelocked=not_space(match.group(5)), treeconflict=not_space(match.group(6)), wc_rev=not_space(match.group('wc_rev')), ) desc[to_relpath(match.group('path'))] = item return cls('', desc) @classmethod def from_skipped(cls, lines): """Create a State object from 'Skipped' lines.""" desc = { } for line in lines: if line.startswith('DBG:'): continue match = _re_parse_skipped.search(line) if match: desc[to_relpath(match.group(1))] = StateItem() return cls('', desc) @classmethod def from_summarize(cls, lines): """Create a State object from 'svn diff --summarize' lines.""" desc = { } for line in lines: if line.startswith('DBG:'): continue match = _re_parse_summarize.search(line) if match: desc[to_relpath(match.group(2))] = StateItem(status=match.group(1)) return cls('', desc) @classmethod def from_checkout(cls, lines, include_skipped=True): """Create a State object from 'svn checkout' lines.""" if include_skipped: re_extra = _re_parse_co_skipped else: re_extra = _re_parse_co_restored desc = { } for line in lines: if line.startswith('DBG:'): continue match = _re_parse_checkout.search(line) if match: if match.group(3) != ' ': treeconflict = match.group(3) else: treeconflict = None desc[to_relpath(match.group(4))] = StateItem(status=match.group(1), treeconflict=treeconflict) else: match = re_extra.search(line) if match: desc[to_relpath(match.group(2))] = StateItem(verb=match.group(1)) return cls('', desc) @classmethod def from_commit(cls, lines): """Create a State object from 'svn commit' lines.""" desc = { } for line in lines: if line.startswith('DBG:') or line.startswith('Transmitting'): continue match = _re_parse_commit_ext.search(line) if match: desc[to_relpath(match.group(4))] = StateItem(verb=match.group(1)) continue match = _re_parse_commit.search(line) if match: desc[to_relpath(match.group(3))] = StateItem(verb=match.group(1)) return cls('', desc) @classmethod def from_wc(cls, base, load_props=False, ignore_svn=True): """Create a State object from a working copy. Walks the tree at PATH, building a State based on the actual files and directories found. If LOAD_PROPS is True, then the properties will be loaded for all nodes (Very Expensive!). If IGNORE_SVN is True, then the .svn subdirectories will be excluded from the State. """ if not base: # we're going to walk the base, and the OS wants "." base = '.' desc = { } dot_svn = svntest.main.get_admin_name() for dirpath, dirs, files in os.walk(base): parent = path_to_key(dirpath, base) if ignore_svn and dot_svn in dirs: dirs.remove(dot_svn) for name in dirs + files: node = os.path.join(dirpath, name) if os.path.isfile(node): contents = open(node, 'r').read() else: contents = None desc[repos_join(parent, name)] = StateItem(contents=contents) if load_props: paths = [os.path.join(base, to_ospath(p)) for p in desc.keys()] paths.append(base) all_props = svntest.tree.get_props(paths) for node, props in all_props.items(): if node == base: desc['.'] = StateItem(props=props) else: if base == '.': # 'svn proplist' strips './' from the paths. put it back on. node = os.path.join('.', node) desc[path_to_key(node, base)].props = props return cls('', desc) @classmethod def from_entries(cls, base): """Create a State object from a working copy, via the old "entries" API. Walks the tree at PATH, building a State based on the information provided by the old entries API, as accessed via the 'entries-dump' program. """ if not base: # we're going to walk the base, and the OS wants "." base = '.' if os.path.isfile(base): # a few tests run status on a single file. quick-and-dirty this. we # really should analyze the entry (similar to below) to be general. dirpath, basename = os.path.split(base) entries = svntest.main.run_entriesdump(dirpath) return cls('', { to_relpath(base): StateItem.from_entry(entries[basename]), }) desc = { } dot_svn = svntest.main.get_admin_name() for dirpath in svntest.main.run_entriesdump_subdirs(base): if base == '.' and dirpath != '.': dirpath = '.' + os.path.sep + dirpath entries = svntest.main.run_entriesdump(dirpath) if entries is None: continue if dirpath == '.': parent = '' elif dirpath.startswith('.' + os.sep): parent = to_relpath(dirpath[2:]) else: parent = to_relpath(dirpath) parent_url = entries[''].url for name, entry in entries.items(): # if the entry is marked as DELETED *and* it is something other than # schedule-add, then skip it. we can add a new node "over" where a # DELETED node lives. if entry.deleted and entry.schedule != 1: continue # entries that are ABSENT don't show up in status if entry.absent: continue if name and entry.kind == 2: # stub subdirectory. leave a "missing" StateItem in here. note # that we can't put the status as "! " because that gets tweaked # out of our expected tree. item = StateItem(status=' ', wc_rev='?') desc[repos_join(parent, name)] = item continue item = StateItem.from_entry(entry) if name: desc[repos_join(parent, name)] = item implied_url = repos_join(parent_url, svn_uri_quote(name)) else: item._url = entry.url # attach URL to directory StateItems desc[parent] = item grandpa, this_name = repos_split(parent) if grandpa in desc: implied_url = repos_join(desc[grandpa]._url, svn_uri_quote(this_name)) else: implied_url = None if implied_url and implied_url != entry.url: item.switched = 'S' return cls('', desc) class StateItem: """Describes an individual item within a working copy. Note that the location of this item is not specified. An external mechanism, such as the State class, will provide location information for each item. """ def __init__(self, contents=None, props=None, status=None, verb=None, wc_rev=None, entry_rev=None, entry_status=None, locked=None, copied=None, switched=None, writelocked=None, treeconflict=None): # provide an empty prop dict if it wasn't provided if props is None: props = { } ### keep/make these ints one day? if wc_rev is not None: wc_rev = str(wc_rev) # Any attribute can be None if not relevant, unless otherwise stated. # A string of content (if the node is a file). self.contents = contents # A dictionary mapping prop name to prop value; never None. self.props = props # A two-character string from the first two columns of 'svn status'. self.status = status # The action word such as 'Adding' printed by commands like 'svn update'. self.verb = verb # The base revision number of the node in the WC, as a string. self.wc_rev = wc_rev # These will be set when we expect the wc_rev/status to differ from those # found in the entries code. self.entry_rev = entry_rev self.entry_status = entry_status # For the following attributes, the value is the status character of that # field from 'svn status', except using value None instead of status ' '. self.locked = locked self.copied = copied self.switched = switched self.writelocked = writelocked # Value 'C' or ' ', or None as an expected status meaning 'do not check'. self.treeconflict = treeconflict def copy(self): "Make a deep copy of self." new = StateItem() vars(new).update(vars(self)) new.props = self.props.copy() return new def tweak(self, **kw): for name, value in kw.items(): # Refine the revision args (for now) to ensure they are strings. if value is not None and name == 'wc_rev': value = str(value) setattr(self, name, value) def __eq__(self, other): if not isinstance(other, StateItem): return False v_self = dict([(k, v) for k, v in vars(self).items() if not k.startswith('_')]) v_other = dict([(k, v) for k, v in vars(other).items() if not k.startswith('_')]) if self.treeconflict is None: v_other = v_other.copy() v_other['treeconflict'] = None if other.treeconflict is None: v_self = v_self.copy() v_self['treeconflict'] = None return v_self == v_other def __ne__(self, other): return not self.__eq__(other) def as_node_tuple(self, path): atts = { } if self.status is not None: atts['status'] = self.status if self.verb is not None: atts['verb'] = self.verb if self.wc_rev is not None: atts['wc_rev'] = self.wc_rev if self.locked is not None: atts['locked'] = self.locked if self.copied is not None: atts['copied'] = self.copied if self.switched is not None: atts['switched'] = self.switched if self.writelocked is not None: atts['writelocked'] = self.writelocked if self.treeconflict is not None: atts['treeconflict'] = self.treeconflict return (os.path.normpath(path), self.contents, self.props, atts) @classmethod def from_entry(cls, entry): status = ' ' if entry.schedule == 1: # svn_wc_schedule_add status = 'A ' elif entry.schedule == 2: # svn_wc_schedule_delete status = 'D ' elif entry.schedule == 3: # svn_wc_schedule_replace status = 'R ' elif entry.conflict_old: ### I'm assuming we only need to check one, rather than all conflict_* status = 'C ' ### is this the sufficient? guessing here w/o investigation. if entry.prejfile: status = status[0] + 'C' if entry.locked: locked = 'L' else: locked = None if entry.copied: wc_rev = '-' copied = '+' else: if entry.revision == -1: wc_rev = '?' else: wc_rev = entry.revision copied = None ### figure out switched switched = None if entry.lock_token: writelocked = 'K' else: writelocked = None return cls(status=status, wc_rev=wc_rev, locked=locked, copied=copied, switched=switched, writelocked=writelocked, ) if os.sep == '/': to_relpath = to_ospath = lambda path: path else: def to_relpath(path): """Return PATH but with all native path separators changed to '/'.""" return path.replace(os.sep, '/') def to_ospath(path): """Return PATH but with each '/' changed to the native path separator.""" return path.replace('/', os.sep) def path_to_key(path, base): """Return the relative path that represents the absolute path PATH under the absolute path BASE. PATH must be a path under BASE. The returned path has '/' separators.""" if path == base: return '' if base.endswith(os.sep) or base.endswith('/') or base.endswith(':'): # Special path format on Windows: # 'C:/' Is a valid root which includes its separator ('C:/file') # 'C:' is a valid root which isn't followed by a separator ('C:file') # # In this case, we don't need a separator between the base and the path. pass else: # Account for a separator between the base and the relpath we're creating base += os.sep assert path.startswith(base), "'%s' is not a prefix of '%s'" % (base, path) return to_relpath(path[len(base):]) def repos_split(repos_relpath): """Split a repos path into its directory and basename parts.""" idx = repos_relpath.rfind('/') if idx == -1: return '', repos_relpath return repos_relpath[:idx], repos_relpath[idx+1:] def repos_join(base, path): """Join two repos paths. This generally works for URLs too.""" if base == '': return path if path == '': return base return base + '/' + path def svn_uri_quote(url): # svn defines a different set of "safe" characters than Python does, so # we need to avoid escaping them. see subr/path.c:uri_char_validity[] return urllib.quote(url, "!$&'()*+,-./:=@_~") # ------------ def open_wc_db(local_path): """Open the SQLite DB for the WC path LOCAL_PATH. Return (DB object, WC root path, WC relpath of LOCAL_PATH).""" dot_svn = svntest.main.get_admin_name() root_path = local_path relpath = '' while True: db_path = os.path.join(root_path, dot_svn, 'wc.db') try: db = svntest.sqlite3.connect(db_path) break except: pass head, tail = os.path.split(root_path) if head == root_path: raise svntest.Failure("No DB for " + local_path) root_path = head relpath = os.path.join(tail, relpath).replace(os.path.sep, '/').rstrip('/') return db, root_path, relpath # ------------ def text_base_path(file_path): """Return the path to the text-base file for the versioned file FILE_PATH.""" info = svntest.actions.run_and_parse_info(file_path)[0] checksum = info['Checksum'] db, root_path, relpath = open_wc_db(file_path) # Calculate single DB location dot_svn = svntest.main.get_admin_name() fn = os.path.join(root_path, dot_svn, 'pristine', checksum[0:2], checksum) # For SVN_WC__VERSION < 29 if os.path.isfile(fn): return fn # For SVN_WC__VERSION >= 29 if os.path.isfile(fn + ".svn-base"): return fn + ".svn-base" raise svntest.Failure("No pristine text for " + relpath) # ------------ ### probably toss these at some point. or major rework. or something. ### just bootstrapping some changes for now. # def item_to_node(path, item): tree = svntest.tree.build_generic_tree([item.as_node_tuple(path)]) while tree.children: assert len(tree.children) == 1 tree = tree.children[0] return tree ### yanked from tree.compare_trees() def display_nodes(label, path, expected, actual): 'Display two nodes, expected and actual.' expected = item_to_node(path, expected) actual = item_to_node(path, actual) o = StringIO() o.write("=============================================================\n") o.write("Expected '%s' and actual '%s' in %s tree are different!\n" % (expected.name, actual.name, label)) o.write("=============================================================\n") o.write("EXPECTED NODE TO BE:\n") o.write("=============================================================\n") expected.pprint(o) o.write("=============================================================\n") o.write("ACTUAL NODE FOUND:\n") o.write("=============================================================\n") actual.pprint(o) logger.warn(o.getvalue()) o.close() ### yanked from tree.py def default_singleton_handler(description, path, item): node = item_to_node(path, item) logger.warn("Couldn't find node '%s' in %s tree" % (node.name, description)) o = StringIO() node.pprint(o) logger.warn(o.getvalue()) o.close() raise svntest.tree.SVNTreeUnequal cvs2svn-2.5.0/svntest/factory.py0000664000175100017510000016770012203665123017736 0ustar mhaggermhagger00000000000000# # factory.py: Automatically generate a (near-)complete new cmdline test # from a series of shell commands. # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### ## HOW TO USE: # # (1) Edit the py test script you want to enhance (for example # cmdline/basic_tests.py), add a new test header as usual. # Insert a call to factory.make() into the empty test: # # def my_new_test(sbox): # "my new test modifies iota" # svntest.factory.make(sbox, """ # echo "foo" > A/D/foo # svn add A/D/foo # svn st # svn ci # """) # # (2) Add the test to the tests list at the bottom of the py file. # [...] # some_other_test, # my_new_test, # ] # # # (3) Run the test, paste the output back into your new test, # replacing the factory call. # # $ ./foo_tests.py my_new_test # OR # $ ./foo_tests.py my_new_test > new_test.snippet # OR # $ ./foo_tests.py my_new_test >> basic_tests.py # Then edit (e.g.) basic_tests.py to put the script in the right place. # # Ensure that the py script (e.g. basic_tests.py) has these imports, # so that the composed script that you pasted back finds everything # that it uses: # import os, shutil # from svntest import main, wc, actions, verify # # Be aware that you have to paste the result back to the .py file. # # Be more aware that you have to read every single line and understand # that it makes sense. If the current behaviour is wrong, you need to # make the changes to expect the correct behaviour and XFail() your test. # # factory.make() just probes the current situation and writes a test that # PASSES any success AND ANY FAILURE THAT IT FINDS. The resulting script # will never fail anything (if it works correctly), not even a failure. # # ### TODO: some sort of intelligent pasting directly into the # right place, like looking for the factory call, # inserting the new test there, back-up-ing the old file. # # # TROUBLESHOOTING # If the result has a problem somewhere along the middle, you can, # of course, only use the first part of the output result, maybe tweak # something, and continue with another factory.make() at the end of that. # # Or you can first do stuff to your sbox and then call factory on it. # Factory will notice if the sbox has already been built and calls # sbox.build() only if you didn't already. # # You can also have any number of factory.make() calls scattered # around "real" test code. # # Note that you can pass a prev_status and prev_disk to factory, to make # the expected_* trees re-use a pre-existing one in the test, entirely # for code beauty :P (has to match the wc_dir you will be using next). # # # YOU ARE CORDIALLY INVITED to add/tweak/change to your needs. # If you want to know what's going on, look at the switch() # funtion of TestFactory below. # # # DETAILS # ======= # # The input to factory.make(sbox, input) is not "real" shell-script. # Factory goes at great lengths to try and understand your script, it # parses common shell operations during tests and translates them. # # All arguments are tokenized similarly to shell, so if you need a space # in an argument, use quotes. # echo "my content" > A/new_file # Quote char escaping is done like this: # echo "my \\" content" > A/new_file # echo 'my \\' content' > A/new_file # If you use r""" echo 'my \' content' > A/new_file """ (triple quotes # with a leading 'r' character), you don't need to double-escape any # characters. # # You can either supply multiple lines, or separate the lines with ';'. # factory.make(sbox, 'echo foo > bar; svn add bar') # factory.make(sbox, 'echo foo > bar\n svn add bar') # factory.make(sbox, r""" # echo "foo\nbar" > bar # svn add bar # """) # # # WORKING COPY PATHS # - Factory will automatically build sbox.wc_dir if you didn't do so yet. # # - If you supply any path or file name, factory will prepend sbox.wc_dir # to it. # echo more >> iota # --> main.file_append( # os.path.join(sbox.wc_dir, 'iota'), # "more") # You can also do so explicitly. # echo more >> wc_dir/iota # --> main.file_append( # os.path.join(sbox.wc_dir, 'iota'), # "more") # # Factory implies the sbox.wc_dir if you fail to supply an explicit # working copy dir. If you want to supply one explicitly, you can # choose among these wildcards: # 'wc_dir', 'wcdir', '$WC_DIR', '$WC' -- all expanded to sbox.wc_dir # For example: # 'svn mkdir wc_dir/A/D/X' # But as long as you want to use only the default sbox.wc_dir, you usually # don't need to supply any wc_dir-wildcard: # 'mkdir A/X' creates the directory sbox.wc_dir/A/X # (Factory tries to know which arguments of the commands you supplied # are eligible to be path arguments. If something goes wrong here, try # to fix factory.py to not mistake the arg for something different. # You usually just need to tweak some parameters to args2svntest() to # achieve correct expansion.) # # - If you want to use a second (or Nth) working copy, just supply any # working copy wildcard with any made-up suffix, e.g. like this: # 'svn st wc_dir_2' or 'svn info $WC_2' # Factory will detect that you used another wc_dir and will automatically # add a corresponding directory to your sbox. The directory will initially # be nonexistent, so call 'mkdir', 'svn co' or 'cp' before using: # 'cp wc_dir wc_dir_other' -- a copy of the current WC # 'svn co $URL wc_dir_new' -- a clean checkout # 'mkdir wc_dir_empty' -- an empty directory # You can subsequently use any wc-dir wildcard with your suffix added. # # cp wc_dir wc_dir_2 # echo more >> wc_dir_2/iota # --> wc_dir_2 = sbox.add_wc_path('2') # shutil.copytrees(wc_dir, wc_dir_2) # main.file_append( # os.path.join(wc_dir_2, 'iota'), # "more") # # # URLs # Factory currently knows only one repository, thus only one repos root. # The wildcards you can use for it are: # 'url', '$URL' # A URL is not inserted automatically like wc_dir, you need to supply a # URL wildcard. # Alternatively, you can use '^/' URLs. However, that is in effect a different # test from an explicit entire URL. The test needs to chdir to the working # copy in order find which URL '^/' should expand to. # (currently, factory will chdir to sbox.wc_dir. It will only chdir # to another working copy if one of the other arguments involved a WC. # ### TODO add a 'cd wc_dir_2' command to select another WC as default.) # Example: # 'svn co $URL Y' -- make a new nested working copy in sbox.wc_dir/Y # 'svn co $URL wc_dir_2' -- create a new separate working copy # 'svn cp ^/A ^/X' -- do a URL copy, creating $URL/X (branch) # # # SOME EXAMPLES # These commands should work: # # - "svn " # Some subcommands are parsed specially, others by a catch-all default # parser (cmd_svn()), see switch(). # 'svn commit', 'svn commit --force', 'svn ci wc_dir_2' # 'svn copy url/A url/X' # # - "echo contents > file" (replace) # "echo contents >> file" (append) # Calls main.file_write() / main.file_append(). # 'echo "froogle" >> A/D/G/rho' -- append to an existing file # 'echo "bar" > A/quux' -- create a new file # 'echo "fool" > wc_dir_2/me' -- manipulate other working copies # # - "mkdir ..." # Calls os.makedirs(). # You probably want 'svn mkdir' instead, or use 'svn add' after this. # 'mkdir A/D/X' -- create an unversioned directory # 'mkdir wc_dir_5' -- create a new, empty working copy # # - "rm " # Calls main.safe_rmtree(). # You probably want to use 'svn delete' instead. # 'rm A/D/G' # 'rm wc_dir_2' # # - "mv [ ...] " # Calls shutil.move() # You probably want to use 'svn move' instead. # 'mv iota A/D/' -- move sbox.wc_dir/iota to sbox.wc_dir/A/D/. # # - "cp [ ...] " # Do a filesystem copy. # You probably want to use 'svn copy' instead. # 'cp wc_dir wc_dir_copy' # 'cp A/D/G A/X' # # IF YOU NEED ANY OTHER COMMANDS: # - first check if it doesn't work already. If not, # - add your desired commands to factory.py! :) # - alternatively, use a number of separate factory calls, doing what # you need done in "real" svntest language in-between. # # IF YOU REALLY DON'T GROK THIS: # - ask #svn-dev # - ask dev@ # - ask neels import sys, re, os, shutil, bisect, textwrap, shlex import svntest from svntest import main, actions, tree from svntest import Failure if sys.version_info[0] >= 3: # Python >=3.0 from io import StringIO else: # Python <3.0 from cStringIO import StringIO def make(wc_dir, commands, prev_status=None, prev_disk=None, verbose=True): """The Factory Invocation Function. This is typically the only one called from outside this file. See top comment in factory.py. Prints the resulting py script to stdout when verbose is True and returns the resulting line-list containing items as: [ ['pseudo-shell input line #1', ' translation\n to\n py #1'], ...]""" fac = TestFactory(wc_dir, prev_status, prev_disk) fac.make(commands) fac.print_script() return fac.lines class TestFactory: """This class keeps all state around a factory.make() call.""" def __init__(self, sbox, prev_status=None, prev_disk=None): self.sbox = sbox # The input lines and their translations. # Each translation usually has multiple output lines ('\n' characters). self.lines = [] # [ ['in1', 'out1'], ['in2', 'out'], ... # Any expected_status still there from a previous verification self.prev_status = None if prev_status: self.prev_status = [None, prev_status] # svntest.wc.State # Any expected_disk still there from a previous verification self.prev_disk = None if prev_disk: reparented_prev_disk = svntest.wc.State(prev_disk.wc_dir, {}); reparented_prev_disk.add_state(sbox.wc_dir, prev_disk); self.prev_disk = [None, reparented_prev_disk] # Those command line options that expect an argument following # which is not a path. (don't expand args following these) self.keep_args_of = ['--depth', '--encoding', '-r', '--changelist', '-m', '--message'] # A stack of $PWDs, to be able to chdir back after a chdir. self.prevdirs = [] # The python variables we want to be declared at the beginning. # These are path variables like "A_D = os.path.join(wc_dir, 'A', 'D')". # The original wc_dir and url vars are not kept here. self.vars = {} # An optimized list kept up-to-date by variable additions self.sorted_vars_by_pathlen = [] # Wether we ever used the variables 'wc_dir' and 'url' (tiny tweak) self.used_wc_dir = False self.used_url = False # The alternate working copy directories created that need to be # registered with sbox (are not inside another working copy). self.other_wc_dirs = {} def make(self, commands): "internal main function, delegates everything except final output." # keep a spacer for init self.add_line(None, None) init = "" if not self.sbox.is_built(): self.sbox.build() init += "sbox.build()\n" try: # split input args input_lines = commands.replace(';','\n').splitlines() for str in input_lines: if len(str.strip()) > 0: self.add_line(str) for i in range(len(self.lines)): if self.lines[i][0] is not None: # This is where everything happens: self.lines[i][1] = self.switch(self.lines[i][0]) # We're done. Add a final greeting. self.add_line( None, "Remember, this only saves you typing. Doublecheck everything.") # -- Insert variable defs in the first line -- # main wc_dir and url if self.used_wc_dir: init += 'wc_dir = sbox.wc_dir\n' if self.used_url: init += 'url = sbox.repo_url\n' # registration of new WC dirs sorted_names = self.get_sorted_other_wc_dir_names() for name in sorted_names: init += name + ' = ' + self.other_wc_dirs[name][0] + '\n' if len(init) > 0: init += '\n' # general variable definitions sorted_names = self.get_sorted_var_names() for name in sorted_names: init += name + ' = ' + self.vars[name][0] + '\n' # Insert at the first line, being the spacer from above if len(init) > 0: self.lines[0][1] = init # This usually goes to make() below (outside this class) return self.lines except: for line in self.lines: if line[1] is not None: print(line[1]) raise def print_script(self, stream=sys.stdout): "Output the resulting script of the preceding make() call" if self.lines is not None: for line in self.lines: if line[1] is None: # fall back to just that line as it was in the source stripped = line[0].strip() if not stripped.startswith('#'): # for comments, don't say this: stream.write(" # don't know how to handle:\n") stream.write(" " + line[0].strip() + '\n') else: if line[0] is not None: stream.write( wrap_each_line(line[0].strip(), " # ", " # ", True) + '\n') stream.write(wrap_each_line(line[1], " ", " ", False) + '\n\n') else: stream.write(" # empty.\n") stream.flush() # End of public functions. # "Shell" command handlers: def switch(self, line): "Given one input line, delegates to the appropriate sub-functions." args = shlex.split(line) if len(args) < 1: return "" first = args[0] # This is just an if-cascade. Feel free to change that. if first == 'svn': second = args[1] if second == 'add': return self.cmd_svn(args[1:], False, self.keep_args_of) if second in ['changelist', 'cl']: keep_count = 2 if '--remove' in args: keep_count = 1 return self.cmd_svn(args[1:], False, self.keep_args_of, keep_count) if second in ['status','stat','st']: return self.cmd_svn_status(args[2:]) if second in ['commit','ci']: return self.cmd_svn_commit(args[2:]) if second in ['update','up']: return self.cmd_svn_update(args[2:]) if second in ['switch','sw']: return self.cmd_svn_switch(args[2:]) if second in ['copy', 'cp', 'move', 'mv', 'rename', 'ren']: return self.cmd_svn_copy_move(args[1:]) if second in ['checkout', 'co']: return self.cmd_svn_checkout(args[2:]) if second in ['propset','pset','ps']: return self.cmd_svn(args[1:], False, self.keep_args_of, 3) if second in ['delete','del','remove', 'rm']: return self.cmd_svn(args[1:], False, self.keep_args_of + ['--with-revprop']) # NOTE that not all commands need to be listed here, since # some are already adequately handled by self.cmd_svn(). # If you find yours is not, add another self.cmd_svn_xxx(). return self.cmd_svn(args[1:], False, self.keep_args_of) if first == 'echo': return self.cmd_echo(args[1:]) if first == 'mkdir': return self.cmd_mkdir(args[1:]) if first == 'rm': return self.cmd_rm(args[1:]) if first == 'mv': return self.cmd_mv(args[1:]) if first == 'cp': return self.cmd_cp(args[1:]) # if all fails, take the line verbatim return None def cmd_svn_standard_run(self, pyargs, runargs, do_chdir, wc): "The generic invocation of svn, helper function." pychdir = self.chdir(do_chdir, wc) code, out, err = main.run_svn("Maybe", *runargs) if code == 0 and len(err) < 1: # write a test that expects success pylist = self.strlist2py(out) if len(out) <= 1: py = "expected_stdout = " + pylist + "\n\n" else: py = "expected_stdout = verify.UnorderedOutput(" + pylist + ")\n\n" py += pychdir py += "actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0" else: # write a test that expects failure pylist = self.strlist2py(err) if len(err) <= 1: py = "expected_stderr = " + pylist + "\n\n" else: py = "expected_stderr = verify.UnorderedOutput(" + pylist + ")\n\n" py += pychdir py += ("actions.run_and_verify_svn2('OUTPUT', " + "[], expected_stderr, " + str(code)) if len(pyargs) > 0: py += ", " + ", ".join(pyargs) py += ")\n" py += self.chdir_back(do_chdir) return py def cmd_svn(self, svnargs, append_wc_dir_if_missing = False, keep_args_of = [], keep_first_count = 1, drop_with_arg = []): "Handles all svn calls not handled by more specific functions." pyargs, runargs, do_chdir, targets = self.args2svntest(svnargs, append_wc_dir_if_missing, keep_args_of, keep_first_count, drop_with_arg) return self.cmd_svn_standard_run(pyargs, runargs, do_chdir, self.get_first_wc(targets)) def cmd_svn_status(self, status_args): "Runs svn status, looks what happened and writes the script for it." pyargs, runargs, do_chdir, targets = self.args2svntest( status_args, True, self.keep_args_of, 0) py = "" for target in targets: if not target.wc: py += '# SKIPPING NON-WC ' + target.runarg + '\n' continue if '-q' in status_args: pystatus = self.get_current_status(target.wc, True) py += (pystatus + "actions.run_and_verify_status(" + target.wc.py + ", expected_status)\n") else: pystatus = self.get_current_status(target.wc, False) py += (pystatus + "actions.run_and_verify_unquiet_status(" + target.wc.py + ", expected_status)\n") return py def cmd_svn_commit(self, commit_args): "Runs svn commit, looks what happened and writes the script for it." # these are the options that are followed by something that should not # be parsed as a filename in the WC. commit_arg_opts = [ "--depth", "--with-revprop", "--changelist", # "-F", "--file", these take a file argument, don't list here. # "-m", "--message", treated separately ] pyargs, runargs, do_chdir, targets = self.args2svntest( commit_args, True, commit_arg_opts, 0, ['-m', '--message']) wc = self.get_first_wc(targets) pychdir = self.chdir(do_chdir, wc) code, output, err = main.run_svn("Maybe", 'ci', '-m', 'log msg', *runargs) if code == 0 and len(err) < 1: # write a test that expects success output = actions.process_output_for_commit(output) actual_out = tree.build_tree_from_commit(output) py = ("expected_output = " + self.tree2py(actual_out, wc) + "\n\n") pystatus = self.get_current_status(wc) py += pystatus py += pychdir py += ("actions.run_and_verify_commit(" + wc.py + ", " + "expected_output, expected_status, " + "None") else: # write a test that expects error py = "expected_error = " + self.strlist2py(err) + "\n\n" py += pychdir py += ("actions.run_and_verify_commit(" + wc.py + ", " + "None, None, expected_error") if len(pyargs) > 0: py += ', ' + ', '.join(pyargs) py += ")" py += self.chdir_back(do_chdir) return py def cmd_svn_update(self, update_args): "Runs svn update, looks what happened and writes the script for it." pyargs, runargs, do_chdir, targets = self.args2svntest( update_args, True, self.keep_args_of, 0) wc = self.get_first_wc(targets) pychdir = self.chdir(do_chdir, wc) code, output, err = main.run_svn('Maybe', 'up', *runargs) if code == 0 and len(err) < 1: # write a test that expects success actual_out = svntest.wc.State.from_checkout(output).old_tree() py = ("expected_output = " + self.tree2py(actual_out, wc) + "\n\n") pydisk = self.get_current_disk(wc) py += pydisk pystatus = self.get_current_status(wc) py += pystatus py += pychdir py += ("actions.run_and_verify_update(" + wc.py + ", " + "expected_output, expected_disk, expected_status, " + "None, None, None, None, None, False") else: # write a test that expects error py = "expected_error = " + self.strlist2py(err) + "\n\n" py += pychdir py += ("actions.run_and_verify_update(" + wc.py + ", None, None, " + "None, expected_error, None, None, None, None, False") if len(pyargs) > 0: py += ', ' + ', '.join(pyargs) py += ")" py += self.chdir_back(do_chdir) return py def cmd_svn_switch(self, switch_args): "Runs svn switch, looks what happened and writes the script for it." pyargs, runargs, do_chdir, targets = self.args2svntest( switch_args, True, self.keep_args_of, 0) # Sort out the targets. We need one URL and one wc node, in that order. if len(targets) < 2: raise Failure("Sorry, I'm currently enforcing two targets for svn " + "switch. If you want to supply less, remove this " + "check and implement whatever seems appropriate.") wc_arg = targets[1] del pyargs[wc_arg.argnr] del runargs[wc_arg.argnr] url_arg = targets[0] del pyargs[url_arg.argnr] del runargs[url_arg.argnr] wc = wc_arg.wc if not wc: raise Failure("Unexpected argument ordering to factory's 'svn switch'?") pychdir = self.chdir(do_chdir, wc) #if '--force' in runargs: # self.really_safe_rmtree(wc_arg.runarg) code, output, err = main.run_svn('Maybe', 'sw', url_arg.runarg, wc_arg.runarg, *runargs) py = "" if code == 0 and len(err) < 1: # write a test that expects success actual_out = tree.build_tree_from_checkout(output) py = ("expected_output = " + self.tree2py(actual_out, wc) + "\n\n") pydisk = self.get_current_disk(wc) py += pydisk pystatus = self.get_current_status(wc) py += pystatus py += pychdir py += ("actions.run_and_verify_switch(" + wc.py + ", " + wc_arg.pyarg + ", " + url_arg.pyarg + ", " + "expected_output, expected_disk, expected_status, " + "None, None, None, None, None, False") else: # write a test that expects error py = "expected_error = " + self.strlist2py(err) + "\n\n" py += pychdir py += ("actions.run_and_verify_switch(" + wc.py + ", " + wc_arg.pyarg + ", " + url_arg.pyarg + ", " + "None, None, None, expected_error, None, None, None, None, False") if len(pyargs) > 0: py += ', ' + ', '.join(pyargs) py += ")" py += self.chdir_back(do_chdir) return py def cmd_svn_checkout(self, checkout_args): "Runs svn checkout, looks what happened and writes the script for it." pyargs, runargs, do_chdir, targets = self.args2svntest( checkout_args, True, self.keep_args_of, 0) # Sort out the targets. We need one URL and one dir, in that order. if len(targets) < 2: raise Failure("Sorry, I'm currently enforcing two targets for svn " + "checkout. If you want to supply less, remove this " + "check and implement whatever seems appropriate.") # We need this separate for the call to run_and_verify_checkout() # that's composed in the output script. wc_arg = targets[1] del pyargs[wc_arg.argnr] del runargs[wc_arg.argnr] url_arg = targets[0] del pyargs[url_arg.argnr] del runargs[url_arg.argnr] wc = wc_arg.wc pychdir = self.chdir(do_chdir, wc) #if '--force' in runargs: # self.really_safe_rmtree(wc_arg.runarg) code, output, err = main.run_svn('Maybe', 'co', url_arg.runarg, wc_arg.runarg, *runargs) py = "" if code == 0 and len(err) < 1: # write a test that expects success actual_out = tree.build_tree_from_checkout(output) pyout = ("expected_output = " + self.tree2py(actual_out, wc) + "\n\n") py += pyout pydisk = self.get_current_disk(wc) py += pydisk py += pychdir py += ("actions.run_and_verify_checkout(" + url_arg.pyarg + ", " + wc_arg.pyarg + ", expected_output, expected_disk, None, None, None, None") else: # write a test that expects failure pylist = self.strlist2py(err) if len(err) <= 1: py += "expected_stderr = " + pylist + "\n\n" else: py += "expected_stderr = verify.UnorderedOutput(" + pylist + ")\n\n" py += pychdir py += ("actions.run_and_verify_svn2('OUTPUT', " + "[], expected_stderr, " + str(code) + ", " + url_arg.pyarg + ", " + wc_arg.pyarg) # Append the remaining args if len(pyargs) > 0: py += ', ' + ', '.join(pyargs) py += ")" py += self.chdir_back(do_chdir) return py def cmd_svn_copy_move(self, args): "Runs svn copy or move, looks what happened and writes the script for it." pyargs, runargs, do_chdir, targets = self.args2svntest(args, False, self.keep_args_of, 1) if len(targets) == 2 and targets[1].is_url: # The second argument is a URL. # This needs a log message. Is one supplied? has_message = False for arg in runargs: if arg.startswith('-m') or arg == '--message': has_message = True break if not has_message: # add one runargs += [ '-m', 'copy log' ] pyargs = [] for arg in runargs: pyargs += [ self.str2svntest(arg) ] return self.cmd_svn_standard_run(pyargs, runargs, do_chdir, self.get_first_wc(targets)) def cmd_echo(self, echo_args): "Writes a string to a file and writes the script for it." # split off target target_arg = None replace = True contents = None for i in range(len(echo_args)): arg = echo_args[i] if arg.startswith('>'): if len(arg) > 1: if arg[1] == '>': # it's a '>>' replace = False arg = arg[2:] else: arg = arg[1:] if len(arg) > 0: target_arg = arg if target_arg is None: # we need an index (i+1) to exist, and # we need (i+1) to be the only existing index left in the list. if i+1 != len(echo_args)-1: raise Failure("don't understand: echo " + " ".join(echo_args)) target_arg = echo_args[i+1] else: # already got the target. no more indexes should exist. if i != len(echo_args)-1: raise Failure("don't understand: echo " + " ".join(echo_args)) contents = " ".join(echo_args[:i]) + '\n' if target_arg is None: raise Failure("echo needs a '>' pipe to a file name: echo " + " ".join(echo_args)) target = self.path2svntest(target_arg) if replace: main.file_write(target.runarg, contents) py = "main.file_write(" else: main.file_append(target.runarg, contents) py = "main.file_append(" py += target.pyarg + ", " + self.str2svntest(contents) + ")" return py def cmd_mkdir(self, mkdir_args): "Makes a new directory and writes the script for it." # treat all mkdirs as -p, ignore all -options. out = "" for arg in mkdir_args: if not arg.startswith('-'): target = self.path2svntest(arg) # don't check for not being a url, # maybe it's desired by the test or something. os.makedirs(target.runarg) out += "os.makedirs(" + target.pyarg + ")\n" return out def cmd_rm(self, rm_args): "Removes a directory tree and writes the script for it." # treat all removes as -rf, ignore all -options. out = "" for arg in rm_args: if not arg.startswith('-'): target = self.path2svntest(arg) if os.path.isfile(target.runarg): os.remove(target.runarg) out += "os.remove(" + target.pyarg + ")\n" else: self.really_safe_rmtree(target.runarg) out += "main.safe_rmtree(" + target.pyarg + ")\n" return out def cmd_mv(self, mv_args): "Moves things in the filesystem and writes the script for it." # ignore all -options. out = "" sources = [] target = None for arg in mv_args: if not arg.startswith('-'): if target is not None: sources += [target] target = self.path2svntest(arg) out = "" for source in sources: out += "shutil.move(" + source.pyarg + ", " + target.pyarg + ")\n" shutil.move(source.runarg, target.runarg) return out def cmd_cp(self, mv_args): "Copies in the filesystem and writes the script for it." # ignore all -options. out = "" sources = [] target = None for arg in mv_args: if not arg.startswith('-'): if target is not None: sources += [target] target = self.path2svntest(arg) if not target: raise Failure("cp needs a source and a target 'cp wc_dir wc_dir_2'") out = "" for source in sources: if os.path.exists(target.runarg): raise Failure("cp target exists, remove first: " + target.pyarg) if os.path.isdir(source.runarg): shutil.copytree(source.runarg, target.runarg) out += "shutil.copytree(" + source.pyarg + ", " + target.pyarg + ")\n" elif os.path.isfile(source.runarg): shutil.copy2(source.runarg, target.runarg) out += "shutil.copy2(" + source.pyarg + ", " + target.pyarg + ")\n" else: raise Failure("cp copy source does not exist: " + source.pyarg) return out # End of "shell" command handling functions. # Internal helpers: class WorkingCopy: "Defines the list of info we need around a working copy." def __init__(self, py, realpath, suffix): self.py = py self.realpath = realpath self.suffix = suffix class Target: "Defines the list of info we need around a command line supplied target." def __init__(self, pyarg, runarg, argnr, is_url=False, wc=None): self.pyarg = pyarg self.runarg = runarg self.argnr = argnr self.is_url = is_url self.wc = wc def add_line(self, args, translation=None): "Definition of how to add a new in/out line pair to LINES." self.lines += [ [args, translation] ] def really_safe_rmtree(self, dir): # Safety catch. We don't want to remove outside the sandbox. if dir.find('svn-test-work') < 0: raise Failure("Tried to remove path outside working area: " + dir) main.safe_rmtree(dir) def get_current_disk(self, wc): "Probes the given working copy and writes an expected_disk for it." actual_disk = svntest.wc.State.from_wc(wc.realpath, False, True) actual_disk.wc_dir = wc.realpath make_py, prev_disk = self.get_prev_disk(wc) # The tests currently compare SVNTreeNode trees, so let's do that too. actual_disk_tree = actual_disk.old_tree() prev_disk_tree = prev_disk.old_tree() # find out the tweaks tweaks = self.diff_trees(prev_disk_tree, actual_disk_tree, wc) if tweaks == 'Purge': make_py = '' else: tweaks = self.optimize_tweaks(tweaks, actual_disk_tree, wc) self.remember_disk(wc, actual_disk) pydisk = make_py + self.tweaks2py(tweaks, "expected_disk", wc) if len(pydisk) > 0: pydisk += '\n' return pydisk def get_prev_disk(self, wc): "Retrieves the last used expected_disk tree if any." make_py = "" # If a disk was supplied via __init__(), self.prev_disk[0] is set # to None, in which case we always use it, not checking WC. if self.prev_disk is None or \ not self.prev_disk[0] in [None, wc.realpath]: disk = svntest.main.greek_state.copy() disk.wc_dir = wc.realpath self.remember_disk(wc, disk) make_py = "expected_disk = svntest.main.greek_state.copy()\n" else: disk = self.prev_disk[1] return make_py, disk def remember_disk(self, wc, actual): "Remembers the current disk tree for future reference." self.prev_disk = [wc.realpath, actual] def get_current_status(self, wc, quiet=True): "Probes the given working copy and writes an expected_status for it." if quiet: code, output, err = main.run_svn(None, 'status', '-v', '-u', '-q', wc.realpath) else: code, output, err = main.run_svn(None, 'status', '-v', '-u', wc.realpath) if code != 0 or len(err) > 0: raise Failure("Hmm. `svn status' failed. What now.") make_py, prev_status = self.get_prev_status(wc) actual_status = svntest.wc.State.from_status(output) # The tests currently compare SVNTreeNode trees, so let's do that too. prev_status_tree = prev_status.old_tree() actual_status_tree = actual_status.old_tree() # Get the tweaks tweaks = self.diff_trees(prev_status_tree, actual_status_tree, wc) if tweaks == 'Purge': # The tree is empty (happens with invalid WC dirs) make_py = "expected_status = wc.State(" + wc.py + ", {})\n" tweaks = [] else: tweaks = self.optimize_tweaks(tweaks, actual_status_tree, wc) self.remember_status(wc, actual_status) pystatus = make_py + self.tweaks2py(tweaks, "expected_status", wc) if len(pystatus) > 0: pystatus += '\n' return pystatus def get_prev_status(self, wc): "Retrieves the last used expected_status tree if any." make_py = "" prev_status = None # re-use any previous status if we are still in the same WC dir. # If a status was supplied via __init__(), self.prev_status[0] is set # to None, in which case we always use it, not checking WC. if self.prev_status is None or \ not self.prev_status[0] in [None, wc.realpath]: # There is no or no matching previous status. Make new one. try: # If it's really a WC, use its base revision base_rev = actions.get_wc_base_rev(wc.realpath) except: # Else, just use zero. Whatever. base_rev = 0 prev_status = actions.get_virginal_state(wc.realpath, base_rev) make_py += ("expected_status = actions.get_virginal_state(" + wc.py + ", " + str(base_rev) + ")\n") else: # We will re-use the previous expected_status. prev_status = self.prev_status[1] # no need to make_py anything return make_py, prev_status def remember_status(self, wc, actual_status): "Remembers the current status tree for future reference." self.prev_status = [wc.realpath, actual_status] def chdir(self, do_chdir, wc): "Pushes the current dir onto the dir stack, does an os.chdir()." if not do_chdir: return "" self.prevdirs.append(os.getcwd()) os.chdir(wc.realpath) py = ("orig_dir = os.getcwd() # Need to chdir because of '^/' args\n" + "os.chdir(" + wc.py + ")\n") return py def chdir_back(self, do_chdir): "Does os.chdir() back to the directory popped from the dir stack's top." if not do_chdir: return "" # If this fails, there's a missing chdir() call: os.chdir(self.prevdirs.pop()) return "os.chdir(orig_dir)\n" def get_sorted_vars_by_pathlen(self): """Compose a listing of variable names to be expanded in script output. This is intended to be stored in self.sorted_vars_by_pathlen.""" lst = [] for dict in [self.vars, self.other_wc_dirs]: for name in dict: runpath = dict[name][1] if not runpath: continue strlen = len(runpath) item = [strlen, name, runpath] bisect.insort(lst, item) return lst def get_sorted_var_names(self): """Compose a listing of variable names to be declared. This is used by TestFactory.make().""" paths = [] urls = [] for name in self.vars: if name.startswith('url_'): bisect.insort(urls, [name.lower(), name]) else: bisect.insort(paths, [name.lower(), name]) list = [] for path in paths: list += [path[1]] for url in urls: list += [url[1]] return list def get_sorted_other_wc_dir_names(self): """Compose a listing of working copies to be declared with sbox. This is used by TestFactory.make().""" list = [] for name in self.other_wc_dirs: bisect.insort(list, [name.lower(), name]) names = [] for item in list: names += [item[1]] return names def str2svntest(self, str): "Like str2py(), but replaces any known paths with variable names." if str is None: return "None" str = str2py(str) quote = str[0] def replace(str, path, name, quote): return str.replace(path, quote + " + " + name + " + " + quote) # We want longer paths first. for var in reversed(self.sorted_vars_by_pathlen): name = var[1] path = var[2] str = replace(str, path, name, quote) str = replace(str, self.sbox.wc_dir, 'wc_dir', quote) str = replace(str, self.sbox.repo_url, 'url', quote) # now remove trailing null-str adds: # '' + url_A_C + '' str = str.replace("'' + ",'').replace(" + ''",'') # "" + url_A_C + "" str = str.replace('"" + ',"").replace(' + ""',"") # just a stupid check. tiny tweak. (don't declare wc_dir and url # if they never appear) if not self.used_wc_dir: self.used_wc_dir = (re.search('\bwc_dir\b', str) is not None) if not self.used_url: self.used_url = str.find('url') >= 0 return str def strlist2py(self, list): "Given a list of strings, composes a py script that produces the same." if list is None: return "None" if len(list) < 1: return "[]" if len(list) == 1: return "[" + self.str2svntest(list[0]) + "]" py = "[\n" for line in list: py += " " + self.str2svntest(line) + ",\n" py += "]" return py def get_node_path(self, node, wc): "Tries to return the node path relative to the given working copy." path = node.get_printable_path() if path.startswith(wc.realpath + os.sep): path = path[len(wc.realpath + os.sep):] elif path.startswith(wc.realpath): path = path[len(wc.realpath):] return path def node2py(self, node, wc, prepend="", drop_empties=True): "Creates a line like 'A/C' : Item({ ... }) for wc.State composition." buf = StringIO() node.print_script(buf, wc.realpath, prepend, drop_empties) return buf.getvalue() def tree2py(self, node, wc): "Writes the wc.State definition for the given SVNTreeNode in given WC." # svntest.wc.State(wc_dir, { # 'A/mu' : Item(verb='Sending'), # 'A/D/G/rho' : Item(verb='Sending'), # }) buf = StringIO() tree.dump_tree_script(node, stream=buf, subtree=wc.realpath, wc_varname=wc.py) return buf.getvalue() def diff_trees(self, left, right, wc): """Compares the two trees given by the SVNTreeNode instances LEFT and RIGHT in the given working copy and composes an internal list of tweaks necessary to make LEFT into RIGHT.""" if not right.children: return 'Purge' return self._diff_trees(left, right, wc) def _diff_trees(self, left, right, wc): "Used by self.diff_trees(). No need to call this. See there." # all tweaks collected tweaks = [] # the current tweak in composition path = self.get_node_path(left, wc) tweak = [] # node attributes if ((left.contents is None) != (right.contents is None)) or \ (left.contents != right.contents): tweak += [ ["contents", right.contents] ] for key in left.props: if key not in right.props: tweak += [ [key, None] ] elif left.props[key] != right.props[key]: tweak += [ [key, right.props[key]] ] for key in right.props: if key not in left.props: tweak += [ [key, right.props[key]] ] for key in left.atts: if key not in right.atts: tweak += [ [key, None] ] elif left.atts[key] != right.atts[key]: tweak += [ [key, right.atts[key]] ] for key in right.atts: if key not in left.atts: tweak += [ [key, right.atts[key]] ] if len(tweak) > 0: changetweak = [ 'Change', [path], tweak] tweaks += [changetweak] if left.children is not None: for leftchild in left.children: rightchild = None if right.children is not None: rightchild = tree.get_child(right, leftchild.name) if rightchild is None: paths = leftchild.recurse(lambda n: self.get_node_path(n, wc)) removetweak = [ 'Remove', paths ] tweaks += [removetweak] if right.children is not None: for rightchild in right.children: leftchild = None if left.children is not None: leftchild = tree.get_child(left, rightchild.name) if leftchild is None: paths_and_nodes = rightchild.recurse( lambda n: [ self.get_node_path(n, wc), n ] ) addtweak = [ 'Add', paths_and_nodes ] tweaks += [addtweak] else: tweaks += self._diff_trees(leftchild, rightchild, wc) return tweaks def optimize_tweaks(self, tweaks, actual_tree, wc): "Given an internal list of tweaks, make them optimal by common sense." if tweaks == 'Purge': return tweaks subtree = actual_tree.find_node(wc.realpath) if not subtree: subtree = actual_tree remove_paths = [] additions = [] changes = [] for tweak in tweaks: if tweak[0] == 'Remove': remove_paths += tweak[1] elif tweak[0] == 'Add': additions += tweak[1] else: changes += [tweak] # combine removals removal = [] if len(remove_paths) > 0: removal = [ [ 'Remove', remove_paths] ] # combine additions addition = [] if len(additions) > 0: addition = [ [ 'Add', additions ] ] # find those changes that should be done on all nodes at once. def remove_mod(mod): for change in changes: if mod in change[2]: change[2].remove(mod) seen = [] tweak_all = [] for change in changes: tweak = change[2] for mod in tweak: if mod in seen: continue seen += [mod] # here we see each single "name=value" tweak in mod. # Check if the actual tree had this anyway all the way through. name = mod[0] val = mod[1] if name == 'contents' and val is None: continue; def check_node(node): if ( (name == 'contents' and node.contents == val) or (node.props and (name in node.props) and node.props[name] == val) or (node.atts and (name in node.atts) and node.atts[name] == val)): # has this same thing set. count on the left. return [node, None] return [None, node] results = subtree.recurse(check_node) have = [] havent = [] for result in results: if result[0]: have += [result[0]] else: havent += [result[1]] if havent == []: # ok, then, remove all tweaks that are like this, then # add a generic tweak. remove_mod(mod) tweak_all += [mod] elif len(havent) < len(have) * 3: # this is "an empirical factor" remove_mod(mod) tweak_all += [mod] # record the *other* nodes' actual item, overwritten above for node in havent: name = mod[0] if name == 'contents': value = node.contents elif name in node.props: value = node.props[name] elif name in node.atts: value = node.atts[name] else: continue changes += [ ['Change', [self.get_node_path(node, wc)], [[name, value]] ] ] # combine those paths that have exactly the same changes i = 0 j = 0 while i < len(changes): # find other changes that are identical j = i + 1 while j < len(changes): if changes[i][2] == changes[j][2]: changes[i][1] += changes[j][1] del changes[j] else: j += 1 i += 1 # combine those changes that have exactly the same paths i = 0 j = 0 while i < len(changes): # find other paths that are identical j = i + 1 while j < len(changes): if changes[i][1] == changes[j][1]: changes[i][2] += changes[j][2] del changes[j] else: j += 1 i += 1 if tweak_all != []: changes = [ ['Change', [], tweak_all ] ] + changes return removal + addition + changes def tweaks2py(self, tweaks, var_name, wc): "Given an internal list of tweaks, write the tweak script for it." py = "" if tweaks is None: return "" if tweaks == 'Purge': return var_name + " = wc.State(" + wc.py + ", {})\n" for tweak in tweaks: if tweak[0] == 'Remove': py += var_name + ".remove(" paths = tweak[1] py += self.str2svntest(paths[0]) for path in paths[1:]: py += ", " + self.str2svntest(path) py += ")\n" elif tweak[0] == 'Add': # add({'A/D/H/zeta' : Item(status=' ', wc_rev=9), ...}) py += var_name + ".add({" adds = tweak[1] for add in adds: path = add[0] node = add[1] py += self.node2py(node, wc, "\n ", False) py += "\n})\n" else: paths = tweak[1] mods = tweak[2] if mods != []: py += var_name + ".tweak(" for path in paths: py += self.str2svntest(path) + ", " def mod2py(mod): return mod[0] + "=" + self.str2svntest(mod[1]) py += mod2py(mods[0]) for mod in mods[1:]: py += ", " + mod2py(mod) py += ")\n" return py def path2svntest(self, path, argnr=None, do_remove_on_new_wc_path=True): """Given an input argument, do one hell of a path expansion on it. ARGNR is simply inserted into the resulting Target. Returns a self.Target instance. """ wc = self.WorkingCopy('wc_dir', self.sbox.wc_dir, None) url = self.sbox.repo_url # do we need multiple URLs too?? pathsep = '/' if path.find('/') < 0 and path.find('\\') >= 0: pathsep = '\\' is_url = False # If you add to these, make sure you add longer ones first, to # avoid e.g. '$WC_DIR' matching '$WC' first. wc_dir_wildcards = ['wc_dir', 'wcdir', '$WC_DIR', '$WC'] url_wildcards = ['url', '$URL'] first = path.split(pathsep, 1)[0] if first in wc_dir_wildcards: path = path[len(first):] elif first in url_wildcards: path = path[len(first):] is_url = True else: for url_scheme in ['^/', 'file:/', 'http:/', 'svn:/', 'svn+ssh:/']: if path.startswith(url_scheme): is_url = True # keep it as it is pyarg = self.str2svntest(path) runarg = path return self.Target(pyarg, runarg, argnr, is_url, None) for wc_dir_wildcard in wc_dir_wildcards: if first.startswith(wc_dir_wildcard): # The first path element starts with "wc_dir" (or similar), # but it has more attached to it. Like "wc_dir.2" or "wc_dir_other" # Record a new wc dir name. # try to figure out a nice suffix to pass to sbox. # (it will create a new dir called sbox.wc_dir + '.' + suffix) suffix = '' if first[len(wc_dir_wildcard)] in ['.','-','_']: # it's a separator already, don't duplicate the dot. (warm&fuzzy) suffix = first[len(wc_dir_wildcard) + 1:] if len(suffix) < 1: suffix = first[len(wc_dir_wildcard):] if len(suffix) < 1: raise Failure("no suffix supplied to other-wc_dir arg") # Streamline the var name suffix = suffix.replace('.','_').replace('-','_') other_wc_dir_varname = 'wc_dir_' + suffix path = path[len(first):] real_path = self.get_other_wc_real_path(other_wc_dir_varname, suffix, do_remove_on_new_wc_path) wc = self.WorkingCopy(other_wc_dir_varname, real_path, suffix) # found a match, no need to loop further, but still process # the path further. break if len(path) < 1 or path == pathsep: if is_url: self.used_url = True pyarg = 'url' runarg = url wc = None else: if wc.suffix is None: self.used_wc_dir = True pyarg = wc.py runarg = wc.realpath else: pathelements = split_remove_empty(path, pathsep) # make a new variable, if necessary if is_url: pyarg, runarg = self.ensure_url_var(pathelements) wc = None else: pyarg, runarg = self.ensure_path_var(wc, pathelements) return self.Target(pyarg, runarg, argnr, is_url, wc) def get_other_wc_real_path(self, varname, suffix, do_remove): "Create or retrieve the path of an alternate working copy." if varname in self.other_wc_dirs: return self.other_wc_dirs[varname][1] # see if there is a wc already in the sbox path = self.sbox.wc_dir + '.' + suffix if path in self.sbox.test_paths: py = "sbox.wc_dir + '." + suffix + "'" else: # else, we must still create one. path = self.sbox.add_wc_path(suffix, do_remove) py = "sbox.add_wc_path(" + str2py(suffix) if not do_remove: py += ", remove=False" py += ')' value = [py, path] self.other_wc_dirs[varname] = [py, path] self.sorted_vars_by_pathlen = self.get_sorted_vars_by_pathlen() return path def define_var(self, name, value): "Add a variable definition, don't allow redefinitions." # see if we already have this var if name in self.vars: if self.vars[name] != value: raise Failure("Variable name collision. Hm, fix factory.py?") # ok, it's recorded correctly. Nothing needs to happen. return # a new variable needs to be recorded self.vars[name] = value # update the sorted list of vars for substitution by str2svntest() self.sorted_vars_by_pathlen = self.get_sorted_vars_by_pathlen() def ensure_path_var(self, wc, pathelements): "Given a path in a working copy, make sure we have a variable for it." # special case: if a path is '.', simply use wc_dir. if pathelements == ['.']: return wc.py, wc.realpath name = "_".join(pathelements) if wc.suffix is not None: # This is an "other" working copy (not the default). # The suffix of the wc_dir variable serves as the prefix: # wc_dir_other ==> other_A_D = os.path.join(wc_dir_other, 'A', 'D') name = wc.suffix + "_" + name if name[0].isdigit(): name = "_" + name else: self.used_wc_dir = True py = 'os.path.join(' + wc.py if len(pathelements) > 0: py += ", '" + "', '".join(pathelements) + "'" py += ')' wc_dir_real_path = wc.realpath run = os.path.join(wc_dir_real_path, *pathelements) value = [py, run] self.define_var(name, value) return name, run def ensure_url_var(self, pathelements): "Given a path in the test repository, ensure we have a url var for it." name = "url_" + "_".join(pathelements) joined = "/" + "/".join(pathelements) py = 'url' if len(pathelements) > 0: py += " + " + str2py(joined) self.used_url = True run = self.sbox.repo_url + joined value = [py, run] self.define_var(name, value) return name, run def get_first_wc(self, target_list): """In a list of Target instances, find the first one that is in a working copy and return that WorkingCopy. Default to sbox.wc_dir. This is useful if we need a working copy for a '^/' URL.""" for target in target_list: if target.wc: return target.wc return self.WorkingCopy('wc_dir', self.sbox.wc_dir, None) def args2svntest(self, args, append_wc_dir_if_missing = False, keep_args_of = [], keep_first_count = 1, drop_with_arg = []): """Tries to be extremely intelligent at parsing command line arguments. It needs to know which args are file targets that should be in a working copy. File targets are magically expanded. args: list of string tokens as passed to factory.make(), e.g. ['svn', 'commit', '--force', 'wc_dir2'] append_wc_dir_if_missing: It's a switch. keep_args_of: See TestFactory.keep_args_of (comment in __init__) keep_first_count: Don't expand the first N non-option args. This is used to preserve e.g. the token 'update' in '[svn] update wc_dir' (the 'svn' is usually split off before this function is called). drop_with_arg: list of string tokens that are commandline options with following argument which we want to drop from the list of args (e.g. -m message). """ wc_dir = self.sbox.wc_dir url = self.sbox.repo_url target_supplied = False pyargs = [] runargs = [] do_chdir = False targets = [] wc_dirs = [] i = 0 while i < len(args): arg = args[i] if arg in drop_with_arg: # skip this and the next arg if not arg.startswith('--') and len(arg) > 2: # it is a concatenated arg like -r123 instead of -r 123 # skip only this one. Do nothing. i = i else: # skip this and the next arg i += 1 elif arg.startswith('-'): # keep this option arg verbatim. pyargs += [ self.str2svntest(arg) ] runargs += [ arg ] # does this option expect a non-filename argument? # take that verbatim as well. if arg in keep_args_of: i += 1 if i < len(args): arg = args[i] pyargs += [ self.str2svntest(arg) ] runargs += [ arg ] elif keep_first_count > 0: # args still to be taken verbatim. pyargs += [ self.str2svntest(arg) ] runargs += [ arg ] keep_first_count -= 1 elif arg.startswith('^/'): # this is a ^/url, keep it verbatim. # if we use "^/", we need to chdir(wc_dir). do_chdir = True pyarg = str2py(arg) targets += [ self.Target(pyarg, arg, len(pyargs), True, None) ] pyargs += [ pyarg ] runargs += [ arg ] else: # well, then this must be a filename or url, autoexpand it. target = self.path2svntest(arg, argnr=len(pyargs)) pyargs += [ target.pyarg ] runargs += [ target.runarg ] target_supplied = True targets += [ target ] i += 1 if not target_supplied and append_wc_dir_if_missing: # add a simple wc_dir target self.used_wc_dir = True wc = self.WorkingCopy('wc_dir', wc_dir, None) targets += [ self.Target('wc_dir', wc_dir, len(pyargs), False, wc) ] pyargs += [ 'wc_dir' ] runargs += [ wc_dir ] return pyargs, runargs, do_chdir, targets ###### END of the TestFactory class ###### # Quotes-preserving text wrapping for output def find_quote_end(text, i): "In string TEXT, find the end of the qoute that starts at TEXT[i]" # don't handle """ quotes quote = text[i] i += 1 while i < len(text): if text[i] == '\\': i += 1 elif text[i] == quote: return i i += 1 return len(text) - 1 class MyWrapper(textwrap.TextWrapper): "A textwrap.TextWrapper that doesn't break a line within quotes." ### TODO regexes would be nice, maybe? def _split(self, text): parts = [] i = 0 start = 0 # This loop will break before and after each space, but keep # quoted strings in one piece. Example, breaks marked '/': # /(one,/ /two(blagger),/ /'three three three',)/ while i < len(text): if text[i] in ['"', "'"]: # handle """ quotes. (why, actually?) if text[i:i+3] == '"""': end = text[i+3:].find('"""') if end >= 0: i += end + 2 else: i = len(text) - 1 else: # handle normal quotes i = find_quote_end(text, i) elif text[i].isspace(): # split off previous section, if any if start < i: parts += [text[start:i]] start = i # split off this space parts += [text[i]] start = i + 1 i += 1 if start < len(text): parts += [text[start:]] return parts def wrap_each_line(str, ii, si, blw): """Wrap lines to a defined width (<80 chars). Feed the lines single to MyWrapper, so that it preserves the current line endings already in there. We only want to insert new wraps, not remove existing newlines.""" wrapper = MyWrapper(77, initial_indent=ii, subsequent_indent=si) lines = str.splitlines() for i in range(0,len(lines)): if lines[i] != '': lines[i] = wrapper.fill(lines[i]) return '\n'.join(lines) # Other miscellaneous helpers def sh2str(string): "un-escapes away /x sequences" if string is None: return None return string.decode("string-escape") def get_quote_style(str): """find which quote is the outer one, ' or ".""" quote_char = None at = None found = str.find("'") found2 = str.find('"') # If found == found2, both must be -1, so nothing was found. if found != found2: # If a quote was found if found >= 0 and found2 >= 0: # If both were found, invalidate the later one if found < found2: found2 = -1 else: found = -1 # See which one remains. if found >= 0: at = found + 1 quote_char = "'" elif found2 >= 0: at = found2 + 1 quote_char = '"' return quote_char, at def split_remove_empty(str, sep): "do a split, then remove empty elements." list = str.split(sep) return filter(lambda item: item and len(item) > 0, list) def str2py(str): "returns the string enclosed in quotes, suitable for py scripts." if str is None: return "None" # try to make a nice choice of quoting character if str.find("'") >= 0: return '"' + str.encode("string-escape" ).replace("\\'", "'" ).replace('"', '\\"') + '"' else: return "'" + str.encode("string-escape") + "'" return str ### End of file. cvs2svn-2.5.0/svntest/verify.py0000664000175100017510000004762212203665123017573 0ustar mhaggermhagger00000000000000# # verify.py: routines that handle comparison and display of expected # vs. actual output # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import re, sys from difflib import unified_diff, ndiff import pprint import logging import svntest logger = logging.getLogger() ###################################################################### # Exception types class SVNUnexpectedOutput(svntest.Failure): """Exception raised if an invocation of svn results in unexpected output of any kind.""" pass class SVNUnexpectedStdout(SVNUnexpectedOutput): """Exception raised if an invocation of svn results in unexpected output on STDOUT.""" pass class SVNUnexpectedStderr(SVNUnexpectedOutput): """Exception raised if an invocation of svn results in unexpected output on STDERR.""" pass class SVNExpectedStdout(SVNUnexpectedOutput): """Exception raised if an invocation of svn results in no output on STDOUT when output was expected.""" pass class SVNExpectedStderr(SVNUnexpectedOutput): """Exception raised if an invocation of svn results in no output on STDERR when output was expected.""" pass class SVNUnexpectedExitCode(SVNUnexpectedOutput): """Exception raised if an invocation of svn exits with a value other than what was expected.""" pass class SVNIncorrectDatatype(SVNUnexpectedOutput): """Exception raised if invalid input is passed to the run_and_verify_* API""" pass class SVNDumpParseError(svntest.Failure): """Exception raised if parsing a dump file fails""" pass ###################################################################### # Comparison of expected vs. actual output def createExpectedOutput(expected, output_type, match_all=True): """Return EXPECTED, promoted to an ExpectedOutput instance if not None. Raise SVNIncorrectDatatype if the data type of EXPECTED is not handled.""" if isinstance(expected, list): expected = ExpectedOutput(expected) elif isinstance(expected, str): expected = RegexOutput(expected, match_all) elif isinstance(expected, int): expected = RegexOutput(".*: E%d:.*" % expected, False) elif expected is AnyOutput: expected = AnyOutput() elif expected is not None and not isinstance(expected, ExpectedOutput): raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type) return expected class ExpectedOutput: """Contains expected output, and performs comparisons.""" is_regex = False is_unordered = False def __init__(self, output, match_all=True): """Initialize the expected output to OUTPUT which is a string, or a list of strings, or None meaning an empty list. If MATCH_ALL is True, the expected strings will be matched with the actual strings, one-to-one, in the same order. If False, they will be matched with a subset of the actual strings, one-to-one, in the same order, ignoring any other actual strings among the matching ones.""" self.output = output self.match_all = match_all def __str__(self): return str(self.output) def __cmp__(self, other): raise Exception('badness') def matches(self, other, except_re=None): """Return whether SELF.output matches OTHER (which may be a list of newline-terminated lines, or a single string). Either value may be None.""" if self.output is None: expected = [] else: expected = self.output if other is None: actual = [] else: actual = other if not isinstance(actual, list): actual = [actual] if not isinstance(expected, list): expected = [expected] if except_re: return self.matches_except(expected, actual, except_re) else: return self.is_equivalent_list(expected, actual) def matches_except(self, expected, actual, except_re): "Return whether EXPECTED and ACTUAL match except for except_re." if not self.is_regex: i_expected = 0 i_actual = 0 while i_expected < len(expected) and i_actual < len(actual): if re.match(except_re, actual[i_actual]): i_actual += 1 elif re.match(except_re, expected[i_expected]): i_expected += 1 elif expected[i_expected] == actual[i_actual]: i_expected += 1 i_actual += 1 else: return False if i_expected == len(expected) and i_actual == len(actual): return True return False else: raise Exception("is_regex and except_re are mutually exclusive") def is_equivalent_list(self, expected, actual): "Return whether EXPECTED and ACTUAL are equivalent." if not self.is_regex: if self.match_all: # The EXPECTED lines must match the ACTUAL lines, one-to-one, in # the same order. return expected == actual # The EXPECTED lines must match a subset of the ACTUAL lines, # one-to-one, in the same order, with zero or more other ACTUAL # lines interspersed among the matching ACTUAL lines. i_expected = 0 for actual_line in actual: if expected[i_expected] == actual_line: i_expected += 1 if i_expected == len(expected): return True return False expected_re = expected[0] # If we want to check that every line matches the regexp # assume they all match and look for any that don't. If # only one line matching the regexp is enough, assume none # match and look for even one that does. if self.match_all: all_lines_match_re = True else: all_lines_match_re = False # If a regex was provided assume that we actually require # some output. Fail if we don't have any. if len(actual) == 0: return False for actual_line in actual: if self.match_all: if not re.match(expected_re, actual_line): return False else: # As soon an actual_line matches something, then we're good. if re.match(expected_re, actual_line): return True return all_lines_match_re def display_differences(self, message, label, actual): """Delegate to the display_lines() routine with the appropriate args. MESSAGE is ignored if None.""" display_lines(message, label, self.output, actual, self.is_regex, self.is_unordered) class AnyOutput(ExpectedOutput): def __init__(self): ExpectedOutput.__init__(self, None, False) def is_equivalent_list(self, ignored, actual): if len(actual) == 0: # No actual output. No match. return False for line in actual: # If any line has some text, then there is output, so we match. if line: return True # We did not find a line with text. No match. return False def display_differences(self, message, label, actual): if message: logger.warn(message) class RegexOutput(ExpectedOutput): is_regex = True class UnorderedOutput(ExpectedOutput): """Marks unordered output, and performs comparisons.""" is_unordered = True def __cmp__(self, other): raise Exception('badness') def matches_except(self, expected, actual, except_re): assert type(actual) == type([]) # ### if this trips: fix it! return self.is_equivalent_list([l for l in expected if not except_re.match(l)], [l for l in actual if not except_re.match(l)]) def is_equivalent_list(self, expected, actual): "Disregard the order of ACTUAL lines during comparison." e_set = set(expected) a_set = set(actual) if self.match_all: if len(e_set) != len(a_set): return False if self.is_regex: for expect_re in e_set: for actual_line in a_set: if re.match(expect_re, actual_line): a_set.remove(actual_line) break else: # One of the regexes was not found return False return True # All expected lines must be in the output. return e_set == a_set if self.is_regex: # If any of the expected regexes are in the output, then we match. for expect_re in e_set: for actual_line in a_set: if re.match(expect_re, actual_line): return True return False # If any of the expected lines are in the output, then we match. return len(e_set.intersection(a_set)) > 0 class UnorderedRegexOutput(UnorderedOutput, RegexOutput): is_regex = True is_unordered = True ###################################################################### # Displaying expected and actual output def display_trees(message, label, expected, actual): 'Print two trees, expected and actual.' if message is not None: logger.warn(message) if expected is not None: logger.warn('EXPECTED %s:', label) svntest.tree.dump_tree(expected) if actual is not None: logger.warn('ACTUAL %s:', label) svntest.tree.dump_tree(actual) def display_lines(message, label, expected, actual, expected_is_regexp=None, expected_is_unordered=None): """Print MESSAGE, unless it is None, then print EXPECTED (labeled with LABEL) followed by ACTUAL (also labeled with LABEL). Both EXPECTED and ACTUAL may be strings or lists of strings.""" if message is not None: logger.warn(message) if expected is not None: output = 'EXPECTED %s' % label if expected_is_regexp: output += ' (regexp)' expected = [expected + '\n'] if expected_is_unordered: output += ' (unordered)' output += ':' logger.warn(output) for x in expected: sys.stdout.write(x) if actual is not None: logger.warn('ACTUAL %s:', label) for x in actual: sys.stdout.write(x) # Additionally print unified diff if not expected_is_regexp: logger.warn('DIFF ' + ' '.join(output.split(' ')[1:])) if type(expected) is str: expected = [expected] if type(actual) is str: actual = [actual] for x in unified_diff(expected, actual, fromfile="EXPECTED %s" % label, tofile="ACTUAL %s" % label): sys.stdout.write(x) def compare_and_display_lines(message, label, expected, actual, raisable=None, except_re=None): """Compare two sets of output lines, and print them if they differ, preceded by MESSAGE iff not None. EXPECTED may be an instance of ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an exception class, an instance of which is thrown if ACTUAL doesn't match EXPECTED.""" if raisable is None: raisable = svntest.main.SVNLineUnequal ### It'd be nicer to use createExpectedOutput() here, but its ### semantics don't match all current consumers of this function. if not isinstance(expected, ExpectedOutput): expected = ExpectedOutput(expected) if isinstance(actual, str): actual = [actual] actual = [line for line in actual if not line.startswith('DBG:')] if not expected.matches(actual, except_re): expected.display_differences(message, label, actual) raise raisable def verify_outputs(message, actual_stdout, actual_stderr, expected_stdout, expected_stderr, all_stdout=True): """Compare and display expected vs. actual stderr and stdout lines: if they don't match, print the difference (preceded by MESSAGE iff not None) and raise an exception. If EXPECTED_STDERR or EXPECTED_STDOUT is a string the string is interpreted as a regular expression. For EXPECTED_STDOUT and ACTUAL_STDOUT to match, every line in ACTUAL_STDOUT must match the EXPECTED_STDOUT regex, unless ALL_STDOUT is false. For EXPECTED_STDERR regexes only one line in ACTUAL_STDERR need match.""" expected_stderr = createExpectedOutput(expected_stderr, 'stderr', False) expected_stdout = createExpectedOutput(expected_stdout, 'stdout', all_stdout) for (actual, expected, label, raisable) in ( (actual_stderr, expected_stderr, 'STDERR', SVNExpectedStderr), (actual_stdout, expected_stdout, 'STDOUT', SVNExpectedStdout)): if expected is None: continue if isinstance(expected, RegexOutput): raisable = svntest.main.SVNUnmatchedError elif not isinstance(expected, AnyOutput): raisable = svntest.main.SVNLineUnequal compare_and_display_lines(message, label, expected, actual, raisable) def verify_exit_code(message, actual, expected, raisable=SVNUnexpectedExitCode): """Compare and display expected vs. actual exit codes: if they don't match, print the difference (preceded by MESSAGE iff not None) and raise an exception.""" if expected != actual: display_lines(message, "Exit Code", str(expected) + '\n', str(actual) + '\n') raise raisable # A simple dump file parser. While sufficient for the current # testsuite it doesn't cope with all valid dump files. class DumpParser: def __init__(self, lines): self.current = 0 self.lines = lines self.parsed = {} def parse_line(self, regex, required=True): m = re.match(regex, self.lines[self.current]) if not m: if required: raise SVNDumpParseError("expected '%s' at line %d\n%s" % (regex, self.current, self.lines[self.current])) else: return None self.current += 1 return m.group(1) def parse_blank(self, required=True): if self.lines[self.current] != '\n': # Works on Windows if required: raise SVNDumpParseError("expected blank at line %d\n%s" % (self.current, self.lines[self.current])) else: return False self.current += 1 return True def parse_format(self): return self.parse_line('SVN-fs-dump-format-version: ([0-9]+)$') def parse_uuid(self): return self.parse_line('UUID: ([0-9a-z-]+)$') def parse_revision(self): return self.parse_line('Revision-number: ([0-9]+)$') def parse_prop_length(self, required=True): return self.parse_line('Prop-content-length: ([0-9]+)$', required) def parse_content_length(self, required=True): return self.parse_line('Content-length: ([0-9]+)$', required) def parse_path(self): path = self.parse_line('Node-path: (.+)$', required=False) if not path and self.lines[self.current] == 'Node-path: \n': self.current += 1 path = '' return path def parse_kind(self): return self.parse_line('Node-kind: (.+)$', required=False) def parse_action(self): return self.parse_line('Node-action: ([0-9a-z-]+)$') def parse_copyfrom_rev(self): return self.parse_line('Node-copyfrom-rev: ([0-9]+)$', required=False) def parse_copyfrom_path(self): path = self.parse_line('Node-copyfrom-path: (.+)$', required=False) if not path and self.lines[self.current] == 'Node-copyfrom-path: \n': self.current += 1 path = '' return path def parse_copy_md5(self): return self.parse_line('Text-copy-source-md5: ([0-9a-z]+)$', required=False) def parse_copy_sha1(self): return self.parse_line('Text-copy-source-sha1: ([0-9a-z]+)$', required=False) def parse_text_md5(self): return self.parse_line('Text-content-md5: ([0-9a-z]+)$', required=False) def parse_text_sha1(self): return self.parse_line('Text-content-sha1: ([0-9a-z]+)$', required=False) def parse_text_length(self): return self.parse_line('Text-content-length: ([0-9]+)$', required=False) # One day we may need to parse individual property name/values into a map def get_props(self): props = [] while not re.match('PROPS-END$', self.lines[self.current]): props.append(self.lines[self.current]) self.current += 1 self.current += 1 return props def get_content(self, length): content = '' while len(content) < length: content += self.lines[self.current] self.current += 1 if len(content) == length + 1: content = content[:-1] elif len(content) != length: raise SVNDumpParseError("content length expected %d actual %d at line %d" % (length, len(content), self.current)) return content def parse_one_node(self): node = {} node['kind'] = self.parse_kind() action = self.parse_action() node['copyfrom_rev'] = self.parse_copyfrom_rev() node['copyfrom_path'] = self.parse_copyfrom_path() node['copy_md5'] = self.parse_copy_md5() node['copy_sha1'] = self.parse_copy_sha1() node['prop_length'] = self.parse_prop_length(required=False) node['text_length'] = self.parse_text_length() node['text_md5'] = self.parse_text_md5() node['text_sha1'] = self.parse_text_sha1() node['content_length'] = self.parse_content_length(required=False) self.parse_blank() if node['prop_length']: node['props'] = self.get_props() if node['text_length']: node['content'] = self.get_content(int(node['text_length'])) # Hard to determine how may blanks is 'correct' (a delete that is # followed by an add that is a replace and a copy has one fewer # than expected but that can't be predicted until seeing the add) # so allow arbitrary number blanks = 0 while self.current < len(self.lines) and self.parse_blank(required=False): blanks += 1 node['blanks'] = blanks return action, node def parse_all_nodes(self): nodes = {} while True: if self.current >= len(self.lines): break path = self.parse_path() if not path and not path is '': break if not nodes.get(path): nodes[path] = {} action, node = self.parse_one_node() if nodes[path].get(action): raise SVNDumpParseError("duplicate action '%s' for node '%s' at line %d" % (action, path, self.current)) nodes[path][action] = node return nodes def parse_one_revision(self): revision = {} number = self.parse_revision() revision['prop_length'] = self.parse_prop_length() revision['content_length'] = self.parse_content_length() self.parse_blank() revision['props'] = self.get_props() self.parse_blank() revision['nodes'] = self.parse_all_nodes() return number, revision def parse_all_revisions(self): while self.current < len(self.lines): number, revision = self.parse_one_revision() if self.parsed.get(number): raise SVNDumpParseError("duplicate revision %d at line %d" % (number, self.current)) self.parsed[number] = revision def parse(self): self.parsed['format'] = self.parse_format() self.parse_blank() self.parsed['uuid'] = self.parse_uuid() self.parse_blank() self.parse_all_revisions() return self.parsed def compare_dump_files(message, label, expected, actual): """Parse two dump files EXPECTED and ACTUAL, both of which are lists of lines as returned by run_and_verify_dump, and check that the same revisions, nodes, properties, etc. are present in both dumps. """ parsed_expected = DumpParser(expected).parse() parsed_actual = DumpParser(actual).parse() if parsed_expected != parsed_actual: raise svntest.Failure('\n' + '\n'.join(ndiff( pprint.pformat(parsed_expected).splitlines(), pprint.pformat(parsed_actual).splitlines()))) cvs2svn-2.5.0/svntest/err.py0000664000175100017510000002036412203665123017051 0ustar mhaggermhagger00000000000000#!/usr/bin/env python ### This file automatically generated by tools/dev/gen-py-error.py, ### which see for more information ### ### It is versioned for convenience. APMOD_ACTIVITY_NOT_FOUND = 190002 APMOD_BAD_BASELINE = 190003 APMOD_CONNECTION_ABORTED = 190004 APMOD_MALFORMED_URI = 190001 APMOD_MISSING_PATH_TO_FS = 190000 ASSERTION_FAIL = 235000 ASSERTION_ONLY_TRACING_LINKS = 235001 ATOMIC_INIT_FAILURE = 200029 AUTHN_CREDS_NOT_SAVED = 215003 AUTHN_CREDS_UNAVAILABLE = 215000 AUTHN_FAILED = 215004 AUTHN_NO_PROVIDER = 215001 AUTHN_PROVIDERS_EXHAUSTED = 215002 AUTHZ_INVALID_CONFIG = 220003 AUTHZ_PARTIALLY_READABLE = 220002 AUTHZ_ROOT_UNREADABLE = 220000 AUTHZ_UNREADABLE = 220001 AUTHZ_UNWRITABLE = 220004 BAD_ATOMIC = 125015 BAD_CHANGELIST_NAME = 125014 BAD_CHECKSUM_KIND = 125011 BAD_CHECKSUM_PARSE = 125012 BAD_CONFIG_VALUE = 125009 BAD_CONTAINING_POOL = 125000 BAD_DATE = 125003 BAD_FILENAME = 125001 BAD_MIME_TYPE = 125004 BAD_PROPERTY_VALUE = 125005 BAD_PROP_KIND = 200008 BAD_RELATIVE_PATH = 125007 BAD_SERVER_SPECIFICATION = 125010 BAD_TOKEN = 125013 BAD_URL = 125002 BAD_UUID = 125008 BAD_VERSION_FILE_FORMAT = 125006 BASE = 200000 CANCELLED = 200015 CEASE_INVOCATION = 200021 CHECKSUM_MISMATCH = 200014 CLIENT_BAD_REVISION = 195002 CLIENT_CYCLE_DETECTED = 195019 CLIENT_DUPLICATE_COMMIT_URL = 195003 CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED = 195017 CLIENT_FORBIDDEN_BY_SERVER = 195023 CLIENT_INVALID_EXTERNALS_DESCRIPTION = 195005 CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING = 195021 CLIENT_INVALID_RELOCATION = 195009 CLIENT_IS_BINARY_FILE = 195004 CLIENT_IS_DIRECTORY = 195007 CLIENT_MERGE_UPDATE_REQUIRED = 195020 CLIENT_MISSING_LOCK_TOKEN = 195013 CLIENT_MODIFIED = 195006 CLIENT_MULTIPLE_SOURCES_DISALLOWED = 195014 CLIENT_NOT_READY_TO_MERGE = 195016 CLIENT_NO_LOCK_TOKEN = 195022 CLIENT_NO_VERSIONED_PARENT = 195015 CLIENT_PATCH_BAD_STRIP_COUNT = 195018 CLIENT_PROPERTY_NAME = 195011 CLIENT_RA_ACCESS_REQUIRED = 195001 CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE = 195010 CLIENT_REVISION_RANGE = 195008 CLIENT_UNRELATED_RESOURCES = 195012 CLIENT_VERSIONED_PATH_REQUIRED = 195000 CL_ADM_DIR_RESERVED = 205003 CL_ARG_PARSING_ERROR = 205000 CL_BAD_LOG_MESSAGE = 205008 CL_COMMIT_IN_ADDED_DIR = 205006 CL_ERROR_PROCESSING_EXTERNALS = 205011 CL_INSUFFICIENT_ARGS = 205001 CL_LOG_MESSAGE_IS_PATHNAME = 205005 CL_LOG_MESSAGE_IS_VERSIONED_FILE = 205004 CL_MUTUALLY_EXCLUSIVE_ARGS = 205002 CL_NO_EXTERNAL_EDITOR = 205007 CL_NO_EXTERNAL_MERGE_TOOL = 205010 CL_UNNECESSARY_LOG_MESSAGE = 205009 DELTA_MD5_CHECKSUM_ABSENT = 200010 DIFF_DATASOURCE_MODIFIED = 225000 DIR_NOT_EMPTY = 200011 ENTRY_ATTRIBUTE_INVALID = 150005 ENTRY_EXISTS = 150002 ENTRY_FORBIDDEN = 150006 ENTRY_MISSING_REVISION = 150003 ENTRY_MISSING_URL = 150004 ENTRY_NOT_FOUND = 150000 EXTERNAL_PROGRAM = 200012 FS_ALREADY_EXISTS = 160020 FS_ALREADY_OPEN = 160002 FS_BAD_LOCK_TOKEN = 160037 FS_BERKELEY_DB = 160029 FS_BERKELEY_DB_DEADLOCK = 160030 FS_CLEANUP = 160001 FS_CONFLICT = 160024 FS_CORRUPT = 160004 FS_GENERAL = 160000 FS_ID_NOT_FOUND = 160014 FS_INCORRECT_EDITOR_COMPLETION = 160050 FS_LOCK_EXPIRED = 160041 FS_LOCK_OWNER_MISMATCH = 160039 FS_MALFORMED_SKEL = 160027 FS_NOT_DIRECTORY = 160016 FS_NOT_FILE = 160017 FS_NOT_FOUND = 160013 FS_NOT_ID = 160015 FS_NOT_MUTABLE = 160019 FS_NOT_OPEN = 160003 FS_NOT_REVISION_ROOT = 160023 FS_NOT_SINGLE_PATH_COMPONENT = 160018 FS_NOT_TXN_ROOT = 160022 FS_NO_LOCK_TOKEN = 160038 FS_NO_SUCH_CHECKSUM_REP = 160048 FS_NO_SUCH_COPY = 160011 FS_NO_SUCH_ENTRY = 160008 FS_NO_SUCH_LOCK = 160040 FS_NO_SUCH_NODE_ORIGIN = 160046 FS_NO_SUCH_REPRESENTATION = 160009 FS_NO_SUCH_REVISION = 160006 FS_NO_SUCH_STRING = 160010 FS_NO_SUCH_TRANSACTION = 160007 FS_NO_USER = 160034 FS_OUT_OF_DATE = 160042 FS_PATH_ALREADY_LOCKED = 160035 FS_PATH_NOT_LOCKED = 160036 FS_PATH_SYNTAX = 160005 FS_PROP_BASEVALUE_MISMATCH = 160049 FS_REP_BEING_WRITTEN = 160044 FS_REP_CHANGED = 160025 FS_REP_NOT_MUTABLE = 160026 FS_ROOT_DIR = 160021 FS_TRANSACTION_DEAD = 160031 FS_TRANSACTION_NOT_DEAD = 160032 FS_TRANSACTION_NOT_MUTABLE = 160012 FS_TXN_NAME_TOO_LONG = 160045 FS_TXN_OUT_OF_DATE = 160028 FS_UNKNOWN_FS_TYPE = 160033 FS_UNSUPPORTED_FORMAT = 160043 FS_UNSUPPORTED_UPGRADE = 160047 ILLEGAL_TARGET = 200009 INCOMPLETE_DATA = 200003 INCORRECT_PARAMS = 200004 INVALID_DIFF_OPTION = 200016 IO_CORRUPT_EOL = 135002 IO_INCONSISTENT_EOL = 135000 IO_PIPE_FRAME_ERROR = 135004 IO_PIPE_READ_ERROR = 135005 IO_PIPE_WRITE_ERROR = 135007 IO_UNIQUE_NAMES_EXHAUSTED = 135003 IO_UNKNOWN_EOL = 135001 IO_WRITE_ERROR = 135006 ITER_BREAK = 200023 MALFORMED_FILE = 200002 MERGEINFO_PARSE_ERROR = 200020 NODE_UNEXPECTED_KIND = 145001 NODE_UNKNOWN_KIND = 145000 NO_APR_MEMCACHE = 200028 NO_AUTH_FILE_PATH = 200018 PLUGIN_LOAD_FAILURE = 200001 PROPERTY_NOT_FOUND = 200017 RA_DAV_ALREADY_EXISTS = 175005 RA_DAV_CONN_TIMEOUT = 175012 RA_DAV_CREATING_REQUEST = 175001 RA_DAV_FORBIDDEN = 175013 RA_DAV_INVALID_CONFIG_VALUE = 175006 RA_DAV_MALFORMED_DATA = 175009 RA_DAV_OPTIONS_REQ_FAILED = 175003 RA_DAV_PATH_NOT_FOUND = 175007 RA_DAV_PROPPATCH_FAILED = 175008 RA_DAV_PROPS_NOT_FOUND = 175004 RA_DAV_RELOCATED = 175011 RA_DAV_REQUEST_FAILED = 175002 RA_DAV_RESPONSE_HEADER_BADNESS = 175010 RA_DAV_SOCK_INIT = 175000 RA_ILLEGAL_URL = 170000 RA_LOCAL_REPOS_NOT_FOUND = 180000 RA_LOCAL_REPOS_OPEN_FAILED = 180001 RA_NOT_AUTHORIZED = 170001 RA_NOT_IMPLEMENTED = 170003 RA_NOT_LOCKED = 170007 RA_NO_REPOS_UUID = 170005 RA_OUT_OF_DATE = 170004 RA_PARTIAL_REPLAY_NOT_SUPPORTED = 170008 RA_REPOS_ROOT_URL_MISMATCH = 170010 RA_SERF_GSSAPI_INITIALISATION_FAILED = 230002 RA_SERF_SSL_CERT_UNTRUSTED = 230001 RA_SERF_SSPI_INITIALISATION_FAILED = 230000 RA_SERF_WRAPPED_ERROR = 230003 RA_SESSION_URL_MISMATCH = 170011 RA_SVN_BAD_VERSION = 210006 RA_SVN_CMD_ERR = 210000 RA_SVN_CONNECTION_CLOSED = 210002 RA_SVN_EDIT_ABORTED = 210008 RA_SVN_IO_ERROR = 210003 RA_SVN_MALFORMED_DATA = 210004 RA_SVN_NO_MECHANISMS = 210007 RA_SVN_REPOS_NOT_FOUND = 210005 RA_SVN_UNKNOWN_CMD = 210001 RA_UNKNOWN_AUTH = 170002 RA_UNSUPPORTED_ABI_VERSION = 170006 RA_UUID_MISMATCH = 170009 REPOS_BAD_ARGS = 165002 REPOS_BAD_REVISION_REPORT = 165004 REPOS_DISABLED_FEATURE = 165006 REPOS_HOOK_FAILURE = 165001 REPOS_LOCKED = 165000 REPOS_NO_DATA_FOR_REPORT = 165003 REPOS_POST_COMMIT_HOOK_FAILED = 165007 REPOS_POST_LOCK_HOOK_FAILED = 165008 REPOS_POST_UNLOCK_HOOK_FAILED = 165009 REPOS_UNSUPPORTED_UPGRADE = 165010 REPOS_UNSUPPORTED_VERSION = 165005 RESERVED_FILENAME_SPECIFIED = 200025 REVNUM_PARSE_FAILURE = 200022 SQLITE_BUSY = 200033 SQLITE_CONSTRAINT = 200035 SQLITE_ERROR = 200030 SQLITE_READONLY = 200031 SQLITE_RESETTING_FOR_ROLLBACK = 200034 SQLITE_UNSUPPORTED_SCHEMA = 200032 STREAM_MALFORMED_DATA = 140001 STREAM_SEEK_NOT_SUPPORTED = 140003 STREAM_UNEXPECTED_EOF = 140000 STREAM_UNRECOGNIZED_DATA = 140002 SVNDIFF_BACKWARD_VIEW = 185002 SVNDIFF_CORRUPT_WINDOW = 185001 SVNDIFF_INVALID_COMPRESSED_DATA = 185005 SVNDIFF_INVALID_HEADER = 185000 SVNDIFF_INVALID_OPS = 185003 SVNDIFF_UNEXPECTED_END = 185004 SWIG_PY_EXCEPTION_SET = 200013 TEST_FAILED = 200006 TEST_SKIPPED = 200027 UNKNOWN_CAPABILITY = 200026 UNKNOWN_CHANGELIST = 200024 UNSUPPORTED_FEATURE = 200007 UNVERSIONED_RESOURCE = 200005 VERSION_MISMATCH = 200019 WC_BAD_ADM_LOG = 155009 WC_BAD_ADM_LOG_START = 155020 WC_BAD_PATH = 155022 WC_CANNOT_DELETE_FILE_EXTERNAL = 155030 WC_CANNOT_MOVE_FILE_EXTERNAL = 155031 WC_CHANGELIST_MOVE = 155029 WC_CLEANUP_REQUIRED = 155037 WC_CONFLICT_RESOLVER_FAILURE = 155027 WC_COPYFROM_PATH_NOT_FOUND = 155028 WC_CORRUPT = 155016 WC_CORRUPT_TEXT_BASE = 155017 WC_DB_ERROR = 155032 WC_FOUND_CONFLICT = 155015 WC_INVALID_LOCK = 155006 WC_INVALID_OPERATION_DEPTH = 155038 WC_INVALID_OP_ON_CWD = 155019 WC_INVALID_RELOCATION = 155024 WC_INVALID_SCHEDULE = 155023 WC_INVALID_SWITCH = 155025 WC_LEFT_LOCAL_MOD = 155012 WC_LOCKED = 155004 WC_MISMATCHED_CHANGELIST = 155026 WC_MISSING = 155033 WC_NODE_KIND_CHANGE = 155018 WC_NOT_FILE = 155008 WC_NOT_LOCKED = 155005 WC_NOT_SYMLINK = 155034 WC_NOT_UP_TO_DATE = 155011 WC_NOT_WORKING_COPY = 155007 WC_OBSTRUCTED_UPDATE = 155000 WC_PATH_ACCESS_DENIED = 155039 WC_PATH_FOUND = 155014 WC_PATH_NOT_FOUND = 155010 WC_PATH_UNEXPECTED_STATUS = 155035 WC_SCHEDULE_CONFLICT = 155013 WC_UNSUPPORTED_FORMAT = 155021 WC_UNWIND_EMPTY = 155002 WC_UNWIND_MISMATCH = 155001 WC_UNWIND_NOT_EMPTY = 155003 WC_UPGRADE_REQUIRED = 155036 XML_ATTRIB_NOT_FOUND = 130000 XML_MALFORMED = 130003 XML_MISSING_ANCESTRY = 130001 XML_UNESCAPABLE_DATA = 130004 XML_UNKNOWN_ENCODING = 130002 cvs2svn-2.5.0/svntest/testcase.py0000664000175100017510000002377012203665123020100 0ustar mhaggermhagger00000000000000# # testcase.py: Control of test case execution. # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import os, types, sys import svntest # if somebody does a "from testcase import *", they only get these names __all__ = ['_XFail', '_Wimp', '_Skip', '_SkipUnless'] RESULT_OK = 'ok' RESULT_FAIL = 'fail' RESULT_SKIP = 'skip' class TextColors: '''Some ANSI terminal constants for output color''' ENDC = '\033[0;m' FAILURE = '\033[1;31m' SUCCESS = '\033[1;32m' @classmethod def disable(cls): cls.ENDC = '' cls.FAILURE = '' cls.SUCCESS = '' @classmethod def success(cls, str): return lambda: cls.SUCCESS + str + cls.ENDC @classmethod def failure(cls, str): return lambda: cls.FAILURE + str + cls.ENDC if not sys.stdout.isatty() or sys.platform == 'win32': TextColors.disable() class TestCase: """A thing that can be tested. This is an abstract class with several methods that need to be overridden.""" _result_map = { RESULT_OK: (0, TextColors.success('PASS: '), True), RESULT_FAIL: (1, TextColors.failure('FAIL: '), False), RESULT_SKIP: (2, TextColors.success('SKIP: '), True), } def __init__(self, delegate=None, cond_func=lambda: True, doc=None, wip=None, issues=None): """Create a test case instance based on DELEGATE. COND_FUNC is a callable that is evaluated at test run time and should return a boolean value that determines how a pass or failure is interpreted: see the specialized kinds of test case such as XFail and Skip for details. The evaluation of COND_FUNC is deferred so that it can base its decision on useful bits of information that are not available at __init__ time (like the fact that we're running over a particular RA layer). DOC is ... WIP is a string describing the reason for the work-in-progress """ assert hasattr(cond_func, '__call__') self._delegate = delegate self._cond_func = cond_func self.description = doc or delegate.description self.inprogress = wip self.issues = issues def get_function_name(self): """Return the name of the python function implementing the test.""" return self._delegate.get_function_name() def get_sandbox_name(self): """Return the name that should be used for the sandbox. If a sandbox should not be constructed, this method returns None. """ return self._delegate.get_sandbox_name() def set_issues(self, issues): """Set the issues associated with this test.""" self.issues = issues def run(self, sandbox): """Run the test within the given sandbox.""" return self._delegate.run(sandbox) def list_mode(self): return '' def results(self, result): # if our condition applied, then use our result map. otherwise, delegate. if self._cond_func(): val = list(self._result_map[result]) val[1] = val[1]() return val return self._delegate.results(result) class FunctionTestCase(TestCase): """A TestCase based on a naked Python function object. FUNC should be a function that returns None on success and throws an svntest.Failure exception on failure. It should have a brief docstring describing what it does (and fulfilling certain conditions). FUNC must take one argument, an Sandbox instance. (The sandbox name is derived from the file name in which FUNC was defined) """ def __init__(self, func, issues=None): # it better be a function that accepts an sbox parameter and has a # docstring on it. assert isinstance(func, types.FunctionType) name = func.func_name assert func.func_code.co_argcount == 1, \ '%s must take an sbox argument' % name doc = func.__doc__.strip() assert doc, '%s must have a docstring' % name # enforce stylistic guidelines for the function docstrings: # - no longer than 50 characters # - should not end in a period # - should not be capitalized assert len(doc) <= 50, \ "%s's docstring must be 50 characters or less" % name assert doc[-1] != '.', \ "%s's docstring should not end in a period" % name assert doc[0].lower() == doc[0], \ "%s's docstring should not be capitalized" % name TestCase.__init__(self, doc=doc, issues=issues) self.func = func def get_function_name(self): return self.func.func_name def get_sandbox_name(self): """Base the sandbox's name on the name of the file in which the function was defined.""" filename = self.func.func_code.co_filename return os.path.splitext(os.path.basename(filename))[0] def run(self, sandbox): return self.func(sandbox) class _XFail(TestCase): """A test that is expected to fail, if its condition is true.""" _result_map = { RESULT_OK: (1, TextColors.failure('XPASS:'), False), RESULT_FAIL: (0, TextColors.success('XFAIL:'), True), RESULT_SKIP: (2, TextColors.success('SKIP: '), True), } def __init__(self, test_case, cond_func=lambda: True, wip=None, issues=None): """Create an XFail instance based on TEST_CASE. COND_FUNC is a callable that is evaluated at test run time and should return a boolean value. If COND_FUNC returns true, then TEST_CASE is expected to fail (and a pass is considered an error); otherwise, TEST_CASE is run normally. The evaluation of COND_FUNC is deferred so that it can base its decision on useful bits of information that are not available at __init__ time (like the fact that we're running over a particular RA layer). WIP is ... ISSUES is an issue number (or a list of issue numbers) tracking this.""" TestCase.__init__(self, create_test_case(test_case), cond_func, wip=wip, issues=issues) def list_mode(self): # basically, the only possible delegate is a Skip test. favor that mode. return self._delegate.list_mode() or 'XFAIL' class _Wimp(_XFail): """Like XFail, but indicates a work-in-progress: an unexpected pass is not considered a test failure.""" _result_map = { RESULT_OK: (0, TextColors.success('XPASS:'), True), RESULT_FAIL: (0, TextColors.success('XFAIL:'), True), RESULT_SKIP: (2, TextColors.success('SKIP: '), True), } def __init__(self, wip, test_case, cond_func=lambda: True): _XFail.__init__(self, test_case, cond_func, wip) class _Skip(TestCase): """A test that will be skipped if its conditional is true.""" def __init__(self, test_case, cond_func=lambda: True, issues=None): """Create a Skip instance based on TEST_CASE. COND_FUNC is a callable that is evaluated at test run time and should return a boolean value. If COND_FUNC returns true, then TEST_CASE is skipped; otherwise, TEST_CASE is run normally. The evaluation of COND_FUNC is deferred so that it can base its decision on useful bits of information that are not available at __init__ time (like the fact that we're running over a particular RA layer).""" TestCase.__init__(self, create_test_case(test_case), cond_func, issues=issues) def list_mode(self): if self._cond_func(): return 'SKIP' return self._delegate.list_mode() def get_sandbox_name(self): if self._cond_func(): return None return self._delegate.get_sandbox_name() def run(self, sandbox): if self._cond_func(): raise svntest.Skip return self._delegate.run(sandbox) class _SkipUnless(_Skip): """A test that will be skipped if its conditional is false.""" def __init__(self, test_case, cond_func): _Skip.__init__(self, test_case, lambda c=cond_func: not c()) def create_test_case(func, issues=None): if isinstance(func, TestCase): return func else: return FunctionTestCase(func, issues=issues) # Various decorators to make declaring tests as such simpler def XFail_deco(cond_func = lambda: True): def _second(func): if isinstance(func, TestCase): return _XFail(func, cond_func, issues=func.issues) else: return _XFail(func, cond_func) return _second def Wimp_deco(wip, cond_func = lambda: True): def _second(func): if isinstance(func, TestCase): return _Wimp(wip, func, cond_func, issues=func.issues) else: return _Wimp(wip, func, cond_func) return _second def Skip_deco(cond_func = lambda: True): def _second(func): if isinstance(func, TestCase): return _Skip(func, cond_func, issues=func.issues) else: return _Skip(func, cond_func) return _second def SkipUnless_deco(cond_func): def _second(func): if isinstance(func, TestCase): return _Skip(func, lambda c=cond_func: not c(), issues=func.issues) else: return _Skip(func, lambda c=cond_func: not c()) return _second def Issues_deco(*issues): def _second(func): if isinstance(func, TestCase): # if the wrapped thing is already a test case, just set the issues func.set_issues(issues) return func else: # we need to wrap the function return create_test_case(func, issues=issues) return _second # Create a singular alias, for linguistic correctness Issue_deco = Issues_deco cvs2svn-2.5.0/svntest/sandbox.py0000664000175100017510000003347412203665123017725 0ustar mhaggermhagger00000000000000# # sandbox.py : tools for manipulating a test's working area ("a sandbox") # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ==================================================================== # import os import shutil import copy import urllib import logging import svntest logger = logging.getLogger() class Sandbox: """Manages a sandbox (one or more repository/working copy pairs) for a test to operate within.""" dependents = None def __init__(self, module, idx): self.test_paths = [] self._set_name("%s-%d" % (module, idx)) # This flag is set to True by build() and returned by is_built() self._is_built = False # Create an empty directory for temporary files self.tmp_dir = self.add_wc_path('tmp', remove=True) os.mkdir(self.tmp_dir) def _set_name(self, name, read_only=False): """A convenience method for renaming a sandbox, useful when working with multiple repositories in the same unit test.""" if not name is None: self.name = name self.read_only = read_only self.wc_dir = os.path.join(svntest.main.general_wc_dir, self.name) self.add_test_path(self.wc_dir) if not read_only: self.repo_dir = os.path.join(svntest.main.general_repo_dir, self.name) self.repo_url = (svntest.main.options.test_area_url + '/' + urllib.pathname2url(self.repo_dir)) self.add_test_path(self.repo_dir) else: self.repo_dir = svntest.main.pristine_greek_repos_dir self.repo_url = svntest.main.pristine_greek_repos_url ### TODO: Move this into to the build() method # For dav tests we need a single authz file which must be present, # so we recreate it each time a sandbox is created with some default # contents, making sure that an empty file is never present if self.repo_url.startswith("http"): # this dir doesn't exist out of the box, so we may have to make it if not os.path.exists(svntest.main.work_dir): os.makedirs(svntest.main.work_dir) self.authz_file = os.path.join(svntest.main.work_dir, "authz") tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name) open(tmp_authz_file, 'w').write("[/]\n* = rw\n") shutil.move(tmp_authz_file, self.authz_file) # For svnserve tests we have a per-repository authz file, and it # doesn't need to be there in order for things to work, so we don't # have any default contents. elif self.repo_url.startswith("svn"): self.authz_file = os.path.join(self.repo_dir, "conf", "authz") def clone_dependent(self, copy_wc=False): """A convenience method for creating a near-duplicate of this sandbox, useful when working with multiple repositories in the same unit test. If COPY_WC is true, make an exact copy of this sandbox's working copy at the new sandbox's working copy directory. Any necessary cleanup operations are triggered by cleanup of the original sandbox.""" if not self.dependents: self.dependents = [] clone = copy.deepcopy(self) self.dependents.append(clone) clone._set_name("%s-%d" % (self.name, len(self.dependents))) if copy_wc: self.add_test_path(clone.wc_dir) shutil.copytree(self.wc_dir, clone.wc_dir, symlinks=True) return clone def build(self, name=None, create_wc=True, read_only=False, minor_version=None): """Make a 'Greek Tree' repo (or refer to the central one if READ_ONLY), and check out a WC from it (unless CREATE_WC is false). Change the sandbox's name to NAME. See actions.make_repo_and_wc() for details.""" self._set_name(name, read_only) svntest.actions.make_repo_and_wc(self, create_wc, read_only, minor_version) self._is_built = True def authz_name(self, repo_dir=None): "return this sandbox's name for use in an authz file" repo_dir = repo_dir or self.repo_dir if self.repo_url.startswith("http"): return os.path.basename(repo_dir) else: return repo_dir.replace('\\', '/') def add_test_path(self, path, remove=True): self.test_paths.append(path) if remove: svntest.main.safe_rmtree(path) def add_repo_path(self, suffix, remove=True): """Generate a path, under the general repositories directory, with a name that ends in SUFFIX, e.g. suffix="2" -> ".../basic_tests.2". If REMOVE is true, remove anything currently on disk at that path. Remember that path so that the automatic clean-up mechanism can delete it at the end of the test. Generate a repository URL to refer to a repository at that path. Do not create a repository. Return (REPOS-PATH, REPOS-URL).""" path = (os.path.join(svntest.main.general_repo_dir, self.name) + '.' + suffix) url = svntest.main.options.test_area_url + \ '/' + urllib.pathname2url(path) self.add_test_path(path, remove) return path, url def add_wc_path(self, suffix, remove=True): """Generate a path, under the general working copies directory, with a name that ends in SUFFIX, e.g. suffix="2" -> ".../basic_tests.2". If REMOVE is true, remove anything currently on disk at that path. Remember that path so that the automatic clean-up mechanism can delete it at the end of the test. Do not create a working copy. Return the generated WC-PATH.""" path = self.wc_dir + '.' + suffix self.add_test_path(path, remove) return path tempname_offs = 0 # Counter for get_tempname def get_tempname(self, prefix='tmp'): """Get a stable name for a temporary file that will be removed after running the test""" self.tempname_offs = self.tempname_offs + 1 return os.path.join(self.tmp_dir, '%s-%s' % (prefix, self.tempname_offs)) def cleanup_test_paths(self): "Clean up detritus from this sandbox, and any dependents." if self.dependents: # Recursively cleanup any dependent sandboxes. for sbox in self.dependents: sbox.cleanup_test_paths() # cleanup all test specific working copies and repositories for path in self.test_paths: if not path is svntest.main.pristine_greek_repos_dir: _cleanup_test_path(path) def is_built(self): "Returns True when build() has been called on this instance." return self._is_built def ospath(self, relpath, wc_dir=None): """Return RELPATH converted to an OS-style path relative to the WC dir of this sbox, or relative to OS-style path WC_DIR if supplied.""" if wc_dir is None: wc_dir = self.wc_dir return os.path.join(wc_dir, svntest.wc.to_ospath(relpath)) def ospaths(self, relpaths, wc_dir=None): """Return a list of RELPATHS but with each path converted to an OS-style path relative to the WC dir of this sbox, or relative to OS-style path WC_DIR if supplied.""" return [self.ospath(rp, wc_dir) for rp in relpaths] def redirected_root_url(self, temporary=False): """If TEMPORARY is set, return the URL which should be configured to temporarily redirect to the root of this repository; otherwise, return the URL which should be configured to permanent redirect there. (Assumes that the sandbox is not read-only.)""" assert not self.read_only assert self.repo_url.startswith("http") parts = self.repo_url.rsplit('/', 1) return '%s/REDIRECT-%s-%s' % (parts[0], temporary and 'TEMP' or 'PERM', parts[1]) def simple_update(self, target=None, revision='HEAD'): """Update the WC or TARGET. TARGET is a relpath relative to the WC.""" if target is None: target = self.wc_dir else: target = self.ospath(target) svntest.main.run_svn(False, 'update', target, '-r', revision) def simple_switch(self, url, target=None): """Switch the WC or TARGET to URL. TARGET is a relpath relative to the WC.""" if target is None: target = self.wc_dir else: target = self.ospath(target) svntest.main.run_svn(False, 'switch', url, target, '--ignore-ancestry') def simple_commit(self, target=None, message=None): """Commit the WC or TARGET, with a default or supplied log message. Raise if the exit code is non-zero or there is output on stderr. TARGET is a relpath relative to the WC.""" assert not self.read_only if target is None: target = self.wc_dir else: target = self.ospath(target) if message is None: message = svntest.main.make_log_msg() svntest.main.run_svn(False, 'commit', '-m', message, target) def simple_rm(self, *targets): """Schedule TARGETS for deletion. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'rm', *targets) def simple_mkdir(self, *targets): """Create TARGETS as directories scheduled for addition. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'mkdir', *targets) def simple_add(self, *targets): """Schedule TARGETS for addition. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'add', *targets) def simple_revert(self, *targets): """Revert TARGETS. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'revert', *targets) def simple_propset(self, name, value, *targets): """Set property NAME to VALUE on TARGETS. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'propset', name, value, *targets) def simple_propdel(self, name, *targets): """Delete property NAME from TARGETS. TARGETS are relpaths relative to the WC.""" assert len(targets) > 0 targets = self.ospaths(targets) svntest.main.run_svn(False, 'propdel', name, *targets) def simple_propget(self, name, target): """Return the value of the property NAME on TARGET. TARGET is a relpath relative to the WC.""" target = self.ospath(target) exit, out, err = svntest.main.run_svn(False, 'propget', '--strict', name, target) return ''.join(out) def simple_proplist(self, target): """Return a dictionary mapping property name to property value, of the properties on TARGET. TARGET is a relpath relative to the WC.""" target = self.ospath(target) exit, out, err = svntest.main.run_svn(False, 'proplist', '--verbose', '--quiet', target) props = {} for line in out: line = line.rstrip('\r\n') if line[2] != ' ': # property name name = line[2:] val = None elif line.startswith(' '): # property value if val is None: val = line[4:] else: val += '\n' + line[4:] props[name] = val else: raise Exception("Unexpected line '" + line + "' in proplist output" + str(out)) return props def simple_copy(self, source, dest): """Copy SOURCE to DEST in the WC. SOURCE and DEST are relpaths relative to the WC.""" source = self.ospath(source) dest = self.ospath(dest) svntest.main.run_svn(False, 'copy', source, dest) def simple_move(self, source, dest): """Move SOURCE to DEST in the WC. SOURCE and DEST are relpaths relative to the WC.""" source = self.ospath(source) dest = self.ospath(dest) svntest.main.run_svn(False, 'move', source, dest) def simple_repo_copy(self, source, dest): """Copy SOURCE to DEST in the repository, committing the result with a default log message. SOURCE and DEST are relpaths relative to the repo root.""" svntest.main.run_svn(False, 'copy', '-m', svntest.main.make_log_msg(), self.repo_url + '/' + source, self.repo_url + '/' + dest) def simple_append(self, dest, contents, truncate=False): """Append CONTENTS to file DEST, optionally truncating it first. DEST is a relpath relative to the WC.""" open(self.ospath(dest), truncate and 'w' or 'a').write(contents) def is_url(target): return (target.startswith('^/') or target.startswith('file://') or target.startswith('http://') or target.startswith('https://') or target.startswith('svn://') or target.startswith('svn+ssh://')) _deferred_test_paths = [] def cleanup_deferred_test_paths(): global _deferred_test_paths test_paths = _deferred_test_paths _deferred_test_paths = [] for path in test_paths: _cleanup_test_path(path, True) def _cleanup_test_path(path, retrying=False): if retrying: logger.info("CLEANUP: RETRY: %s", path) else: logger.info("CLEANUP: %s", path) try: svntest.main.safe_rmtree(path) except: logger.info("WARNING: cleanup failed, will try again later") _deferred_test_paths.append(path) cvs2svn-2.5.0/svntest/actions.py0000664000175100017510000034030312203665123017717 0ustar mhaggermhagger00000000000000# # actions.py: routines that actually run the svn client. # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import os, shutil, re, sys, errno import difflib, pprint, logging import xml.parsers.expat from xml.dom.minidom import parseString if sys.version_info[0] >= 3: # Python >=3.0 from io import StringIO else: # Python <3.0 from cStringIO import StringIO import svntest from svntest import main, verify, tree, wc from svntest import Failure logger = logging.getLogger() def _log_tree_state(msg, actual, subtree=""): if subtree: subtree += os.sep o = StringIO() o.write(msg + '\n') tree.dump_tree_script(actual, subtree, stream=o) logger.warn(o.getvalue()) o.close() def no_sleep_for_timestamps(): os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'yes' def do_sleep_for_timestamps(): os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS'] = 'no' def no_relocate_validation(): os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'yes' def do_relocate_validation(): os.environ['SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION'] = 'no' def setup_pristine_greek_repository(): """Create the pristine repository and 'svn import' the greek tree""" # these directories don't exist out of the box, so we may have to create them if not os.path.exists(main.general_wc_dir): os.makedirs(main.general_wc_dir) if not os.path.exists(main.general_repo_dir): os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs # If there's no pristine repos, create one. if not os.path.exists(main.pristine_greek_repos_dir): main.create_repos(main.pristine_greek_repos_dir) # if this is dav, gives us access rights to import the greek tree. if main.is_ra_type_dav(): authz_file = os.path.join(main.work_dir, "authz") main.file_write(authz_file, "[/]\n* = rw\n") # dump the greek tree to disk. main.greek_state.write_to_disk(main.greek_dump_dir) # import the greek tree, using l:foo/p:bar ### todo: svn should not be prompting for auth info when using ### repositories with no auth/auth requirements exit_code, output, errput = main.run_svn(None, 'import', '-m', 'Log message for revision 1.', main.greek_dump_dir, main.pristine_greek_repos_url) # check for any errors from the import if len(errput): display_lines("Errors during initial 'svn import':", 'STDERR', None, errput) sys.exit(1) # verify the printed output of 'svn import'. lastline = output.pop().strip() match = re.search("(Committed|Imported) revision [0-9]+.", lastline) if not match: logger.error("import did not succeed, while creating greek repos.") logger.error("The final line from 'svn import' was:") logger.error(lastline) sys.exit(1) output_tree = wc.State.from_commit(output) expected_output_tree = main.greek_state.copy(main.greek_dump_dir) expected_output_tree.tweak(verb='Adding', contents=None) try: expected_output_tree.compare_and_display('output', output_tree) except tree.SVNTreeUnequal: verify.display_trees("ERROR: output of import command is unexpected.", "OUTPUT TREE", expected_output_tree.old_tree(), output_tree.old_tree()) sys.exit(1) # Finally, disallow any changes to the "pristine" repos. error_msg = "Don't modify the pristine repository" create_failing_hook(main.pristine_greek_repos_dir, 'start-commit', error_msg) create_failing_hook(main.pristine_greek_repos_dir, 'pre-lock', error_msg) create_failing_hook(main.pristine_greek_repos_dir, 'pre-revprop-change', error_msg) ###################################################################### def guarantee_empty_repository(path): """Guarantee that a local svn repository exists at PATH, containing nothing.""" if path == main.pristine_greek_repos_dir: logger.error("attempt to overwrite the pristine repos! Aborting.") sys.exit(1) # create an empty repository at PATH. main.safe_rmtree(path) main.create_repos(path) # Used by every test, so that they can run independently of one # another. Every time this routine is called, it recursively copies # the `pristine repos' to a new location. # Note: make sure setup_pristine_greek_repository was called once before # using this function. def guarantee_greek_repository(path, minor_version): """Guarantee that a local svn repository exists at PATH, containing nothing but the greek-tree at revision 1.""" if path == main.pristine_greek_repos_dir: logger.error("attempt to overwrite the pristine repos! Aborting.") sys.exit(1) # copy the pristine repository to PATH. main.safe_rmtree(path) if main.copy_repos(main.pristine_greek_repos_dir, path, 1, 1, minor_version): logger.error("copying repository failed.") sys.exit(1) # make the repos world-writeable, for mod_dav_svn's sake. main.chmod_tree(path, 0666, 0666) def run_and_verify_atomic_ra_revprop_change(message, expected_stdout, expected_stderr, expected_exit, url, revision, propname, old_propval, propval, want_error): """Run atomic-ra-revprop-change helper and check its output and exit code. Transforms OLD_PROPVAL and PROPVAL into a skel. For HTTP, the default HTTP library is used.""" KEY_OLD_PROPVAL = "old_value_p" KEY_NEW_PROPVAL = "value" def skel_make_atom(word): return "%d %s" % (len(word), word) def make_proplist_skel_part(nick, val): if val is None: return "" else: return "%s %s" % (skel_make_atom(nick), skel_make_atom(val)) skel = "( %s %s )" % (make_proplist_skel_part(KEY_OLD_PROPVAL, old_propval), make_proplist_skel_part(KEY_NEW_PROPVAL, propval)) exit_code, out, err = main.run_atomic_ra_revprop_change(url, revision, propname, skel, want_error) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def run_and_verify_svnlook(message, expected_stdout, expected_stderr, *varargs): """Like run_and_verify_svnlook2, but the expected exit code is assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 return run_and_verify_svnlook2(message, expected_stdout, expected_stderr, expected_exit, *varargs) def run_and_verify_svnlook2(message, expected_stdout, expected_stderr, expected_exit, *varargs): """Run svnlook command and check its output and exit code.""" exit_code, out, err = main.run_svnlook(*varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def run_and_verify_svnadmin(message, expected_stdout, expected_stderr, *varargs): """Like run_and_verify_svnadmin2, but the expected exit code is assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 return run_and_verify_svnadmin2(message, expected_stdout, expected_stderr, expected_exit, *varargs) def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr, expected_exit, *varargs): """Run svnadmin command and check its output and exit code.""" exit_code, out, err = main.run_svnadmin(*varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def run_and_verify_svnversion(message, wc_dir, trail_url, expected_stdout, expected_stderr, *varargs): """like run_and_verify_svnversion2, but the expected exit code is assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 return run_and_verify_svnversion2(message, wc_dir, trail_url, expected_stdout, expected_stderr, expected_exit, *varargs) def run_and_verify_svnversion2(message, wc_dir, trail_url, expected_stdout, expected_stderr, expected_exit, *varargs): """Run svnversion command and check its output and exit code.""" if trail_url is None: exit_code, out, err = main.run_svnversion(wc_dir, *varargs) else: exit_code, out, err = main.run_svnversion(wc_dir, trail_url, *varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs): """like run_and_verify_svn2, but the expected exit code is assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" expected_exit = 0 if expected_stderr is not None: if isinstance(expected_stderr, verify.ExpectedOutput): if not expected_stderr.matches([]): expected_exit = 1 elif expected_stderr != []: expected_exit = 1 return run_and_verify_svn2(message, expected_stdout, expected_stderr, expected_exit, *varargs) def run_and_verify_svn2(message, expected_stdout, expected_stderr, expected_exit, *varargs): """Invoke main.run_svn() with *VARARGS. Return exit code as int; stdout, stderr as lists of lines (including line terminators). For both EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of verify.ExpectedOutput (if necessary): - If it is an array of strings, create a vanilla ExpectedOutput. - If it is a single string, create a RegexOutput that must match every line (for stdout) or any line (for stderr) of the expected output. - If it is already an instance of ExpectedOutput (e.g. UnorderedOutput), leave it alone. ...and invoke compare_and_display_lines() on MESSAGE, a label based on the name of the stream being compared (e.g. STDOUT), the ExpectedOutput instance, and the actual output. If EXPECTED_STDOUT is None, do not check stdout. EXPECTED_STDERR may not be None. If output checks pass, the expected and actual codes are compared. If a comparison fails, a Failure will be raised.""" if expected_stderr is None: raise verify.SVNIncorrectDatatype("expected_stderr must not be None") want_err = None if isinstance(expected_stderr, verify.ExpectedOutput): if not expected_stderr.matches([]): want_err = True elif expected_stderr != []: want_err = True exit_code, out, err = main.run_svn(want_err, *varargs) verify.verify_outputs(message, out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def run_and_verify_load(repo_dir, dump_file_content, bypass_prop_validation = False): "Runs 'svnadmin load' and reports any errors." if not isinstance(dump_file_content, list): raise TypeError("dump_file_content argument should have list type") expected_stderr = [] if bypass_prop_validation: exit_code, output, errput = main.run_command_stdin( main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content, 'load', '--force-uuid', '--quiet', '--bypass-prop-validation', repo_dir) else: exit_code, output, errput = main.run_command_stdin( main.svnadmin_binary, expected_stderr, 0, 1, dump_file_content, 'load', '--force-uuid', '--quiet', repo_dir) verify.verify_outputs("Unexpected stderr output", None, errput, None, expected_stderr) def run_and_verify_dump(repo_dir, deltas=False): "Runs 'svnadmin dump' and reports any errors, returning the dump content." if deltas: exit_code, output, errput = main.run_svnadmin('dump', '--deltas', repo_dir) else: exit_code, output, errput = main.run_svnadmin('dump', repo_dir) verify.verify_outputs("Missing expected output(s)", output, errput, verify.AnyOutput, verify.AnyOutput) return output def run_and_verify_svnrdump(dumpfile_content, expected_stdout, expected_stderr, expected_exit, *varargs): """Runs 'svnrdump dump|load' depending on dumpfile_content and reports any errors.""" exit_code, output, err = main.run_svnrdump(dumpfile_content, *varargs) # Since main.run_svnrdump() uses binary mode, normalize the stderr # line endings on Windows ourselves. if sys.platform == 'win32': err = map(lambda x : x.replace('\r\n', '\n'), err) for index, line in enumerate(err[:]): if re.search("warning: W200007", line): del err[index] verify.verify_outputs("Unexpected output", output, err, expected_stdout, expected_stderr) verify.verify_exit_code("Unexpected return code", exit_code, expected_exit) return output def run_and_verify_svnmucc(message, expected_stdout, expected_stderr, *varargs): """Run svnmucc command and check its output""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 return run_and_verify_svnmucc2(message, expected_stdout, expected_stderr, expected_exit, *varargs) def run_and_verify_svnmucc2(message, expected_stdout, expected_stderr, expected_exit, *varargs): """Run svnmucc command and check its output and exit code.""" exit_code, out, err = main.run_svnmucc(*varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err def load_repo(sbox, dumpfile_path = None, dump_str = None, bypass_prop_validation = False): "Loads the dumpfile into sbox" if not dump_str: dump_str = open(dumpfile_path, "rb").read() # Create a virgin repos and working copy main.safe_rmtree(sbox.repo_dir, 1) main.safe_rmtree(sbox.wc_dir, 1) main.create_repos(sbox.repo_dir) # Load the mergetracking dumpfile into the repos, and check it out the repo run_and_verify_load(sbox.repo_dir, dump_str.splitlines(True), bypass_prop_validation) run_and_verify_svn(None, None, [], "co", sbox.repo_url, sbox.wc_dir) return dump_str def expected_noop_update_output(rev): """Return an ExpectedOutput object describing what we'd expect to see from an update to revision REV that was effectively a no-op (no server changes transmitted).""" return verify.createExpectedOutput("Updating '.*':|At revision %d." % (rev), "no-op update") ###################################################################### # Subversion Actions # # These are all routines that invoke 'svn' in particular ways, and # then verify the results by comparing expected trees with actual # trees. # def run_and_verify_checkout2(do_remove, URL, wc_dir_name, output_tree, disk_tree, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None, *args): """Checkout the URL into a new directory WC_DIR_NAME. *ARGS are any extra optional args to the checkout subcommand. The subcommand output will be verified against OUTPUT_TREE, and the working copy itself will be verified against DISK_TREE. For the latter comparison, SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that function's doc string for more details. Return if successful, raise on failure. WC_DIR_NAME is deleted if DO_REMOVE is True. """ if isinstance(output_tree, wc.State): output_tree = output_tree.old_tree() if isinstance(disk_tree, wc.State): disk_tree = disk_tree.old_tree() # Remove dir if it's already there, unless this is a forced checkout. # In that case assume we want to test a forced checkout's toleration # of obstructing paths. if do_remove: main.safe_rmtree(wc_dir_name) # Checkout and make a tree of the output, using l:foo/p:bar ### todo: svn should not be prompting for auth info when using ### repositories with no auth/auth requirements exit_code, output, errput = main.run_svn(None, 'co', URL, wc_dir_name, *args) actual = tree.build_tree_from_checkout(output) # Verify actual output against expected output. try: tree.compare_trees("output", actual, output_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL OUTPUT TREE:", actual, wc_dir_name) raise # Create a tree by scanning the working copy actual = tree.build_tree_from_wc(wc_dir_name) # Verify expected disk against actual disk. try: tree.compare_trees("disk", actual, disk_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL DISK TREE:", actual, wc_dir_name) raise def run_and_verify_checkout(URL, wc_dir_name, output_tree, disk_tree, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None, *args): """Same as run_and_verify_checkout2(), but without the DO_REMOVE arg. WC_DIR_NAME is deleted if present unless the '--force' option is passed in *ARGS.""" # Remove dir if it's already there, unless this is a forced checkout. # In that case assume we want to test a forced checkout's toleration # of obstructing paths. return run_and_verify_checkout2(('--force' not in args), URL, wc_dir_name, output_tree, disk_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton, *args) def run_and_verify_export(URL, export_dir_name, output_tree, disk_tree, *args): """Export the URL into a new directory WC_DIR_NAME. The subcommand output will be verified against OUTPUT_TREE, and the exported copy itself will be verified against DISK_TREE. Return if successful, raise on failure. """ assert isinstance(output_tree, wc.State) assert isinstance(disk_tree, wc.State) disk_tree = disk_tree.old_tree() output_tree = output_tree.old_tree() # Export and make a tree of the output, using l:foo/p:bar ### todo: svn should not be prompting for auth info when using ### repositories with no auth/auth requirements exit_code, output, errput = main.run_svn(None, 'export', URL, export_dir_name, *args) actual = tree.build_tree_from_checkout(output) # Verify actual output against expected output. try: tree.compare_trees("output", actual, output_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL OUTPUT TREE:", actual, export_dir_name) raise # Create a tree by scanning the working copy. Don't ignore # the .svn directories so that we generate an error if they # happen to show up. actual = tree.build_tree_from_wc(export_dir_name, ignore_svn=False) # Verify expected disk against actual disk. try: tree.compare_trees("disk", actual, disk_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL DISK TREE:", actual, export_dir_name) raise # run_and_verify_log_xml class LogEntry: def __init__(self, revision, changed_paths=None, revprops=None): self.revision = revision if changed_paths == None: self.changed_paths = {} else: self.changed_paths = changed_paths if revprops == None: self.revprops = {} else: self.revprops = revprops def assert_changed_paths(self, changed_paths): """Assert that changed_paths is the same as this entry's changed_paths Raises svntest.Failure if not. """ if self.changed_paths != changed_paths: raise Failure('\n' + '\n'.join(difflib.ndiff( pprint.pformat(changed_paths).splitlines(), pprint.pformat(self.changed_paths).splitlines()))) def assert_revprops(self, revprops): """Assert that the dict revprops is the same as this entry's revprops. Raises svntest.Failure if not. """ if self.revprops != revprops: raise Failure('\n' + '\n'.join(difflib.ndiff( pprint.pformat(revprops).splitlines(), pprint.pformat(self.revprops).splitlines()))) class LogParser: def parse(self, data): """Return a list of LogEntrys parsed from the sequence of strings data. This is the only method of interest to callers. """ try: for i in data: self.parser.Parse(i) self.parser.Parse('', True) except xml.parsers.expat.ExpatError, e: raise verify.SVNUnexpectedStdout('%s\n%s\n' % (e, ''.join(data),)) return self.entries def __init__(self): # for expat self.parser = xml.parsers.expat.ParserCreate() self.parser.StartElementHandler = self.handle_start_element self.parser.EndElementHandler = self.handle_end_element self.parser.CharacterDataHandler = self.handle_character_data # Ignore some things. self.ignore_elements('log', 'paths', 'revprops') self.ignore_tags('logentry_end', 'author_start', 'date_start', 'msg_start') # internal state self.cdata = [] self.property = None self.kind = None self.action = None # the result self.entries = [] def ignore(self, *args, **kwargs): del self.cdata[:] def ignore_tags(self, *args): for tag in args: setattr(self, tag, self.ignore) def ignore_elements(self, *args): for element in args: self.ignore_tags(element + '_start', element + '_end') # expat handlers def handle_start_element(self, name, attrs): getattr(self, name + '_start')(attrs) def handle_end_element(self, name): getattr(self, name + '_end')() def handle_character_data(self, data): self.cdata.append(data) # element handler utilities def use_cdata(self): result = ''.join(self.cdata).strip() del self.cdata[:] return result def svn_prop(self, name): self.entries[-1].revprops['svn:' + name] = self.use_cdata() # element handlers def logentry_start(self, attrs): self.entries.append(LogEntry(int(attrs['revision']))) def author_end(self): self.svn_prop('author') def msg_end(self): self.svn_prop('log') def date_end(self): # svn:date could be anything, so just note its presence. self.cdata[:] = [''] self.svn_prop('date') def property_start(self, attrs): self.property = attrs['name'] def property_end(self): self.entries[-1].revprops[self.property] = self.use_cdata() def path_start(self, attrs): self.kind = attrs['kind'] self.action = attrs['action'] def path_end(self): self.entries[-1].changed_paths[self.use_cdata()] = [{'kind': self.kind, 'action': self.action}] def run_and_verify_log_xml(message=None, expected_paths=None, expected_revprops=None, expected_stdout=None, expected_stderr=None, args=[]): """Call run_and_verify_svn with log --xml and args (optional) as command arguments, and pass along message, expected_stdout, and expected_stderr. If message is None, pass the svn log command as message. expected_paths checking is not yet implemented. expected_revprops is an optional list of dicts, compared to each revision's revprops. The list must be in the same order the log entries come in. Any svn:date revprops in the dicts must be '' in order to match, as the actual dates could be anything. expected_paths and expected_revprops are ignored if expected_stdout or expected_stderr is specified. """ if message == None: message = ' '.join(args) # We'll parse the output unless the caller specifies expected_stderr or # expected_stdout for run_and_verify_svn. parse = True if expected_stderr == None: expected_stderr = [] else: parse = False if expected_stdout != None: parse = False log_args = list(args) if expected_paths != None: log_args.append('-v') (exit_code, stdout, stderr) = run_and_verify_svn( message, expected_stdout, expected_stderr, 'log', '--xml', *log_args) if not parse: return entries = LogParser().parse(stdout) for index in range(len(entries)): entry = entries[index] if expected_revprops != None: entry.assert_revprops(expected_revprops[index]) if expected_paths != None: entry.assert_changed_paths(expected_paths[index]) def verify_update(actual_output, actual_mergeinfo_output, actual_elision_output, wc_dir_name, output_tree, mergeinfo_output_tree, elision_output_tree, disk_tree, status_tree, singleton_handler_a=None, a_baton=None, singleton_handler_b=None, b_baton=None, check_props=False): """Verify update of WC_DIR_NAME. The subcommand output (found in ACTUAL_OUTPUT, ACTUAL_MERGEINFO_OUTPUT, and ACTUAL_ELISION_OUTPUT) will be verified against OUTPUT_TREE, MERGEINFO_OUTPUT_TREE, and ELISION_OUTPUT_TREE respectively (if any of these is provided, they may be None in which case a comparison is not done). The working copy itself will be verified against DISK_TREE (if provided), and the working copy's 'svn status' output will be verified against STATUS_TREE (if provided). (This is a good way to check that revision numbers were bumped.) Return if successful, raise on failure. For the comparison with DISK_TREE, pass SINGLETON_HANDLER_A and SINGLETON_HANDLER_B to tree.compare_trees -- see that function's doc string for more details. If CHECK_PROPS is set, then disk comparison will examine props.""" if isinstance(actual_output, wc.State): actual_output = actual_output.old_tree() if isinstance(actual_mergeinfo_output, wc.State): actual_mergeinfo_output = actual_mergeinfo_output.old_tree() if isinstance(actual_elision_output, wc.State): actual_elision_output = actual_elision_output.old_tree() if isinstance(output_tree, wc.State): output_tree = output_tree.old_tree() if isinstance(mergeinfo_output_tree, wc.State): mergeinfo_output_tree = mergeinfo_output_tree.old_tree() if isinstance(elision_output_tree, wc.State): elision_output_tree = elision_output_tree.old_tree() if isinstance(disk_tree, wc.State): disk_tree = disk_tree.old_tree() if isinstance(status_tree, wc.State): status_tree = status_tree.old_tree() # Verify actual output against expected output. if output_tree: try: tree.compare_trees("output", actual_output, output_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL OUTPUT TREE:", actual_output, wc_dir_name) raise # Verify actual mergeinfo recording output against expected output. if mergeinfo_output_tree: try: tree.compare_trees("mergeinfo_output", actual_mergeinfo_output, mergeinfo_output_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL MERGEINFO OUTPUT TREE:", actual_mergeinfo_output, wc_dir_name) raise # Verify actual mergeinfo elision output against expected output. if elision_output_tree: try: tree.compare_trees("elision_output", actual_elision_output, elision_output_tree) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL ELISION OUTPUT TREE:", actual_elision_output, wc_dir_name) raise # Create a tree by scanning the working copy, and verify it if disk_tree: actual_disk = tree.build_tree_from_wc(wc_dir_name, check_props) try: tree.compare_trees("disk", actual_disk, disk_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton) except tree.SVNTreeUnequal: _log_tree_state("EXPECTED DISK TREE:", disk_tree) _log_tree_state("ACTUAL DISK TREE:", actual_disk) raise # Verify via 'status' command too, if possible. if status_tree: run_and_verify_status(wc_dir_name, status_tree) def verify_disk(wc_dir_name, disk_tree, check_props=False): """Verify WC_DIR_NAME against DISK_TREE. If CHECK_PROPS is set, the comparison will examin props. Returns if successful, raises on failure.""" verify_update(None, None, None, wc_dir_name, None, None, None, disk_tree, None, check_props=check_props) def run_and_verify_update(wc_dir_name, output_tree, disk_tree, status_tree, error_re_string = None, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None, check_props = False, *args): """Update WC_DIR_NAME. *ARGS are any extra optional args to the update subcommand. NOTE: If *ARGS is specified at all, explicit target paths must be passed in *ARGS as well (or a default `.' will be chosen by the 'svn' binary). This allows the caller to update many items in a single working copy dir, but still verify the entire working copy dir. If ERROR_RE_STRING, the update must exit with error, and the error message must match regular expression ERROR_RE_STRING. Else if ERROR_RE_STRING is None, then: If OUTPUT_TREE is not None, the subcommand output will be verified against OUTPUT_TREE. If DISK_TREE is not None, the working copy itself will be verified against DISK_TREE. If STATUS_TREE is not None, the 'svn status' output will be verified against STATUS_TREE. (This is a good way to check that revision numbers were bumped.) For the DISK_TREE verification, SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that function's doc string for more details. If CHECK_PROPS is set, then disk comparison will examine props. Return if successful, raise on failure.""" # Update and make a tree of the output. if len(args): exit_code, output, errput = main.run_svn(error_re_string, 'up', *args) else: exit_code, output, errput = main.run_svn(error_re_string, 'up', wc_dir_name, *args) if error_re_string: rm = re.compile(error_re_string) for line in errput: match = rm.search(line) if match: return raise main.SVNUnmatchedError actual = wc.State.from_checkout(output) verify_update(actual, None, None, wc_dir_name, output_tree, None, None, disk_tree, status_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton, check_props) def run_and_parse_info(*args): """Run 'svn info ARGS' and parse its output into a list of dicts, one dict per reported node.""" # the returned array all_infos = [] # per-target variables iter_info = {} prev_key = None lock_comment_lines = 0 lock_comments = [] exit_code, output, errput = main.run_svn(None, 'info', *args) for line in output: line = line[:-1] # trim '\n' if lock_comment_lines > 0: # mop up any lock comment lines lock_comments.append(line) lock_comment_lines = lock_comment_lines - 1 if lock_comment_lines == 0: iter_info[prev_key] = lock_comments elif len(line) == 0: # separator line between items all_infos.append(iter_info) iter_info = {} prev_key = None lock_comment_lines = 0 lock_comments = [] elif line[0].isspace(): # continuation line (for tree conflicts) iter_info[prev_key] += line[1:] else: # normal line key, value = line.split(':', 1) if re.search(' \(\d+ lines?\)$', key): # numbered continuation lines match = re.match('^(.*) \((\d+) lines?\)$', key) key = match.group(1) lock_comment_lines = int(match.group(2)) elif len(value) > 1: # normal normal line iter_info[key] = value[1:] else: ### originally added for "Tree conflict:\n" lines; ### tree-conflicts output format has changed since then # continuation lines are implicit (prefixed by whitespace) iter_info[key] = '' prev_key = key return all_infos def run_and_verify_info(expected_infos, *args): """Run 'svn info' with the arguments in *ARGS and verify the results against expected_infos. The latter should be a list of dicts, one dict per reported node, in the order in which the 'Path' fields of the output will appear after sorting them as Python strings. (The dicts in EXPECTED_INFOS, however, need not have a 'Path' key.) In the dicts, each key is the before-the-colon part of the 'svn info' output, and each value is either None (meaning that the key should *not* appear in the 'svn info' output) or a regex matching the output value. Output lines not matching a key in the dict are ignored. Return if successful, raise on failure.""" actual_infos = run_and_parse_info(*args) actual_infos.sort(key=lambda info: info['Path']) try: # zip() won't complain, so check this manually if len(actual_infos) != len(expected_infos): raise verify.SVNUnexpectedStdout( "Expected %d infos, found %d infos" % (len(expected_infos), len(actual_infos))) for actual, expected in zip(actual_infos, expected_infos): # compare dicts for key, value in expected.items(): assert ':' not in key # caller passed impossible expectations? if value is None and key in actual: raise main.SVNLineUnequal("Found unexpected key '%s' with value '%s'" % (key, actual[key])) if value is not None and key not in actual: raise main.SVNLineUnequal("Expected key '%s' (with value '%s') " "not found" % (key, value)) if value is not None and not re.match(value, actual[key]): raise verify.SVNUnexpectedStdout("Values of key '%s' don't match:\n" " Expected: '%s' (regex)\n" " Found: '%s' (string)\n" % (key, value, actual[key])) except: sys.stderr.write("Bad 'svn info' output:\n" " Received: %s\n" " Expected: %s\n" % (actual_infos, expected_infos)) raise def run_and_verify_merge(dir, rev1, rev2, url1, url2, output_tree, mergeinfo_output_tree, elision_output_tree, disk_tree, status_tree, skip_tree, error_re_string = None, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None, check_props = False, dry_run = True, *args): """Run 'svn merge URL1@REV1 URL2@REV2 DIR' if URL2 is not None (for a three-way merge between URLs and WC). If URL2 is None, run 'svn merge -rREV1:REV2 URL1 DIR'. If both REV1 and REV2 are None, leave off the '-r' argument. If ERROR_RE_STRING, the merge must exit with error, and the error message must match regular expression ERROR_RE_STRING. Else if ERROR_RE_STRING is None, then: The subcommand output will be verified against OUTPUT_TREE. Output related to mergeinfo notifications will be verified against MERGEINFO_OUTPUT_TREE if that is not None. Output related to mergeinfo elision will be verified against ELISION_OUTPUT_TREE if that is not None. The working copy itself will be verified against DISK_TREE. If optional STATUS_TREE is given, then 'svn status' output will be compared. The 'skipped' merge output will be compared to SKIP_TREE. For the DISK_TREE verification, SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that function's doc string for more details. If CHECK_PROPS is set, then disk comparison will examine props. If DRY_RUN is set then a --dry-run merge will be carried out first and the output compared with that of the full merge. Return if successful, raise on failure. *ARGS are any extra optional args to the merge subcommand. NOTE: If *ARGS is specified at all, an explicit target path must be passed in *ARGS as well. This allows the caller to merge into single items inside the working copy, but still verify the entire working copy dir. """ merge_command = [ "merge" ] if url2: merge_command.extend((url1 + "@" + str(rev1), url2 + "@" + str(rev2))) else: if not (rev1 is None and rev2 is None): merge_command.append("-r" + str(rev1) + ":" + str(rev2)) merge_command.append(url1) if len(args) == 0: merge_command.append(dir) merge_command = tuple(merge_command) if dry_run: pre_disk = tree.build_tree_from_wc(dir) dry_run_command = merge_command + ('--dry-run',) dry_run_command = dry_run_command + args exit_code, out_dry, err_dry = main.run_svn(error_re_string, *dry_run_command) post_disk = tree.build_tree_from_wc(dir) try: tree.compare_trees("disk", post_disk, pre_disk) except tree.SVNTreeError: logger.warn("=============================================================") logger.warn("Dry-run merge altered working copy") logger.warn("=============================================================") raise # Update and make a tree of the output. merge_command = merge_command + args exit_code, out, err = main.run_svn(error_re_string, *merge_command) if error_re_string: if not error_re_string.startswith(".*"): error_re_string = ".*(" + error_re_string + ")" expected_err = verify.RegexOutput(error_re_string, match_all=False) verify.verify_outputs(None, None, err, None, expected_err) return elif err: raise verify.SVNUnexpectedStderr(err) # Split the output into that related to application of the actual diff # and that related to the recording of mergeinfo describing the merge. merge_diff_out = [] mergeinfo_notification_out = [] mergeinfo_elision_out = [] mergeinfo_notifications = False elision_notifications = False for line in out: if line.startswith('--- Recording'): mergeinfo_notifications = True elision_notifications = False elif line.startswith('--- Eliding'): mergeinfo_notifications = False elision_notifications = True elif line.startswith('--- Merging') or \ line.startswith('--- Reverse-merging') or \ line.startswith('Summary of conflicts') or \ line.startswith('Skipped missing target'): mergeinfo_notifications = False elision_notifications = False if mergeinfo_notifications: mergeinfo_notification_out.append(line) elif elision_notifications: mergeinfo_elision_out.append(line) else: merge_diff_out.append(line) if dry_run and merge_diff_out != out_dry: # Due to the way ra_serf works, it's possible that the dry-run and # real merge operations did the same thing, but the output came in # a different order. Let's see if maybe that's the case by comparing # the outputs as unordered sets rather than as lists. # # This now happens for other RA layers with modern APR because the # hash order now varies. # # The different orders of the real and dry-run merges may cause # the "Merging rX through rY into" lines to be duplicated a # different number of times in the two outputs. The list-set # conversion removes duplicates so these differences are ignored. # It also removes "U some/path" duplicate lines. Perhaps we # should avoid that? out_copy = set(merge_diff_out[:]) out_dry_copy = set(out_dry[:]) if out_copy != out_dry_copy: logger.warn("=============================================================") logger.warn("Merge outputs differ") logger.warn("The dry-run merge output:") for x in out_dry: logger.warn(x) logger.warn("The full merge output:") for x in out: logger.warn(x) logger.warn("=============================================================") raise main.SVNUnmatchedError def missing_skip(a, b): logger.warn("=============================================================") logger.warn("Merge failed to skip: %s", a.path) logger.warn("=============================================================") raise Failure def extra_skip(a, b): logger.warn("=============================================================") logger.warn("Merge unexpectedly skipped: %s", a.path) logger.warn("=============================================================") raise Failure myskiptree = tree.build_tree_from_skipped(out) if isinstance(skip_tree, wc.State): skip_tree = skip_tree.old_tree() try: tree.compare_trees("skip", myskiptree, skip_tree, extra_skip, None, missing_skip, None) except tree.SVNTreeUnequal: _log_tree_state("ACTUAL SKIP TREE:", myskiptree, dir) raise actual_diff = svntest.wc.State.from_checkout(merge_diff_out, False) actual_mergeinfo = svntest.wc.State.from_checkout(mergeinfo_notification_out, False) actual_elision = svntest.wc.State.from_checkout(mergeinfo_elision_out, False) verify_update(actual_diff, actual_mergeinfo, actual_elision, dir, output_tree, mergeinfo_output_tree, elision_output_tree, disk_tree, status_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton, check_props) def run_and_verify_patch(dir, patch_path, output_tree, disk_tree, status_tree, skip_tree, error_re_string=None, check_props=False, dry_run=True, *args): """Run 'svn patch patch_path DIR'. If ERROR_RE_STRING, 'svn patch' must exit with error, and the error message must match regular expression ERROR_RE_STRING. Else if ERROR_RE_STRING is None, then: The subcommand output will be verified against OUTPUT_TREE, and the working copy itself will be verified against DISK_TREE. If optional STATUS_TREE is given, then 'svn status' output will be compared. The 'skipped' merge output will be compared to SKIP_TREE. If CHECK_PROPS is set, then disk comparison will examine props. If DRY_RUN is set then a --dry-run patch will be carried out first and the output compared with that of the full patch application. Returns if successful, raises on failure.""" patch_command = [ "patch" ] patch_command.append(patch_path) patch_command.append(dir) patch_command = tuple(patch_command) if dry_run: pre_disk = tree.build_tree_from_wc(dir) dry_run_command = patch_command + ('--dry-run',) dry_run_command = dry_run_command + args exit_code, out_dry, err_dry = main.run_svn(error_re_string, *dry_run_command) post_disk = tree.build_tree_from_wc(dir) try: tree.compare_trees("disk", post_disk, pre_disk) except tree.SVNTreeError: logger.warn("=============================================================") logger.warn("'svn patch --dry-run' altered working copy") logger.warn("=============================================================") raise # Update and make a tree of the output. patch_command = patch_command + args exit_code, out, err = main.run_svn(True, *patch_command) if error_re_string: rm = re.compile(error_re_string) match = None for line in err: match = rm.search(line) if match: break if not match: raise main.SVNUnmatchedError elif err: logger.warn("UNEXPECTED STDERR:") for x in err: logger.warn(x) raise verify.SVNUnexpectedStderr if dry_run and out != out_dry: # APR hash order means the output order can vary, assume everything is OK # if only the order changes. out_dry_expected = svntest.verify.UnorderedOutput(out) verify.compare_and_display_lines('dry-run patch output not as expected', '', out_dry_expected, out_dry) def missing_skip(a, b): logger.warn("=============================================================") logger.warn("'svn patch' failed to skip: %s", a.path) logger.warn("=============================================================") raise Failure def extra_skip(a, b): logger.warn("=============================================================") logger.warn("'svn patch' unexpectedly skipped: %s", a.path) logger.warn("=============================================================") raise Failure myskiptree = tree.build_tree_from_skipped(out) if isinstance(skip_tree, wc.State): skip_tree = skip_tree.old_tree() tree.compare_trees("skip", myskiptree, skip_tree, extra_skip, None, missing_skip, None) mytree = tree.build_tree_from_checkout(out, 0) # when the expected output is a list, we want a line-by-line # comparison to happen instead of a tree comparison if (isinstance(output_tree, list) or isinstance(output_tree, verify.UnorderedOutput)): verify.verify_outputs(None, out, err, output_tree, error_re_string) output_tree = None verify_update(mytree, None, None, dir, output_tree, None, None, disk_tree, status_tree, check_props=check_props) def run_and_verify_mergeinfo(error_re_string = None, expected_output = [], *args): """Run 'svn mergeinfo ARGS', and compare the result against EXPECTED_OUTPUT, a list of string representations of revisions expected in the output. Raise an exception if an unexpected output is encountered.""" mergeinfo_command = ["mergeinfo"] mergeinfo_command.extend(args) exit_code, out, err = main.run_svn(error_re_string, *mergeinfo_command) if error_re_string: if not error_re_string.startswith(".*"): error_re_string = ".*(" + error_re_string + ")" expected_err = verify.RegexOutput(error_re_string, match_all=False) verify.verify_outputs(None, None, err, None, expected_err) return out = [_f for _f in [x.rstrip()[1:] for x in out] if _f] expected_output.sort() extra_out = [] if out != expected_output: exp_hash = dict.fromkeys(expected_output) for rev in out: if rev in exp_hash: del(exp_hash[rev]) else: extra_out.append(rev) extra_exp = list(exp_hash.keys()) raise Exception("Unexpected 'svn mergeinfo' output:\n" " expected but not found: %s\n" " found but not expected: %s" % (', '.join([str(x) for x in extra_exp]), ', '.join([str(x) for x in extra_out]))) def run_and_verify_switch(wc_dir_name, wc_target, switch_url, output_tree, disk_tree, status_tree, error_re_string = None, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None, check_props = False, *args): """Switch WC_TARGET (in working copy dir WC_DIR_NAME) to SWITCH_URL. If ERROR_RE_STRING, the switch must exit with error, and the error message must match regular expression ERROR_RE_STRING. Else if ERROR_RE_STRING is None, then: The subcommand output will be verified against OUTPUT_TREE, and the working copy itself will be verified against DISK_TREE. If optional STATUS_TREE is given, then 'svn status' output will be compared. (This is a good way to check that revision numbers were bumped.) For the DISK_TREE verification, SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to tree.compare_trees -- see that function's doc string for more details. If CHECK_PROPS is set, then disk comparison will examine props. Return if successful, raise on failure.""" # Update and make a tree of the output. exit_code, output, errput = main.run_svn(error_re_string, 'switch', switch_url, wc_target, *args) if error_re_string: if not error_re_string.startswith(".*"): error_re_string = ".*(" + error_re_string + ")" expected_err = verify.RegexOutput(error_re_string, match_all=False) verify.verify_outputs(None, None, errput, None, expected_err) return elif errput: raise verify.SVNUnexpectedStderr(err) actual = wc.State.from_checkout(output) verify_update(actual, None, None, wc_dir_name, output_tree, None, None, disk_tree, status_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton, check_props) def process_output_for_commit(output): """Helper for run_and_verify_commit(), also used in the factory.""" # Remove the final output line, and verify that the commit succeeded. lastline = "" rest = [] def external_removal(line): return line.startswith('Removing external') \ or line.startswith('Removed external') if len(output): lastline = output.pop().strip() while len(output) and external_removal(lastline): rest.append(lastline) lastline = output.pop().strip() cm = re.compile("(Committed|Imported) revision [0-9]+.") match = cm.search(lastline) if not match: logger.warn("ERROR: commit did not succeed.") logger.warn("The final line from 'svn ci' was:") logger.warn(lastline) raise main.SVNCommitFailure # The new 'final' line in the output is either a regular line that # mentions {Adding, Deleting, Sending, ...}, or it could be a line # that says "Transmitting file data ...". If the latter case, we # want to remove the line from the output; it should be ignored when # building a tree. if len(output): lastline = output.pop() tm = re.compile("Transmitting file data.+") match = tm.search(lastline) if not match: # whoops, it was important output, put it back. output.append(lastline) if len(rest): output.extend(rest) return output def run_and_verify_commit(wc_dir_name, output_tree, status_tree, error_re_string = None, *args): """Commit and verify results within working copy WC_DIR_NAME, sending ARGS to the commit subcommand. The subcommand output will be verified against OUTPUT_TREE. If optional STATUS_TREE is given, then 'svn status' output will be compared. (This is a good way to check that revision numbers were bumped.) If ERROR_RE_STRING is None, the commit must not exit with error. If ERROR_RE_STRING is a string, the commit must exit with error, and the error message must match regular expression ERROR_RE_STRING. Return if successful, raise on failure.""" if isinstance(output_tree, wc.State): output_tree = output_tree.old_tree() if isinstance(status_tree, wc.State): status_tree = status_tree.old_tree() # Commit. if '-m' not in args and '-F' not in args: args = list(args) + ['-m', 'log msg'] exit_code, output, errput = main.run_svn(error_re_string, 'ci', *args) if error_re_string: if not error_re_string.startswith(".*"): error_re_string = ".*(" + error_re_string + ")" expected_err = verify.RegexOutput(error_re_string, match_all=False) verify.verify_outputs(None, None, errput, None, expected_err) return # Else not expecting error: # Convert the output into a tree. output = process_output_for_commit(output) actual = tree.build_tree_from_commit(output) # Verify actual output against expected output. try: tree.compare_trees("output", actual, output_tree) except tree.SVNTreeError: verify.display_trees("Output of commit is unexpected", "OUTPUT TREE", output_tree, actual) _log_tree_state("ACTUAL OUTPUT TREE:", actual, wc_dir_name) raise # Verify via 'status' command too, if possible. if status_tree: run_and_verify_status(wc_dir_name, status_tree) # This function always passes '-q' to the status command, which # suppresses the printing of any unversioned or nonexistent items. def run_and_verify_status(wc_dir_name, output_tree, singleton_handler_a = None, a_baton = None, singleton_handler_b = None, b_baton = None): """Run 'status' on WC_DIR_NAME and compare it with the expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will be passed to tree.compare_trees - see that function's doc string for more details. Returns on success, raises on failure.""" if isinstance(output_tree, wc.State): output_state = output_tree output_tree = output_tree.old_tree() else: output_state = None exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', '-q', wc_dir_name) actual = tree.build_tree_from_status(output) # Verify actual output against expected output. try: tree.compare_trees("status", actual, output_tree, singleton_handler_a, a_baton, singleton_handler_b, b_baton) except tree.SVNTreeError: verify.display_trees(None, 'STATUS OUTPUT TREE', output_tree, actual) _log_tree_state("ACTUAL STATUS TREE:", actual, wc_dir_name) raise # if we have an output State, and we can/are-allowed to create an # entries-based State, then compare the two. if output_state: entries_state = wc.State.from_entries(wc_dir_name) if entries_state: tweaked = output_state.copy() tweaked.tweak_for_entries_compare() try: tweaked.compare_and_display('entries', entries_state) except tree.SVNTreeUnequal: ### do something more raise # A variant of previous func, but doesn't pass '-q'. This allows us # to verify unversioned or nonexistent items in the list. def run_and_verify_unquiet_status(wc_dir_name, status_tree): """Run 'status' on WC_DIR_NAME and compare it with the expected STATUS_TREE. Returns on success, raises on failure.""" if isinstance(status_tree, wc.State): status_tree = status_tree.old_tree() exit_code, output, errput = main.run_svn(None, 'status', '-v', '-u', wc_dir_name) actual = tree.build_tree_from_status(output) # Verify actual output against expected output. try: tree.compare_trees("UNQUIET STATUS", actual, status_tree) except tree.SVNTreeError: _log_tree_state("ACTUAL UNQUIET STATUS TREE:", actual, wc_dir_name) raise def run_and_verify_status_xml(expected_entries = [], *args): """ Run 'status --xml' with arguments *ARGS. If successful the output is parsed into an XML document and will be verified by comparing against EXPECTED_ENTRIES. """ exit_code, output, errput = run_and_verify_svn(None, None, [], 'status', '--xml', *args) if len(errput) > 0: raise Failure doc = parseString(''.join(output)) entries = doc.getElementsByTagName('entry') def getText(nodelist): rc = [] for node in nodelist: if node.nodeType == node.TEXT_NODE: rc.append(node.data) return ''.join(rc) actual_entries = {} for entry in entries: wcstatus = entry.getElementsByTagName('wc-status')[0] commit = entry.getElementsByTagName('commit') author = entry.getElementsByTagName('author') rstatus = entry.getElementsByTagName('repos-status') actual_entry = {'wcprops' : wcstatus.getAttribute('props'), 'wcitem' : wcstatus.getAttribute('item'), } if wcstatus.hasAttribute('revision'): actual_entry['wcrev'] = wcstatus.getAttribute('revision') if (commit): actual_entry['crev'] = commit[0].getAttribute('revision') if (author): actual_entry['author'] = getText(author[0].childNodes) if (rstatus): actual_entry['rprops'] = rstatus[0].getAttribute('props') actual_entry['ritem'] = rstatus[0].getAttribute('item') actual_entries[entry.getAttribute('path')] = actual_entry if expected_entries != actual_entries: raise Failure('\n' + '\n'.join(difflib.ndiff( pprint.pformat(expected_entries).splitlines(), pprint.pformat(actual_entries).splitlines()))) def run_and_verify_diff_summarize_xml(error_re_string = [], expected_prefix = None, expected_paths = [], expected_items = [], expected_props = [], expected_kinds = [], *args): """Run 'diff --summarize --xml' with the arguments *ARGS, which should contain all arguments beyond for your 'diff --summarize --xml' omitting said arguments. EXPECTED_PREFIX will store a "common" path prefix expected to be at the beginning of each summarized path. If EXPECTED_PREFIX is None, then EXPECTED_PATHS will need to be exactly as 'svn diff --summarize --xml' will output. If ERROR_RE_STRING, the command must exit with error, and the error message must match regular expression ERROR_RE_STRING. Else if ERROR_RE_STRING is None, the subcommand output will be parsed into an XML document and will then be verified by comparing the parsed output to the contents in the EXPECTED_PATHS, EXPECTED_ITEMS, EXPECTED_PROPS and EXPECTED_KINDS. Returns on success, raises on failure.""" exit_code, output, errput = run_and_verify_svn(None, None, error_re_string, 'diff', '--summarize', '--xml', *args) # Return if errors are present since they were expected if len(errput) > 0: return doc = parseString(''.join(output)) paths = doc.getElementsByTagName("path") items = expected_items kinds = expected_kinds for path in paths: modified_path = path.childNodes[0].data if (expected_prefix is not None and modified_path.find(expected_prefix) == 0): modified_path = modified_path.replace(expected_prefix, '')[1:].strip() # Workaround single-object diff if len(modified_path) == 0: modified_path = path.childNodes[0].data.split(os.sep)[-1] # From here on, we use '/' as path separator. if os.sep != "/": modified_path = modified_path.replace(os.sep, "/") if modified_path not in expected_paths: logger.warn("ERROR: %s not expected in the changed paths.", modified_path) raise Failure index = expected_paths.index(modified_path) expected_item = items[index] expected_kind = kinds[index] expected_prop = expected_props[index] actual_item = path.getAttribute('item') actual_kind = path.getAttribute('kind') actual_prop = path.getAttribute('props') if expected_item != actual_item: logger.warn("ERROR: expected: %s actual: %s", expected_item, actual_item) raise Failure if expected_kind != actual_kind: logger.warn("ERROR: expected: %s actual: %s", expected_kind, actual_kind) raise Failure if expected_prop != actual_prop: logger.warn("ERROR: expected: %s actual: %s", expected_prop, actual_prop) raise Failure def run_and_verify_diff_summarize(output_tree, *args): """Run 'diff --summarize' with the arguments *ARGS. The subcommand output will be verified against OUTPUT_TREE. Returns on success, raises on failure. """ if isinstance(output_tree, wc.State): output_tree = output_tree.old_tree() exit_code, output, errput = main.run_svn(None, 'diff', '--summarize', *args) actual = tree.build_tree_from_diff_summarize(output) # Verify actual output against expected output. try: tree.compare_trees("output", actual, output_tree) except tree.SVNTreeError: verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual) _log_tree_state("ACTUAL DIFF OUTPUT TREE:", actual) raise def run_and_validate_lock(path, username): """`svn lock' the given path and validate the contents of the lock. Use the given username. This is important because locks are user specific.""" comment = "Locking path:%s." % path # lock the path run_and_verify_svn(None, ".*locked by user", [], 'lock', '--username', username, '-m', comment, path) # Run info and check that we get the lock fields. exit_code, output, err = run_and_verify_svn(None, None, [], 'info','-R', path) ### TODO: Leverage RegexOuput([...], match_all=True) here. # prepare the regexs to compare against token_re = re.compile(".*?Lock Token: opaquelocktoken:.*?", re.DOTALL) author_re = re.compile(".*?Lock Owner: %s\n.*?" % username, re.DOTALL) created_re = re.compile(".*?Lock Created:.*?", re.DOTALL) comment_re = re.compile(".*?%s\n.*?" % re.escape(comment), re.DOTALL) # join all output lines into one output = "".join(output) # Fail even if one regex does not match if ( not (token_re.match(output) and author_re.match(output) and created_re.match(output) and comment_re.match(output))): raise Failure def _run_and_verify_resolve(cmd, expected_paths, *args): """Run "svn CMD" (where CMD is 'resolve' or 'resolved') with arguments ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the elements of EXPECTED_PATHS as the arguments.""" # TODO: verify that the status of PATHS changes accordingly. if len(args) == 0: args = expected_paths expected_output = verify.UnorderedOutput([ "Resolved conflicted state of '" + path + "'\n" for path in expected_paths]) run_and_verify_svn(None, expected_output, [], cmd, *args) def run_and_verify_resolve(expected_paths, *args): """Run "svn resolve" with arguments ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the elements of EXPECTED_PATHS as the arguments.""" _run_and_verify_resolve('resolve', expected_paths, *args) def run_and_verify_resolved(expected_paths, *args): """Run "svn resolved" with arguments ARGS, and verify that it resolves the paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the elements of EXPECTED_PATHS as the arguments.""" _run_and_verify_resolve('resolved', expected_paths, *args) def run_and_verify_revert(expected_paths, *args): """Run "svn revert" with arguments ARGS, and verify that it reverts the paths in EXPECTED_PATHS and no others. If no ARGS are specified, use the elements of EXPECTED_PATHS as the arguments.""" if len(args) == 0: args = expected_paths expected_output = verify.UnorderedOutput([ "Reverted '" + path + "'\n" for path in expected_paths]) run_and_verify_svn(None, expected_output, [], "revert", *args) ###################################################################### # Other general utilities # This allows a test to *quickly* bootstrap itself. def make_repo_and_wc(sbox, create_wc = True, read_only = False, minor_version = None): """Create a fresh 'Greek Tree' repository and check out a WC from it. If READ_ONLY is False, a dedicated repository will be created, at the path SBOX.repo_dir. If READ_ONLY is True, the pristine repository will be used. In either case, SBOX.repo_url is assumed to point to the repository that will be used. If create_wc is True, a dedicated working copy will be checked out from the repository, at the path SBOX.wc_dir. Returns on success, raises on failure.""" # Create (or copy afresh) a new repos with a greek tree in it. if not read_only: guarantee_greek_repository(sbox.repo_dir, minor_version) if create_wc: # Generate the expected output tree. expected_output = main.greek_state.copy() expected_output.wc_dir = sbox.wc_dir expected_output.tweak(status='A ', contents=None) # Generate an expected wc tree. expected_wc = main.greek_state # Do a checkout, and verify the resulting output and disk contents. run_and_verify_checkout(sbox.repo_url, sbox.wc_dir, expected_output, expected_wc) else: # just make sure the parent folder of our working copy is created try: os.mkdir(main.general_wc_dir) except OSError, err: if err.errno != errno.EEXIST: raise # Duplicate a working copy or other dir. def duplicate_dir(wc_name, wc_copy_name): """Copy the working copy WC_NAME to WC_COPY_NAME. Overwrite any existing tree at that location.""" main.safe_rmtree(wc_copy_name) shutil.copytree(wc_name, wc_copy_name) def get_virginal_state(wc_dir, rev): "Return a virginal greek tree state for a WC and repos at revision REV." rev = str(rev) ### maybe switch rev to an integer? # copy the greek tree, shift it to the new wc_dir, insert a root elem, # then tweak all values state = main.greek_state.copy() state.wc_dir = wc_dir state.desc[''] = wc.StateItem() state.tweak(contents=None, status=' ', wc_rev=rev) return state # Cheap administrative directory locking def lock_admin_dir(wc_dir, recursive=False): "Lock a SVN administrative directory" db, root_path, relpath = wc.open_wc_db(wc_dir) svntest.main.run_wc_lock_tester(recursive, wc_dir) def set_incomplete(wc_dir, revision): "Make wc_dir incomplete at revision" svntest.main.run_wc_incomplete_tester(wc_dir, revision) def get_wc_uuid(wc_dir): "Return the UUID of the working copy at WC_DIR." return run_and_parse_info(wc_dir)[0]['Repository UUID'] def get_wc_base_rev(wc_dir): "Return the BASE revision of the working copy at WC_DIR." return run_and_parse_info(wc_dir)[0]['Revision'] def hook_failure_message(hook_name): """Return the error message that the client prints for failure of the specified hook HOOK_NAME. The wording changed with Subversion 1.5.""" if svntest.main.options.server_minor_version < 5: return "'%s' hook failed with error output:\n" % hook_name else: if hook_name in ["start-commit", "pre-commit"]: action = "Commit" elif hook_name == "pre-revprop-change": action = "Revprop change" elif hook_name == "pre-lock": action = "Lock" elif hook_name == "pre-unlock": action = "Unlock" else: action = None if action is None: message = "%s hook failed (exit code 1)" % (hook_name,) else: message = "%s blocked by %s hook (exit code 1)" % (action, hook_name) return message + " with output:\n" def create_failing_hook(repo_dir, hook_name, text): """Create a HOOK_NAME hook in the repository at REPO_DIR that prints TEXT to stderr and exits with an error.""" hook_path = os.path.join(repo_dir, 'hooks', hook_name) # Embed the text carefully: it might include characters like "%" and "'". main.create_python_hook_script(hook_path, 'import sys\n' 'sys.stderr.write(' + repr(text) + ')\n' 'sys.exit(1)\n') def enable_revprop_changes(repo_dir): """Enable revprop changes in the repository at REPO_DIR by creating a pre-revprop-change hook script and (if appropriate) making it executable.""" hook_path = main.get_pre_revprop_change_hook_path(repo_dir) main.create_python_hook_script(hook_path, 'import sys; sys.exit(0)', cmd_alternative='@exit 0') def disable_revprop_changes(repo_dir): """Disable revprop changes in the repository at REPO_DIR by creating a pre-revprop-change hook script that prints "pre-revprop-change" followed by its arguments, and returns an error.""" hook_path = main.get_pre_revprop_change_hook_path(repo_dir) main.create_python_hook_script(hook_path, 'import sys\n' 'sys.stderr.write("pre-revprop-change %s" %' ' " ".join(sys.argv[1:]))\n' 'sys.exit(1)\n', cmd_alternative= '@echo pre-revprop-change %* 1>&2\n' '@exit 1\n') def create_failing_post_commit_hook(repo_dir): """Create a post-commit hook script in the repository at REPO_DIR that always reports an error.""" hook_path = main.get_post_commit_hook_path(repo_dir) main.create_python_hook_script(hook_path, 'import sys\n' 'sys.stderr.write("Post-commit hook failed")\n' 'sys.exit(1)\n', cmd_alternative= '@echo Post-commit hook failed 1>&2\n' '@exit 1\n') # set_prop can be used for properties with NULL characters which are not # handled correctly when passed to subprocess.Popen() and values like "*" # which are not handled correctly on Windows. def set_prop(name, value, path, expected_re_string=None): """Set a property with specified value""" if value and (value[0] == '-' or '\x00' in value or sys.platform == 'win32'): from tempfile import mkstemp (fd, value_file_path) = mkstemp() os.close(fd) value_file = open(value_file_path, 'wb') value_file.write(value) value_file.flush() value_file.close() exit_code, out, err = main.run_svn(expected_re_string, 'propset', '-F', value_file_path, name, path) os.remove(value_file_path) else: exit_code, out, err = main.run_svn(expected_re_string, 'propset', name, value, path) if expected_re_string: if not expected_re_string.startswith(".*"): expected_re_string = ".*(" + expected_re_string + ")" expected_err = verify.RegexOutput(expected_re_string, match_all=False) verify.verify_outputs(None, None, err, None, expected_err) def check_prop(name, path, exp_out, revprop=None): """Verify that property NAME on PATH has a value of EXP_OUT. If REVPROP is not None, then it is a revision number and a revision property is sought.""" if revprop is not None: revprop_options = ['--revprop', '-r', revprop] else: revprop_options = [] # Not using run_svn because binary_mode must be set exit_code, out, err = main.run_command(main.svn_binary, None, 1, 'pg', '--strict', name, path, '--config-dir', main.default_config_dir, '--username', main.wc_author, '--password', main.wc_passwd, *revprop_options) if out != exp_out: logger.warn("svn pg --strict %s output does not match expected.", name) logger.warn("Expected standard output: %s\n", exp_out) logger.warn("Actual standard output: %s\n", out) raise Failure def fill_file_with_lines(wc_path, line_nbr, line_descrip=None, append=True): """Change the file at WC_PATH (adding some lines), and return its new contents. LINE_NBR indicates the line number at which the new contents should assume that it's being appended. LINE_DESCRIP is something like 'This is line' (the default) or 'Conflicting line'.""" if line_descrip is None: line_descrip = "This is line" # Generate the new contents for the file. contents = "" for n in range(line_nbr, line_nbr + 3): contents = contents + line_descrip + " " + repr(n) + " in '" + \ os.path.basename(wc_path) + "'.\n" # Write the new contents to the file. if append: main.file_append(wc_path, contents) else: main.file_write(wc_path, contents) return contents def inject_conflict_into_wc(sbox, state_path, file_path, expected_disk, expected_status, merged_rev): """Create a conflict at FILE_PATH by replacing its contents, committing the change, backdating it to its previous revision, changing its contents again, then updating it to merge in the previous change.""" wc_dir = sbox.wc_dir # Make a change to the file. contents = fill_file_with_lines(file_path, 1, "This is line", append=False) # Commit the changed file, first taking note of the current revision. prev_rev = expected_status.desc[state_path].wc_rev expected_output = wc.State(wc_dir, { state_path : wc.StateItem(verb='Sending'), }) if expected_status: expected_status.tweak(state_path, wc_rev=merged_rev) run_and_verify_commit(wc_dir, expected_output, expected_status, None, file_path) # Backdate the file. exit_code, output, errput = main.run_svn(None, "up", "-r", str(prev_rev), file_path) if expected_status: expected_status.tweak(state_path, wc_rev=prev_rev) # Make a conflicting change to the file, and backdate the file. conflicting_contents = fill_file_with_lines(file_path, 1, "Conflicting line", append=False) # Merge the previous change into the file to produce a conflict. if expected_disk: expected_disk.tweak(state_path, contents="") expected_output = wc.State(wc_dir, { state_path : wc.StateItem(status='C '), }) inject_conflict_into_expected_state(state_path, expected_disk, expected_status, conflicting_contents, contents, merged_rev) exit_code, output, errput = main.run_svn(None, "up", "-r", str(merged_rev), file_path) if expected_status: expected_status.tweak(state_path, wc_rev=merged_rev) def inject_conflict_into_expected_state(state_path, expected_disk, expected_status, wc_text, merged_text, merged_rev): """Update the EXPECTED_DISK and EXPECTED_STATUS trees for the conflict at STATE_PATH (ignored if None). WC_TEXT, MERGED_TEXT, and MERGED_REV are used to determine the contents of the conflict (the text parameters should be newline-terminated).""" if expected_disk: conflict_marker = make_conflict_marker_text(wc_text, merged_text, merged_rev) existing_text = expected_disk.desc[state_path].contents or "" expected_disk.tweak(state_path, contents=existing_text + conflict_marker) if expected_status: expected_status.tweak(state_path, status='C ') def make_conflict_marker_text(wc_text, merged_text, merged_rev): """Return the conflict marker text described by WC_TEXT (the current text in the working copy, MERGED_TEXT (the conflicting text merged in), and MERGED_REV (the revision from whence the conflicting text came).""" return "<<<<<<< .working\n" + wc_text + "=======\n" + \ merged_text + ">>>>>>> .merge-right.r" + str(merged_rev) + "\n" def build_greek_tree_conflicts(sbox): """Create a working copy that has tree-conflict markings. After this function has been called, sbox.wc_dir is a working copy that has specific tree-conflict markings. In particular, this does two conflicting sets of edits and performs an update so that tree conflicts appear. Note that this function calls sbox.build() because it needs a clean sbox. So, there is no need to call sbox.build() before this. The conflicts are the result of an 'update' on the following changes: Incoming Local A/D/G/pi text-mod del A/D/G/rho del text-mod A/D/G/tau del del This function is useful for testing that tree-conflicts are handled properly once they have appeared, e.g. that commits are blocked, that the info output is correct, etc. See also the tree-conflicts tests using deep_trees in various other .py files, and tree_conflict_tests.py. """ sbox.build() wc_dir = sbox.wc_dir j = os.path.join G = j(wc_dir, 'A', 'D', 'G') pi = j(G, 'pi') rho = j(G, 'rho') tau = j(G, 'tau') # Make incoming changes and "store them away" with a commit. main.file_append(pi, "Incoming edit.\n") main.run_svn(None, 'del', rho) main.run_svn(None, 'del', tau) expected_output = wc.State(wc_dir, { 'A/D/G/pi' : Item(verb='Sending'), 'A/D/G/rho' : Item(verb='Deleting'), 'A/D/G/tau' : Item(verb='Deleting'), }) expected_status = get_virginal_state(wc_dir, 1) expected_status.tweak('A/D/G/pi', wc_rev='2') expected_status.remove('A/D/G/rho', 'A/D/G/tau') run_and_verify_commit(wc_dir, expected_output, expected_status, None, '-m', 'Incoming changes.', wc_dir ) # Update back to the pristine state ("time-warp"). expected_output = wc.State(wc_dir, { 'A/D/G/pi' : Item(status='U '), 'A/D/G/rho' : Item(status='A '), 'A/D/G/tau' : Item(status='A '), }) expected_disk = main.greek_state expected_status = get_virginal_state(wc_dir, 1) run_and_verify_update(wc_dir, expected_output, expected_disk, expected_status, None, None, None, None, None, False, '-r', '1', wc_dir) # Make local changes main.run_svn(None, 'del', pi) main.file_append(rho, "Local edit.\n") main.run_svn(None, 'del', tau) # Update, receiving the incoming changes on top of the local changes, # causing tree conflicts. Don't check for any particular result: that is # the job of other tests. run_and_verify_svn(None, verify.AnyOutput, [], 'update', wc_dir) def make_deep_trees(base): """Helper function for deep trees conflicts. Create a set of trees, each in its own "container" dir. Any conflicts can be tested separately in each container. """ j = os.path.join # Create the container dirs. F = j(base, 'F') D = j(base, 'D') DF = j(base, 'DF') DD = j(base, 'DD') DDF = j(base, 'DDF') DDD = j(base, 'DDD') os.makedirs(F) os.makedirs(j(D, 'D1')) os.makedirs(j(DF, 'D1')) os.makedirs(j(DD, 'D1', 'D2')) os.makedirs(j(DDF, 'D1', 'D2')) os.makedirs(j(DDD, 'D1', 'D2', 'D3')) # Create their files. alpha = j(F, 'alpha') beta = j(DF, 'D1', 'beta') gamma = j(DDF, 'D1', 'D2', 'gamma') main.file_append(alpha, "This is the file 'alpha'.\n") main.file_append(beta, "This is the file 'beta'.\n") main.file_append(gamma, "This is the file 'gamma'.\n") def add_deep_trees(sbox, base_dir_name): """Prepare a "deep_trees" within a given directory. The directory / is created and a deep_tree is created within. The items are only added, a commit has to be called separately, if needed. will thus be a container for the set of containers mentioned in make_deep_trees(). """ j = os.path.join base = j(sbox.wc_dir, base_dir_name) make_deep_trees(base) main.run_svn(None, 'add', base) Item = wc.StateItem # initial deep trees state deep_trees_virginal_state = wc.State('', { 'F' : Item(), 'F/alpha' : Item("This is the file 'alpha'.\n"), 'D' : Item(), 'D/D1' : Item(), 'DF' : Item(), 'DF/D1' : Item(), 'DF/D1/beta' : Item("This is the file 'beta'.\n"), 'DD' : Item(), 'DD/D1' : Item(), 'DD/D1/D2' : Item(), 'DDF' : Item(), 'DDF/D1' : Item(), 'DDF/D1/D2' : Item(), 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\n"), 'DDD' : Item(), 'DDD/D1' : Item(), 'DDD/D1/D2' : Item(), 'DDD/D1/D2/D3' : Item(), }) # Many actions on deep trees and their resulting states... def deep_trees_leaf_edit(base): """Helper function for deep trees test cases. Append text to files, create new files in empty directories, and change leaf node properties.""" j = os.path.join F = j(base, 'F', 'alpha') DF = j(base, 'DF', 'D1', 'beta') DDF = j(base, 'DDF', 'D1', 'D2', 'gamma') main.file_append(F, "More text for file alpha.\n") main.file_append(DF, "More text for file beta.\n") main.file_append(DDF, "More text for file gamma.\n") run_and_verify_svn(None, verify.AnyOutput, [], 'propset', 'prop1', '1', F, DF, DDF) D = j(base, 'D', 'D1') DD = j(base, 'DD', 'D1', 'D2') DDD = j(base, 'DDD', 'D1', 'D2', 'D3') run_and_verify_svn(None, verify.AnyOutput, [], 'propset', 'prop1', '1', D, DD, DDD) D = j(base, 'D', 'D1', 'delta') DD = j(base, 'DD', 'D1', 'D2', 'epsilon') DDD = j(base, 'DDD', 'D1', 'D2', 'D3', 'zeta') main.file_append(D, "This is the file 'delta'.\n") main.file_append(DD, "This is the file 'epsilon'.\n") main.file_append(DDD, "This is the file 'zeta'.\n") run_and_verify_svn(None, verify.AnyOutput, [], 'add', D, DD, DDD) # deep trees state after a call to deep_trees_leaf_edit deep_trees_after_leaf_edit = wc.State('', { 'F' : Item(), 'F/alpha' : Item("This is the file 'alpha'.\nMore text for file alpha.\n"), 'D' : Item(), 'D/D1' : Item(), 'D/D1/delta' : Item("This is the file 'delta'.\n"), 'DF' : Item(), 'DF/D1' : Item(), 'DF/D1/beta' : Item("This is the file 'beta'.\nMore text for file beta.\n"), 'DD' : Item(), 'DD/D1' : Item(), 'DD/D1/D2' : Item(), 'DD/D1/D2/epsilon' : Item("This is the file 'epsilon'.\n"), 'DDF' : Item(), 'DDF/D1' : Item(), 'DDF/D1/D2' : Item(), 'DDF/D1/D2/gamma' : Item("This is the file 'gamma'.\nMore text for file gamma.\n"), 'DDD' : Item(), 'DDD/D1' : Item(), 'DDD/D1/D2' : Item(), 'DDD/D1/D2/D3' : Item(), 'DDD/D1/D2/D3/zeta' : Item("This is the file 'zeta'.\n"), }) def deep_trees_leaf_del(base): """Helper function for deep trees test cases. Delete files and empty dirs.""" j = os.path.join F = j(base, 'F', 'alpha') D = j(base, 'D', 'D1') DF = j(base, 'DF', 'D1', 'beta') DD = j(base, 'DD', 'D1', 'D2') DDF = j(base, 'DDF', 'D1', 'D2', 'gamma') DDD = j(base, 'DDD', 'D1', 'D2', 'D3') main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD) # deep trees state after a call to deep_trees_leaf_del deep_trees_after_leaf_del = wc.State('', { 'F' : Item(), 'D' : Item(), 'DF' : Item(), 'DF/D1' : Item(), 'DD' : Item(), 'DD/D1' : Item(), 'DDF' : Item(), 'DDF/D1' : Item(), 'DDF/D1/D2' : Item(), 'DDD' : Item(), 'DDD/D1' : Item(), 'DDD/D1/D2' : Item(), }) # deep trees state after a call to deep_trees_leaf_del with no commit def deep_trees_after_leaf_del_no_ci(wc_dir): if svntest.main.wc_is_singledb(wc_dir): return deep_trees_after_leaf_del else: return deep_trees_empty_dirs def deep_trees_tree_del(base): """Helper function for deep trees test cases. Delete top-level dirs.""" j = os.path.join F = j(base, 'F', 'alpha') D = j(base, 'D', 'D1') DF = j(base, 'DF', 'D1') DD = j(base, 'DD', 'D1') DDF = j(base, 'DDF', 'D1') DDD = j(base, 'DDD', 'D1') main.run_svn(None, 'rm', F, D, DF, DD, DDF, DDD) def deep_trees_rmtree(base): """Helper function for deep trees test cases. Delete top-level dirs with rmtree instead of svn del.""" j = os.path.join F = j(base, 'F', 'alpha') D = j(base, 'D', 'D1') DF = j(base, 'DF', 'D1') DD = j(base, 'DD', 'D1') DDF = j(base, 'DDF', 'D1') DDD = j(base, 'DDD', 'D1') os.unlink(F) main.safe_rmtree(D) main.safe_rmtree(DF) main.safe_rmtree(DD) main.safe_rmtree(DDF) main.safe_rmtree(DDD) # deep trees state after a call to deep_trees_tree_del deep_trees_after_tree_del = wc.State('', { 'F' : Item(), 'D' : Item(), 'DF' : Item(), 'DD' : Item(), 'DDF' : Item(), 'DDD' : Item(), }) # deep trees state without any files deep_trees_empty_dirs = wc.State('', { 'F' : Item(), 'D' : Item(), 'D/D1' : Item(), 'DF' : Item(), 'DF/D1' : Item(), 'DD' : Item(), 'DD/D1' : Item(), 'DD/D1/D2' : Item(), 'DDF' : Item(), 'DDF/D1' : Item(), 'DDF/D1/D2' : Item(), 'DDD' : Item(), 'DDD/D1' : Item(), 'DDD/D1/D2' : Item(), 'DDD/D1/D2/D3' : Item(), }) # deep trees state after a call to deep_trees_tree_del with no commit def deep_trees_after_tree_del_no_ci(wc_dir): if svntest.main.wc_is_singledb(wc_dir): return deep_trees_after_tree_del else: return deep_trees_empty_dirs def deep_trees_tree_del_repos(base): """Helper function for deep trees test cases. Delete top-level dirs, directly in the repository.""" j = '/'.join F = j([base, 'F', 'alpha']) D = j([base, 'D', 'D1']) DF = j([base, 'DF', 'D1']) DD = j([base, 'DD', 'D1']) DDF = j([base, 'DDF', 'D1']) DDD = j([base, 'DDD', 'D1']) main.run_svn(None, 'mkdir', '-m', '', F, D, DF, DD, DDF, DDD) # Expected merge/update/switch output. deep_trees_conflict_output = wc.State('', { 'F/alpha' : Item(status=' ', treeconflict='C'), 'D/D1' : Item(status=' ', treeconflict='C'), 'DF/D1' : Item(status=' ', treeconflict='C'), 'DD/D1' : Item(status=' ', treeconflict='C'), 'DDF/D1' : Item(status=' ', treeconflict='C'), 'DDD/D1' : Item(status=' ', treeconflict='C'), }) deep_trees_conflict_output_skipped = wc.State('', { 'D/D1' : Item(verb='Skipped'), 'F/alpha' : Item(verb='Skipped'), 'DD/D1' : Item(verb='Skipped'), 'DF/D1' : Item(verb='Skipped'), 'DDD/D1' : Item(verb='Skipped'), 'DDF/D1' : Item(verb='Skipped'), }) # Expected status output after merge/update/switch. deep_trees_status_local_tree_del = wc.State('', { '' : Item(status=' ', wc_rev=3), 'D' : Item(status=' ', wc_rev=3), 'D/D1' : Item(status='D ', wc_rev=2, treeconflict='C'), 'DD' : Item(status=' ', wc_rev=3), 'DD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'), 'DD/D1/D2' : Item(status='D ', wc_rev=2), 'DDD' : Item(status=' ', wc_rev=3), 'DDD/D1' : Item(status='D ', wc_rev=2, treeconflict='C'), 'DDD/D1/D2' : Item(status='D ', wc_rev=2), 'DDD/D1/D2/D3' : Item(status='D ', wc_rev=2), 'DDF' : Item(status=' ', wc_rev=3), 'DDF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'), 'DDF/D1/D2' : Item(status='D ', wc_rev=2), 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev=2), 'DF' : Item(status=' ', wc_rev=3), 'DF/D1' : Item(status='D ', wc_rev=2, treeconflict='C'), 'DF/D1/beta' : Item(status='D ', wc_rev=2), 'F' : Item(status=' ', wc_rev=3), 'F/alpha' : Item(status='D ', wc_rev=2, treeconflict='C'), }) deep_trees_status_local_leaf_edit = wc.State('', { '' : Item(status=' ', wc_rev=3), 'D' : Item(status=' ', wc_rev=3), 'D/D1' : Item(status=' M', wc_rev=2, treeconflict='C'), 'D/D1/delta' : Item(status='A ', wc_rev=0), 'DD' : Item(status=' ', wc_rev=3), 'DD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'), 'DD/D1/D2' : Item(status=' M', wc_rev=2), 'DD/D1/D2/epsilon' : Item(status='A ', wc_rev=0), 'DDD' : Item(status=' ', wc_rev=3), 'DDD/D1' : Item(status=' ', wc_rev=2, treeconflict='C'), 'DDD/D1/D2' : Item(status=' ', wc_rev=2), 'DDD/D1/D2/D3' : Item(status=' M', wc_rev=2), 'DDD/D1/D2/D3/zeta' : Item(status='A ', wc_rev=0), 'DDF' : Item(status=' ', wc_rev=3), 'DDF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'), 'DDF/D1/D2' : Item(status=' ', wc_rev=2), 'DDF/D1/D2/gamma' : Item(status='MM', wc_rev=2), 'DF' : Item(status=' ', wc_rev=3), 'DF/D1' : Item(status=' ', wc_rev=2, treeconflict='C'), 'DF/D1/beta' : Item(status='MM', wc_rev=2), 'F' : Item(status=' ', wc_rev=3), 'F/alpha' : Item(status='MM', wc_rev=2, treeconflict='C'), }) class DeepTreesTestCase: """Describes one tree-conflicts test case. See deep_trees_run_tests_scheme_for_update(), ..._switch(), ..._merge(). The name field is the subdirectory name in which the test should be run. The local_action and incoming_action are the functions to run to construct the local changes and incoming changes, respectively. See deep_trees_leaf_edit, deep_trees_tree_del, etc. The expected_* and error_re_string arguments are described in functions run_and_verify_[update|switch|merge] except expected_info, which is a dict that has path keys with values that are dicts as passed to run_and_verify_info(): expected_info = { 'F/alpha' : { 'Revision' : '3', 'Tree conflict' : '^local delete, incoming edit upon update' + ' Source left: .file.*/F/alpha@2' + ' Source right: .file.*/F/alpha@3$', }, 'DF/D1' : { 'Tree conflict' : '^local delete, incoming edit upon update' + ' Source left: .dir.*/DF/D1@2' + ' Source right: .dir.*/DF/D1@3$', }, ... } Note: expected_skip is only used in merge, i.e. using deep_trees_run_tests_scheme_for_merge. """ def __init__(self, name, local_action, incoming_action, expected_output = None, expected_disk = None, expected_status = None, expected_skip = None, error_re_string = None, commit_block_string = ".*remains in conflict.*", expected_info = None): self.name = name self.local_action = local_action self.incoming_action = incoming_action self.expected_output = expected_output self.expected_disk = expected_disk self.expected_status = expected_status self.expected_skip = expected_skip self.error_re_string = error_re_string self.commit_block_string = commit_block_string self.expected_info = expected_info def deep_trees_run_tests_scheme_for_update(sbox, greater_scheme): """ Runs a given list of tests for conflicts occuring at an update operation. This function wants to save time and perform a number of different test cases using just a single repository and performing just one commit for all test cases instead of one for each test case. 1) Each test case is initialized in a separate subdir. Each subdir again contains one set of "deep_trees", being separate container dirs for different depths of trees (F, D, DF, DD, DDF, DDD). 2) A commit is performed across all test cases and depths. (our initial state, -r2) 3) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"), its *incoming* action is performed (e.g. "deep_trees_leaf_edit"), in each of the different depth trees (F, D, DF, ... DDD). 4) A commit is performed across all test cases and depths: our "incoming" state is "stored away in the repository for now", -r3. 5) All test case dirs and contained deep_trees are time-warped (updated) back to -r2, the initial state containing deep_trees. 6) In each test case subdir (e.g. "local_tree_del_incoming_leaf_edit"), its *local* action is performed (e.g. "deep_trees_leaf_del"), in each of the different depth trees (F, D, DF, ... DDD). 7) An update to -r3 is performed across all test cases and depths. This causes tree-conflicts between the "local" state in the working copy and the "incoming" state from the repository, -r3. 8) A commit is performed in each separate container, to verify that each tree-conflict indeed blocks a commit. The sbox parameter is just the sbox passed to a test function. No need to call sbox.build(), since it is called (once) within this function. The "table" greater_scheme models all of the different test cases that should be run using a single repository. greater_scheme is a list of DeepTreesTestCase items, which define complete test setups, so that they can be performed as described above. """ j = os.path.join if not sbox.is_built(): sbox.build() wc_dir = sbox.wc_dir # 1) create directories for test_case in greater_scheme: try: add_deep_trees(sbox, test_case.name) except: logger.warn("ERROR IN: Tests scheme for update: " + "while setting up deep trees in '%s'", test_case.name) raise # 2) commit initial state main.run_svn(None, 'commit', '-m', 'initial state', wc_dir) # 3) apply incoming changes for test_case in greater_scheme: try: test_case.incoming_action(j(sbox.wc_dir, test_case.name)) except: logger.warn("ERROR IN: Tests scheme for update: " + "while performing incoming action in '%s'", test_case.name) raise # 4) commit incoming changes main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir) # 5) time-warp back to -r2 main.run_svn(None, 'update', '-r2', wc_dir) # 6) apply local changes for test_case in greater_scheme: try: test_case.local_action(j(wc_dir, test_case.name)) except: logger.warn("ERROR IN: Tests scheme for update: " + "while performing local action in '%s'", test_case.name) raise # 7) update to -r3, conflicting with incoming changes. # A lot of different things are expected. # Do separate update operations for each test case. for test_case in greater_scheme: try: base = j(wc_dir, test_case.name) x_out = test_case.expected_output if x_out != None: x_out = x_out.copy() x_out.wc_dir = base x_disk = test_case.expected_disk x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = base run_and_verify_update(base, x_out, x_disk, None, error_re_string = test_case.error_re_string) if x_status: run_and_verify_unquiet_status(base, x_status) x_info = test_case.expected_info or {} for path in x_info: run_and_verify_info([x_info[path]], j(base, path)) except: logger.warn("ERROR IN: Tests scheme for update: " + "while verifying in '%s'", test_case.name) raise # 8) Verify that commit fails. for test_case in greater_scheme: try: base = j(wc_dir, test_case.name) x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = base run_and_verify_commit(base, None, x_status, test_case.commit_block_string, base) except: logger.warn("ERROR IN: Tests scheme for update: " + "while checking commit-blocking in '%s'", test_case.name) raise def deep_trees_skipping_on_update(sbox, test_case, skip_paths, chdir_skip_paths): """ Create tree conflicts, then update again, expecting the existing tree conflicts to be skipped. SKIP_PATHS is a list of paths, relative to the "base dir", for which "update" on the "base dir" should report as skipped. CHDIR_SKIP_PATHS is a list of (target-path, skipped-path) pairs for which an update of "target-path" (relative to the "base dir") should result in "skipped-path" (relative to "target-path") being reported as skipped. """ """FURTHER_ACTION is a function that will make a further modification to each target, this being the modification that we expect to be skipped. The function takes the "base dir" (the WC path to the test case directory) as its only argument.""" further_action = deep_trees_tree_del_repos j = os.path.join wc_dir = sbox.wc_dir base = j(wc_dir, test_case.name) # Initialize: generate conflicts. (We do not check anything here.) setup_case = DeepTreesTestCase(test_case.name, test_case.local_action, test_case.incoming_action, None, None, None) deep_trees_run_tests_scheme_for_update(sbox, [setup_case]) # Make a further change to each target in the repository so there is a new # revision to update to. (This is r4.) further_action(sbox.repo_url + '/' + test_case.name) # Update whole working copy, expecting the nodes still in conflict to be # skipped. x_out = test_case.expected_output if x_out != None: x_out = x_out.copy() x_out.wc_dir = base x_disk = test_case.expected_disk x_status = test_case.expected_status if x_status != None: x_status = x_status.copy() x_status.wc_dir = base # Account for nodes that were updated by further_action x_status.tweak('', 'D', 'F', 'DD', 'DF', 'DDD', 'DDF', wc_rev=4) run_and_verify_update(base, x_out, x_disk, None, error_re_string = test_case.error_re_string) run_and_verify_unquiet_status(base, x_status) # Try to update each in-conflict subtree. Expect a 'Skipped' output for # each, and the WC status to be unchanged. for path in skip_paths: run_and_verify_update(j(base, path), wc.State(base, {path : Item(verb='Skipped')}), None, None) run_and_verify_unquiet_status(base, x_status) # Try to update each in-conflict subtree. Expect a 'Skipped' output for # each, and the WC status to be unchanged. # This time, cd to the subdir before updating it. was_cwd = os.getcwd() for path, skipped in chdir_skip_paths: if isinstance(skipped, list): expected_skip = {} for p in skipped: expected_skip[p] = Item(verb='Skipped') else: expected_skip = {skipped : Item(verb='Skipped')} p = j(base, path) run_and_verify_update(p, wc.State(p, expected_skip), None, None) os.chdir(was_cwd) run_and_verify_unquiet_status(base, x_status) # Verify that commit still fails. for path, skipped in chdir_skip_paths: run_and_verify_commit(j(base, path), None, None, test_case.commit_block_string, base) run_and_verify_unquiet_status(base, x_status) def deep_trees_run_tests_scheme_for_switch(sbox, greater_scheme): """ Runs a given list of tests for conflicts occuring at a switch operation. This function wants to save time and perform a number of different test cases using just a single repository and performing just one commit for all test cases instead of one for each test case. 1) Each test case is initialized in a separate subdir. Each subdir again contains two subdirs: one "local" and one "incoming" for the switch operation. These contain a set of deep_trees each. 2) A commit is performed across all test cases and depths. (our initial state, -r2) 3) In each test case subdir's incoming subdir, the incoming actions are performed. 4) A commit is performed across all test cases and depths. (-r3) 5) In each test case subdir's local subdir, the local actions are performed. They remain uncommitted in the working copy. 6) In each test case subdir's local dir, a switch is performed to its corresponding incoming dir. This causes conflicts between the "local" state in the working copy and the "incoming" state from the incoming subdir (still -r3). 7) A commit is performed in each separate container, to verify that each tree-conflict indeed blocks a commit. The sbox parameter is just the sbox passed to a test function. No need to call sbox.build(), since it is called (once) within this function. The "table" greater_scheme models all of the different test cases that should be run using a single repository. greater_scheme is a list of DeepTreesTestCase items, which define complete test setups, so that they can be performed as described above. """ j = os.path.join if not sbox.is_built(): sbox.build() wc_dir = sbox.wc_dir # 1) Create directories. for test_case in greater_scheme: try: base = j(sbox.wc_dir, test_case.name) os.makedirs(base) make_deep_trees(j(base, "local")) make_deep_trees(j(base, "incoming")) main.run_svn(None, 'add', base) except: logger.warn("ERROR IN: Tests scheme for switch: " + "while setting up deep trees in '%s'", test_case.name) raise # 2) Commit initial state (-r2). main.run_svn(None, 'commit', '-m', 'initial state', wc_dir) # 3) Apply incoming changes for test_case in greater_scheme: try: test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming")) except: logger.warn("ERROR IN: Tests scheme for switch: " + "while performing incoming action in '%s'", test_case.name) raise # 4) Commit all changes (-r3). main.run_svn(None, 'commit', '-m', 'incoming changes', wc_dir) # 5) Apply local changes in their according subdirs. for test_case in greater_scheme: try: test_case.local_action(j(sbox.wc_dir, test_case.name, "local")) except: logger.warn("ERROR IN: Tests scheme for switch: " + "while performing local action in '%s'", test_case.name) raise # 6) switch the local dir to the incoming url, conflicting with incoming # changes. A lot of different things are expected. # Do separate switch operations for each test case. for test_case in greater_scheme: try: local = j(wc_dir, test_case.name, "local") incoming = sbox.repo_url + "/" + test_case.name + "/incoming" x_out = test_case.expected_output if x_out != None: x_out = x_out.copy() x_out.wc_dir = local x_disk = test_case.expected_disk x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = local run_and_verify_switch(local, local, incoming, x_out, x_disk, None, test_case.error_re_string, None, None, None, None, False, '--ignore-ancestry') run_and_verify_unquiet_status(local, x_status) x_info = test_case.expected_info or {} for path in x_info: run_and_verify_info([x_info[path]], j(local, path)) except: logger.warn("ERROR IN: Tests scheme for switch: " + "while verifying in '%s'", test_case.name) raise # 7) Verify that commit fails. for test_case in greater_scheme: try: local = j(wc_dir, test_case.name, 'local') x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = local run_and_verify_commit(local, None, x_status, test_case.commit_block_string, local) except: logger.warn("ERROR IN: Tests scheme for switch: " + "while checking commit-blocking in '%s'", test_case.name) raise def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme, do_commit_local_changes, do_commit_conflicts=True, ignore_ancestry=False): """ Runs a given list of tests for conflicts occuring at a merge operation. This function wants to save time and perform a number of different test cases using just a single repository and performing just one commit for all test cases instead of one for each test case. 1) Each test case is initialized in a separate subdir. Each subdir initially contains another subdir, called "incoming", which contains a set of deep_trees. 2) A commit is performed across all test cases and depths. (a pre-initial state) 3) In each test case subdir, the "incoming" subdir is copied to "local", via the `svn copy' command. Each test case's subdir now has two sub- dirs: "local" and "incoming", initial states for the merge operation. 4) An update is performed across all test cases and depths, so that the copies made in 3) are pulled into the wc. 5) In each test case's "incoming" subdir, the incoming action is performed. 6) A commit is performed across all test cases and depths, to commit the incoming changes. If do_commit_local_changes is True, this becomes step 7 (swap steps). 7) In each test case's "local" subdir, the local_action is performed. If do_commit_local_changes is True, this becomes step 6 (swap steps). Then, in effect, the local changes are committed as well. 8) In each test case subdir, the "incoming" subdir is merged into the "local" subdir. If ignore_ancestry is True, then the merge is done with the --ignore-ancestry option, so mergeinfo is neither considered nor recorded. This causes conflicts between the "local" state in the working copy and the "incoming" state from the incoming subdir. 9) If do_commit_conflicts is True, then a commit is performed in each separate container, to verify that each tree-conflict indeed blocks a commit. The sbox parameter is just the sbox passed to a test function. No need to call sbox.build(), since it is called (once) within this function. The "table" greater_scheme models all of the different test cases that should be run using a single repository. greater_scheme is a list of DeepTreesTestCase items, which define complete test setups, so that they can be performed as described above. """ j = os.path.join if not sbox.is_built(): sbox.build() wc_dir = sbox.wc_dir # 1) Create directories. for test_case in greater_scheme: try: base = j(sbox.wc_dir, test_case.name) os.makedirs(base) make_deep_trees(j(base, "incoming")) main.run_svn(None, 'add', base) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while setting up deep trees in '%s'", test_case.name) raise # 2) Commit pre-initial state (-r2). main.run_svn(None, 'commit', '-m', 'pre-initial state', wc_dir) # 3) Copy "incoming" to "local". for test_case in greater_scheme: try: base_url = sbox.repo_url + "/" + test_case.name incoming_url = base_url + "/incoming" local_url = base_url + "/local" main.run_svn(None, 'cp', incoming_url, local_url, '-m', 'copy incoming to local') except: logger.warn("ERROR IN: Tests scheme for merge: " + "while copying deep trees in '%s'", test_case.name) raise # 4) Update to load all of the "/local" subdirs into the working copies. try: main.run_svn(None, 'up', sbox.wc_dir) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while updating local subdirs") raise # 5) Perform incoming actions for test_case in greater_scheme: try: test_case.incoming_action(j(sbox.wc_dir, test_case.name, "incoming")) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while performing incoming action in '%s'", test_case.name) raise # 6) or 7) Commit all incoming actions if not do_commit_local_changes: try: main.run_svn(None, 'ci', '-m', 'Committing incoming actions', sbox.wc_dir) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while committing incoming actions") raise # 7) or 6) Perform all local actions. for test_case in greater_scheme: try: test_case.local_action(j(sbox.wc_dir, test_case.name, "local")) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while performing local action in '%s'", test_case.name) raise # 6) or 7) Commit all incoming actions if do_commit_local_changes: try: main.run_svn(None, 'ci', '-m', 'Committing incoming and local actions', sbox.wc_dir) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while committing incoming and local actions") raise # 8) Merge all "incoming" subdirs to their respective "local" subdirs. # This creates conflicts between the local changes in the "local" wc # subdirs and the incoming states committed in the "incoming" subdirs. for test_case in greater_scheme: try: local = j(sbox.wc_dir, test_case.name, "local") incoming = sbox.repo_url + "/" + test_case.name + "/incoming" x_out = test_case.expected_output if x_out != None: x_out = x_out.copy() x_out.wc_dir = local x_disk = test_case.expected_disk x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = local x_skip = test_case.expected_skip if x_skip != None: x_skip.copy() x_skip.wc_dir = local varargs = (local,) if ignore_ancestry: varargs = varargs + ('--ignore-ancestry',) run_and_verify_merge(local, None, None, incoming, None, x_out, None, None, x_disk, None, x_skip, test_case.error_re_string, None, None, None, None, False, False, *varargs) run_and_verify_unquiet_status(local, x_status) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while verifying in '%s'", test_case.name) raise # 9) Verify that commit fails. if do_commit_conflicts: for test_case in greater_scheme: try: local = j(wc_dir, test_case.name, 'local') x_status = test_case.expected_status if x_status != None: x_status.copy() x_status.wc_dir = local run_and_verify_commit(local, None, x_status, test_case.commit_block_string, local) except: logger.warn("ERROR IN: Tests scheme for merge: " + "while checking commit-blocking in '%s'", test_case.name) raise cvs2svn-2.5.0/svntest/update.sh0000775000175100017510000000026312203665123017524 0ustar mhaggermhagger00000000000000#!/bin/sh set -ex # Update the svntest library from Subversion's subversion svn export --force http://svn.apache.org/repos/asf/subversion/trunk/subversion/tests/cmdline/svntest . cvs2svn-2.5.0/svntest/__init__.py0000664000175100017510000000343712203665123020022 0ustar mhaggermhagger00000000000000# # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # any bozos that do "from svntest import *" should die. export nothing # to the dumbasses. __all__ = [ ] import sys if sys.hexversion < 0x2050000: sys.stderr.write('[SKIPPED] at least Python 2.5 is required\n') # note: exiting is a bit harsh for a library module, but we really do # require Python 2.5. this package isn't going to work otherwise. # we're skipping this test, not failing, so exit with 0 sys.exit(0) try: import sqlite3 except ImportError: try: from pysqlite2 import dbapi2 as sqlite3 except ImportError: sys.stderr.write('[SKIPPED] Python sqlite3 module required\n') sys.exit(0) # don't export this name del sys class Failure(Exception): 'Base class for exceptions that indicate test failure' pass class Skip(Exception): 'Base class for exceptions that indicate test was skipped' pass # import in a specific order: things with the fewest circular imports first. import testcase import wc import verify import tree import sandbox import main import actions import factory cvs2svn-2.5.0/svntest/main.py0000664000175100017510000021112712203665123017204 0ustar mhaggermhagger00000000000000# # main.py: a shared, automated test suite for Subversion # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### import sys import os import shutil import re import stat import subprocess import time import threading import optparse import xml import urllib import logging import hashlib from urlparse import urlparse try: # Python >=3.0 import queue from urllib.parse import quote as urllib_parse_quote from urllib.parse import unquote as urllib_parse_unquote except ImportError: # Python <3.0 import Queue as queue from urllib import quote as urllib_parse_quote from urllib import unquote as urllib_parse_unquote import svntest from svntest import Failure from svntest import Skip SVN_VER_MINOR = 8 ###################################################################### # # HOW TO USE THIS MODULE: # # Write a new python script that # # 1) imports this 'svntest' package # # 2) contains a number of related 'test' routines. (Each test # routine should take no arguments, and return None on success # or throw a Failure exception on failure. Each test should # also contain a short docstring.) # # 3) places all the tests into a list that begins with None. # # 4) calls svntest.main.client_test() on the list. # # Also, your tests will probably want to use some of the common # routines in the 'Utilities' section below. # ##################################################################### # Global stuff default_num_threads = 5 # Don't try to use this before calling execute_tests() logger = None class SVNProcessTerminatedBySignal(Failure): "Exception raised if a spawned process segfaulted, aborted, etc." pass class SVNLineUnequal(Failure): "Exception raised if two lines are unequal" pass class SVNUnmatchedError(Failure): "Exception raised if an expected error is not found" pass class SVNCommitFailure(Failure): "Exception raised if a commit failed" pass class SVNRepositoryCopyFailure(Failure): "Exception raised if unable to copy a repository" pass class SVNRepositoryCreateFailure(Failure): "Exception raised if unable to create a repository" pass # Windows specifics if sys.platform == 'win32': windows = True file_scheme_prefix = 'file:' _exe = '.exe' _bat = '.bat' os.environ['SVN_DBG_STACKTRACES_TO_STDERR'] = 'y' else: windows = False file_scheme_prefix = 'file://' _exe = '' _bat = '' # The location of our mock svneditor script. if windows: svneditor_script = os.path.join(sys.path[0], 'svneditor.bat') else: svneditor_script = os.path.join(sys.path[0], 'svneditor.py') # Username and password used by the working copies wc_author = 'jrandom' wc_passwd = 'rayjandom' # Username and password used by the working copies for "second user" # scenarios wc_author2 = 'jconstant' # use the same password as wc_author # Set C locale for command line programs os.environ['LC_ALL'] = 'C' ###################################################################### # The locations of the svn, svnadmin and svnlook binaries, relative to # the only scripts that import this file right now (they live in ../). # Use --bin to override these defaults. svn_binary = os.path.abspath('../../svn/svn' + _exe) svnadmin_binary = os.path.abspath('../../svnadmin/svnadmin' + _exe) svnlook_binary = os.path.abspath('../../svnlook/svnlook' + _exe) svnrdump_binary = os.path.abspath('../../svnrdump/svnrdump' + _exe) svnsync_binary = os.path.abspath('../../svnsync/svnsync' + _exe) svnversion_binary = os.path.abspath('../../svnversion/svnversion' + _exe) svndumpfilter_binary = os.path.abspath('../../svndumpfilter/svndumpfilter' + \ _exe) svnmucc_binary=os.path.abspath('../../svnmucc/svnmucc' + _exe) entriesdump_binary = os.path.abspath('entries-dump' + _exe) atomic_ra_revprop_change_binary = os.path.abspath('atomic-ra-revprop-change' + \ _exe) wc_lock_tester_binary = os.path.abspath('../libsvn_wc/wc-lock-tester' + _exe) wc_incomplete_tester_binary = os.path.abspath('../libsvn_wc/wc-incomplete-tester' + _exe) # Location to the pristine repository, will be calculated from test_area_url # when we know what the user specified for --url. pristine_greek_repos_url = None # Global variable to track all of our options options = None # End of command-line-set global variables. ###################################################################### # All temporary repositories and working copies are created underneath # this dir, so there's one point at which to mount, e.g., a ramdisk. work_dir = "svn-test-work" # Constant for the merge info property. SVN_PROP_MERGEINFO = "svn:mergeinfo" # Where we want all the repositories and working copies to live. # Each test will have its own! general_repo_dir = os.path.join(work_dir, "repositories") general_wc_dir = os.path.join(work_dir, "working_copies") # temp directory in which we will create our 'pristine' local # repository and other scratch data. This should be removed when we # quit and when we startup. temp_dir = os.path.join(work_dir, 'local_tmp') # (derivatives of the tmp dir.) pristine_greek_repos_dir = os.path.join(temp_dir, "repos") greek_dump_dir = os.path.join(temp_dir, "greekfiles") default_config_dir = os.path.abspath(os.path.join(temp_dir, "config")) # # Our pristine greek-tree state. # # If a test wishes to create an "expected" working-copy tree, it should # call main.greek_state.copy(). That method will return a copy of this # State object which can then be edited. # _item = svntest.wc.StateItem greek_state = svntest.wc.State('', { 'iota' : _item("This is the file 'iota'.\n"), 'A' : _item(), 'A/mu' : _item("This is the file 'mu'.\n"), 'A/B' : _item(), 'A/B/lambda' : _item("This is the file 'lambda'.\n"), 'A/B/E' : _item(), 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"), 'A/B/E/beta' : _item("This is the file 'beta'.\n"), 'A/B/F' : _item(), 'A/C' : _item(), 'A/D' : _item(), 'A/D/gamma' : _item("This is the file 'gamma'.\n"), 'A/D/G' : _item(), 'A/D/G/pi' : _item("This is the file 'pi'.\n"), 'A/D/G/rho' : _item("This is the file 'rho'.\n"), 'A/D/G/tau' : _item("This is the file 'tau'.\n"), 'A/D/H' : _item(), 'A/D/H/chi' : _item("This is the file 'chi'.\n"), 'A/D/H/psi' : _item("This is the file 'psi'.\n"), 'A/D/H/omega' : _item("This is the file 'omega'.\n"), }) ###################################################################### # Utilities shared by the tests def wrap_ex(func, output): "Wrap a function, catch, print and ignore exceptions" def w(*args, **kwds): try: return func(*args, **kwds) except Failure, ex: if ex.__class__ != Failure or ex.args: ex_args = str(ex) if ex_args: logger.warn('EXCEPTION: %s: %s', ex.__class__.__name__, ex_args) else: logger.warn('EXCEPTION: %s', ex.__class__.__name__) return w def setup_development_mode(): "Wraps functions in module actions" l = [ 'run_and_verify_svn', 'run_and_verify_svnversion', 'run_and_verify_load', 'run_and_verify_dump', 'run_and_verify_checkout', 'run_and_verify_export', 'run_and_verify_update', 'run_and_verify_merge', 'run_and_verify_switch', 'run_and_verify_commit', 'run_and_verify_unquiet_status', 'run_and_verify_status', 'run_and_verify_diff_summarize', 'run_and_verify_diff_summarize_xml', 'run_and_validate_lock'] for func in l: setattr(svntest.actions, func, wrap_ex(getattr(svntest.actions, func))) def get_admin_name(): "Return name of SVN administrative subdirectory." if (windows or sys.platform == 'cygwin') \ and 'SVN_ASP_DOT_NET_HACK' in os.environ: return '_svn' else: return '.svn' def wc_is_singledb(wcpath): """Temporary function that checks whether a working copy directory looks like it is part of a single-db working copy.""" pristine = os.path.join(wcpath, get_admin_name(), 'pristine') if not os.path.exists(pristine): return True # Now we must be looking at a multi-db WC dir or the root dir of a # single-DB WC. Sharded 'pristine' dir => single-db, else => multi-db. for name in os.listdir(pristine): if len(name) == 2: return True elif len(name) == 40: return False return False def get_start_commit_hook_path(repo_dir): "Return the path of the start-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "start-commit") def get_pre_commit_hook_path(repo_dir): "Return the path of the pre-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-commit") def get_post_commit_hook_path(repo_dir): "Return the path of the post-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "post-commit") def get_pre_revprop_change_hook_path(repo_dir): "Return the path of the pre-revprop-change hook script in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-revprop-change") def get_pre_lock_hook_path(repo_dir): "Return the path of the pre-lock hook script in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-lock") def get_pre_unlock_hook_path(repo_dir): "Return the path of the pre-unlock hook script in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-unlock") def get_svnserve_conf_file_path(repo_dir): "Return the path of the svnserve.conf file in REPO_DIR." return os.path.join(repo_dir, "conf", "svnserve.conf") def get_fsfs_conf_file_path(repo_dir): "Return the path of the fsfs.conf file in REPO_DIR." return os.path.join(repo_dir, "db", "fsfs.conf") def get_fsfs_format_file_path(repo_dir): "Return the path of the format file in REPO_DIR." return os.path.join(repo_dir, "db", "format") def filter_dbg(lines): for line in lines: if not line.startswith('DBG:'): yield line # Run any binary, logging the command line and return code def run_command(command, error_expected, binary_mode=0, *varargs): """Run COMMAND with VARARGS. Return exit code as int; stdout, stderr as lists of lines (including line terminators). See run_command_stdin() for details. If ERROR_EXPECTED is None, any stderr output will be printed and any stderr output or a non-zero exit code will raise an exception.""" return run_command_stdin(command, error_expected, 0, binary_mode, None, *varargs) # A regular expression that matches arguments that are trivially safe # to pass on a command line without quoting on any supported operating # system: _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$') def _quote_arg(arg): """Quote ARG for a command line. Return a quoted version of the string ARG, or just ARG if it contains only universally harmless characters. WARNING: This function cannot handle arbitrary command-line arguments: it is just good enough for what we need here.""" arg = str(arg) if _safe_arg_re.match(arg): return arg if windows: # Note: subprocess.list2cmdline is Windows-specific. return subprocess.list2cmdline([arg]) else: # Quoting suitable for most Unix shells. return "'" + arg.replace("'", "'\\''") + "'" def open_pipe(command, bufsize=-1, stdin=None, stdout=None, stderr=None): """Opens a subprocess.Popen pipe to COMMAND using STDIN, STDOUT, and STDERR. BUFSIZE is passed to subprocess.Popen's argument of the same name. Returns (infile, outfile, errfile, waiter); waiter should be passed to wait_on_pipe.""" command = [str(x) for x in command] # On Windows subprocess.Popen() won't accept a Python script as # a valid program to execute, rather it wants the Python executable. if (sys.platform == 'win32') and (command[0].endswith('.py')): command.insert(0, sys.executable) command_string = command[0] + ' ' + ' '.join(map(_quote_arg, command[1:])) if not stdin: stdin = subprocess.PIPE if not stdout: stdout = subprocess.PIPE if not stderr: stderr = subprocess.PIPE p = subprocess.Popen(command, bufsize, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=not windows) return p.stdin, p.stdout, p.stderr, (p, command_string) def wait_on_pipe(waiter, binary_mode, stdin=None): """WAITER is (KID, COMMAND_STRING). Wait for KID (opened with open_pipe) to finish, dying if it does. If KID fails, create an error message containing any stdout and stderr from the kid. Show COMMAND_STRING in diagnostic messages. Normalize Windows line endings of stdout and stderr if not BINARY_MODE. Return KID's exit code as int; stdout, stderr as lists of lines (including line terminators).""" if waiter is None: return kid, command_string = waiter stdout, stderr = kid.communicate(stdin) exit_code = kid.returncode # Normalize Windows line endings if in text mode. if windows and not binary_mode: stdout = stdout.replace('\r\n', '\n') stderr = stderr.replace('\r\n', '\n') # Convert output strings to lists. stdout_lines = stdout.splitlines(True) stderr_lines = stderr.splitlines(True) if exit_code < 0: if not windows: exit_signal = os.WTERMSIG(-exit_code) else: exit_signal = exit_code if stdout_lines is not None: logger.info("".join(stdout_lines)) if stderr_lines is not None: logger.warning("".join(stderr_lines)) # show the whole path to make it easier to start a debugger logger.warning("CMD: %s terminated by signal %d" % (command_string, exit_signal)) raise SVNProcessTerminatedBySignal else: if exit_code: logger.info("CMD: %s exited with %d" % (command_string, exit_code)) return stdout_lines, stderr_lines, exit_code def spawn_process(command, bufsize=-1, binary_mode=0, stdin_lines=None, *varargs): """Run any binary, supplying input text, logging the command line. BUFSIZE dictates the pipe buffer size used in communication with the subprocess: 0 means unbuffered, 1 means line buffered, any other positive value means use a buffer of (approximately) that size. A negative bufsize means to use the system default, which usually means fully buffered. The default value for bufsize is 0 (unbuffered). Normalize Windows line endings of stdout and stderr if not BINARY_MODE. Return exit code as int; stdout, stderr as lists of lines (including line terminators).""" if stdin_lines and not isinstance(stdin_lines, list): raise TypeError("stdin_lines should have list type") # Log the command line if not command.endswith('.py'): logger.info('CMD: %s %s' % (os.path.basename(command), ' '.join([_quote_arg(x) for x in varargs]))) infile, outfile, errfile, kid = open_pipe([command] + list(varargs), bufsize) if stdin_lines: for x in stdin_lines: infile.write(x) stdout_lines, stderr_lines, exit_code = wait_on_pipe(kid, binary_mode) infile.close() outfile.close() errfile.close() return exit_code, stdout_lines, stderr_lines def run_command_stdin(command, error_expected, bufsize=-1, binary_mode=0, stdin_lines=None, *varargs): """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings which should include newline characters) to program via stdin - this should not be very large, as if the program outputs more than the OS is willing to buffer, this will deadlock, with both Python and COMMAND waiting to write to each other for ever. For tests where this is a problem, setting BUFSIZE to a sufficiently large value will prevent the deadlock, see spawn_process(). Normalize Windows line endings of stdout and stderr if not BINARY_MODE. Return exit code as int; stdout, stderr as lists of lines (including line terminators). If ERROR_EXPECTED is None, any stderr output will be printed and any stderr output or a non-zero exit code will raise an exception.""" start = time.time() exit_code, stdout_lines, stderr_lines = spawn_process(command, bufsize, binary_mode, stdin_lines, *varargs) def _line_contains_repos_diskpath(line): # ### Note: this assumes that either svn-test-work isn't a symlink, # ### or the diskpath isn't realpath()'d somewhere on the way from # ### the server's configuration and the client's stderr. We could # ### check for both the symlinked path and the realpath. return \ os.path.join('cmdline', 'svn-test-work', 'repositories') in line \ or os.path.join('cmdline', 'svn-test-work', 'local_tmp', 'repos') in line for lines, name in [[stdout_lines, "stdout"], [stderr_lines, "stderr"]]: if is_ra_type_file() or 'svnadmin' in command or 'svnlook' in command: break # Does the server leak the repository on-disk path? # (prop_tests-12 installs a hook script that does that intentionally) if any(map(_line_contains_repos_diskpath, lines)) \ and not any(map(lambda arg: 'prop_tests-12' in arg, varargs)): raise Failure("Repository diskpath in %s: %r" % (name, lines)) stop = time.time() logger.info('