3 # run_tests.py: test suite for cvs2svn
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
11 # This software is licensed as described in the file COPYING, which
12 # you should have received as part of this distribution. The terms
13 # are also available at http://subversion.tigris.org/license-1.html.
14 # If newer versions of this license are posted there, you may use a
15 # newer version instead, at your option.
17 ######################################################################
30 # This script needs to run in the correct directory. Make sure we're there.
31 if not (os
.path
.exists('cvs2svn.py') and os
.path
.exists('test-data')):
32 sys
.stderr
.write("error: I need to be run in the directory containing "
33 "'cvs2svn.py' and 'test-data'.\n")
36 # Load the Subversion test framework.
40 Skip
= svntest
.testcase
.Skip
41 XFail
= svntest
.testcase
.XFail
42 Item
= svntest
.wc
.StateItem
44 cvs2svn
= os
.path
.abspath('cvs2svn.py')
46 # We use the installed svn and svnlook binaries, instead of using
47 # svntest.main.run_svn() and svntest.main.run_svnlook(), because the
48 # behavior -- or even existence -- of local builds shouldn't affect
49 # the cvs2svn test suite.
53 test_data_dir
= 'test-data'
57 #----------------------------------------------------------------------
59 #----------------------------------------------------------------------
62 class RunProgramException
:
65 class MissingErrorException
:
68 def run_program(program
, error_re
, *varargs
):
69 """Run PROGRAM with VARARGS, return stdout as a list of lines.
70 If there is any stderr and ERROR_RE is None, raise
71 RunProgramException, and print the stderr lines if
72 svntest.main.verbose_mode is true.
74 If ERROR_RE is not None, it is a string regular expression that must
75 match some line of stderr. If it fails to match, raise
76 MissingErrorExpection."""
77 out
, err
= svntest
.main
.run_command(program
, 1, 0, *varargs
)
81 if re
.match(error_re
, line
):
83 raise MissingErrorException
85 if svntest
.main
.verbose_mode
:
86 print '\n%s said:\n' % program
90 raise RunProgramException
94 def run_cvs2svn(error_re
, *varargs
):
95 """Run cvs2svn with VARARGS, return stdout as a list of lines.
96 If there is any stderr and ERROR_RE is None, raise
97 RunProgramException, and print the stderr lines if
98 svntest.main.verbose_mode is true.
100 If ERROR_RE is not None, it is a string regular expression that must
101 match some line of stderr. If it fails to match, raise
102 MissingErrorException."""
103 # Use the same python that is running this script
104 return run_program(sys
.executable
, error_re
, cvs2svn
, *varargs
)
105 # On Windows, for an unknown reason, the cmd.exe process invoked by
106 # os.system('sort ...') in cvs2svn.py receives invalid stdio handles, if
107 # cvs2svn is started as "cvs2svn.py ...". "python cvs2svn.py ..." avoids
108 # this. Therefore, the redirection of the output to the .s-revs file fails.
109 # We no longer use the problematic invocation on any system, but this
110 # comment remains to warn about this problem.
113 def run_svn(*varargs
):
114 """Run svn with VARARGS; return stdout as a list of lines.
115 If there is any stderr, raise RunProgramException, and print the
116 stderr lines if svntest.main.verbose_mode is true."""
117 return run_program(svn
, None, *varargs
)
120 def repos_to_url(path_to_svn_repos
):
121 """This does what you think it does."""
122 rpath
= os
.path
.abspath(path_to_svn_repos
)
125 return 'file://%s' % string
.replace(rpath
, os
.sep
, '/')
127 if hasattr(time
, 'strptime'):
128 def svn_strptime(timestr
):
129 return time
.strptime(timestr
, '%Y-%m-%d %H:%M:%S')
131 # This is for Python earlier than 2.3 on Windows
132 _re_rev_date
= re
.compile(r
'(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)')
133 def svn_strptime(timestr
):
134 matches
= _re_rev_date
.match(timestr
).groups()
135 return tuple(map(int, matches
)) + (0, 1, -1)
138 def __init__(self
, revision
, author
, date
):
139 self
.revision
= revision
142 # Internally, we represent the date as seconds since epoch (UTC).
143 # Since standard subversion log output shows dates in localtime
145 # "1993-06-18 00:46:07 -0500 (Fri, 18 Jun 1993)"
147 # and time.mktime() converts from localtime, it all works out very
149 self
.date
= time
.mktime(svn_strptime(date
[0:19]))
151 # The changed paths will be accumulated later, as log data is read.
152 # Keys here are paths such as '/trunk/foo/bar', values are letter
153 # codes such as 'M', 'A', and 'D'.
154 self
.changed_paths
= { }
156 # The msg will be accumulated later, as log data is read.
159 def __cmp__(self
, other
):
160 return cmp(self
.revision
, other
.revision
) or \
161 cmp(self
.author
, other
.author
) or cmp(self
.date
, other
.date
) or \
162 cmp(self
.changed_paths
, other
.changed_paths
) or \
163 cmp(self
.msg
, other
.msg
)
166 def parse_log(svn_repos
):
167 """Return a dictionary of Logs, keyed on revision number, for SVN_REPOS."""
170 'Make a list of lines behave like an open file handle.'
171 def __init__(self
, lines
):
174 if len(self
.lines
) > 0:
175 return self
.lines
.pop(0)
179 def absorb_changed_paths(out
, log
):
180 'Read changed paths from OUT into Log item LOG, until no more.'
182 line
= out
.readline()
183 if len(line
) == 1: return
185 op_portion
= line
[3:4]
186 path_portion
= line
[5:]
187 # # We could parse out history information, but currently we
188 # # just leave it in the path portion because that's how some
191 # m = re.match("(.*) \(from /.*:[0-9]+\)", path_portion)
193 # path_portion = m.group(1)
194 log
.changed_paths
[path_portion
] = op_portion
196 def absorb_message_body(out
, num_lines
, log
):
197 'Read NUM_LINES of log message body from OUT into Log item LOG.'
200 line
= out
.readline()
204 log_start_re
= re
.compile('^r(?P<rev>[0-9]+) \| '
205 '(?P<author>[^\|]+) \| '
207 '\| (?P<lines>[0-9]+) (line|lines)$')
209 log_separator
= '-' * 72
213 out
= LineFeeder(run_svn('log', '-v', repos_to_url(svn_repos
)))
217 line
= out
.readline()
221 if line
.find(log_separator
) == 0:
222 line
= out
.readline()
225 m
= log_start_re
.match(line
)
227 this_log
= Log(int(m
.group('rev')), m
.group('author'), m
.group('date'))
228 line
= out
.readline()
229 if not line
.find('Changed paths:') == 0:
230 print 'unexpected log output (missing changed paths)'
231 print "Line: '%s'" % line
233 absorb_changed_paths(out
, this_log
)
234 absorb_message_body(out
, int(m
.group('lines')), this_log
)
235 logs
[this_log
.revision
] = this_log
237 break # We've reached the end of the log output.
239 print 'unexpected log output (missing revision line)'
240 print "Line: '%s'" % line
243 print 'unexpected log output (missing log separator)'
244 print "Line: '%s'" % line
251 """Unconditionally remove PATH and its subtree, if any. PATH may be
252 non-existent, a file or symlink, or a directory."""
253 if os
.path
.isdir(path
):
255 elif os
.path
.exists(path
):
259 def find_tag_rev(logs
, tagname
):
260 """Search LOGS for a log message containing 'TAGNAME' and return the
261 revision in which it was found."""
262 for i
in xrange(len(logs
), 0, -1):
263 if logs
[i
].msg
.find("'"+tagname
+"'") != -1:
265 raise ValueError("Tag %s not found in logs" % tagname
)
268 def sym_log_msg(symbolic_name
, is_tag
=None):
269 """Return the expected log message for a cvs2svn-synthesized revision
270 creating branch or tag SYMBOLIC_NAME."""
271 # This is a copy-paste of part of cvs2svn's make_revision_props
277 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
278 if len(symbolic_name
) >= 13:
279 space_or_newline
= '\n'
281 space_or_newline
= ' '
283 log
= "This commit was manufactured by cvs2svn to create %s%s'%s'." \
284 % (type, space_or_newline
, symbolic_name
)
289 def check_rev(logs
, rev
, msg
, changed_paths
):
290 """Verify the REV of LOGS has the MSG and CHANGED_PATHS specified."""
292 if logs
[rev
].msg
.find(msg
) != 0:
293 print "Revision %d log message was:\n%s\n" % (rev
, logs
[rev
].msg
)
294 print "It should have begun with:\n%s\n" % (msg
,)
296 if logs
[rev
].changed_paths
!= changed_paths
:
297 print "Revision %d changed paths list was:\n%s\n" % (rev
,
298 logs
[rev
].changed_paths
)
299 print "It should have been:\n%s\n" % (changed_paths
,)
302 raise svntest
.Failure
305 # List of already converted names; see the NAME argument to ensure_conversion.
307 # Keys are names, values are tuples: (svn_repos, svn_wc, log_dictionary).
308 # The log_dictionary comes from parse_log(svn_repos).
309 already_converted
= { }
311 def ensure_conversion(name
, error_re
=None, trunk_only
=None,
312 no_prune
=None, encoding
=None, passbypass
=None):
313 """Convert CVS repository NAME to Subversion, but only if it has not
314 been converted before by this invocation of this script. If it has
315 been converted before, do nothing.
317 If no error, return a tuple:
319 svn_repository_path, wc_path, log_dict
321 ...log_dict being the type of dictionary returned by parse_log().
323 If ERROR_RE is a string, it is a regular expression expected to
324 match some line of stderr printed by the conversion. If there is an
325 error and ERROR_RE is not set, then raise svntest.Failure.
327 If TRUNK_ONLY is set, then pass the --trunk-only option to cvs2svn.py
328 if converting NAME for the first time.
330 If NO_PRUNE is set, then pass the --no-prune option to cvs2svn.py
331 if converting NAME for the first time.
333 If PASSBYPASS is set, then cvs2svn is run multiple times, each time
334 with a -p option starting at 1 and increasing to a (hardcoded) maximum.
336 NAME is just one word. For example, 'main' would mean to convert
337 './test-data/main-cvsrepos', and after the conversion, the resulting
338 Subversion repository would be in './tmp/main-svnrepos', and a
339 checked out head working copy in './tmp/main-wc'."""
344 arg_list
.append( '--no-prune' )
345 conv_id
= conv_id
+ '-noprune'
347 arg_list
.append( '--trunk-only' )
348 conv_id
= conv_id
+ '-trunkonly'
350 arg_list
.append( '--encoding=' + encoding
)
351 conv_id
= conv_id
+ '-encoding=' + encoding
353 conv_id
= conv_id
+ '-passbypass'
355 if not already_converted
.has_key(conv_id
):
357 if not os
.path
.isdir(tmp_dir
):
360 cvsrepos
= os
.path
.abspath(os
.path
.join(test_data_dir
,
361 '%s-cvsrepos' % name
))
363 saved_wd
= os
.getcwd()
367 svnrepos
= '%s-svnrepos' % conv_id
368 wc
= '%s-wc' % conv_id
370 # Clean up from any previous invocations of this script.
375 arg_list
.extend( [ '--bdb-txn-nosync', '-s', svnrepos
, cvsrepos
] )
377 for p
in range(1, 9):
378 apply(run_cvs2svn
, [ error_re
, '-p', str(p
) ] + arg_list
)
380 ret
= apply(run_cvs2svn
, [ error_re
] + arg_list
)
381 except RunProgramException
:
382 raise svntest
.Failure
383 except MissingErrorException
:
384 print "Test failed because no error matched '%s'" % error_re
385 raise svntest
.Failure
387 if not os
.path
.isdir(svnrepos
):
388 print "Repository not created: '%s'" \
389 % os
.path
.join(os
.getcwd(), svnrepos
)
390 raise svntest
.Failure
392 run_svn('co', repos_to_url(svnrepos
), wc
)
393 log_dict
= parse_log(svnrepos
)
397 # This name is done for the rest of this session.
398 already_converted
[conv_id
] = (os
.path
.join('tmp', svnrepos
),
399 os
.path
.join('tmp', wc
), log_dict
)
400 except svntest
.Failure
:
401 # Remember the failure so that a future attempt to run this conversion
402 # does not bother to retry, but fails immediately.
403 already_converted
[conv_id
] = None
406 if already_converted
[conv_id
] is None:
407 raise svntest
.Failure
408 return already_converted
[conv_id
]
411 #----------------------------------------------------------------------
413 #----------------------------------------------------------------------
417 "cvs2svn with no arguments shows usage"
418 out
= run_cvs2svn(None)
419 if out
[0].find('USAGE') < 0:
420 print 'Basic cvs2svn invocation failed.'
421 raise svntest
.Failure
425 "detection of the executable flag"
426 repos
, wc
, logs
= ensure_conversion('main')
427 st
= os
.stat(os
.path
.join(wc
, 'trunk', 'single-files', 'attr-exec'))
428 if not st
[0] & stat
.S_IXUSR
:
429 raise svntest
.Failure
433 "conversion of filename with a space"
434 repos
, wc
, logs
= ensure_conversion('main')
435 if not os
.path
.exists(os
.path
.join(wc
, 'trunk', 'single-files',
437 raise svntest
.Failure
441 "two commits in quick succession"
442 repos
, wc
, logs
= ensure_conversion('main')
443 logs2
= parse_log(os
.path
.join(repos
, 'trunk', 'single-files', 'twoquick'))
445 raise svntest
.Failure
448 def prune_with_care():
449 "prune, but never too much"
450 # Robert Pluim encountered this lovely one while converting the
451 # directory src/gnu/usr.bin/cvs/contrib/pcl-cvs/ in FreeBSD's CVS
452 # repository (see issue #1302). Step 4 is the doozy:
454 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
455 # revision 2: adds trunk/blah/NEWS
456 # revision 3: deletes trunk/blah/cookie
457 # revision 4: deletes blah [re-deleting trunk/blah/cookie pruned blah!]
458 # revision 5: does nothing
460 # After fixing cvs2svn, the sequence (correctly) looks like this:
462 # revision 1: adds trunk/blah/, adds trunk/blah/cookie
463 # revision 2: adds trunk/blah/NEWS
464 # revision 3: deletes trunk/blah/cookie
465 # revision 4: does nothing [because trunk/blah/cookie already deleted]
466 # revision 5: deletes blah
468 # The difference is in 4 and 5. In revision 4, it's not correct to
469 # prune blah/, because NEWS is still in there, so revision 4 does
470 # nothing now. But when we delete NEWS in 5, that should bubble up
471 # and prune blah/ instead.
473 # ### Note that empty revisions like 4 are probably going to become
474 # ### at least optional, if not banished entirely from cvs2svn's
475 # ### output. Hmmm, or they may stick around, with an extra
476 # ### revision property explaining what happened. Need to think
477 # ### about that. In some sense, it's a bug in Subversion itself,
478 # ### that such revisions don't show up in 'svn log' output.
480 # In the test below, 'trunk/full-prune/first' represents
481 # cookie, and 'trunk/full-prune/second' represents NEWS.
483 repos
, wc
, logs
= ensure_conversion('main')
485 # Confirm that revision 4 removes '/trunk/full-prune/first',
486 # and that revision 6 removes '/trunk/full-prune'.
488 # Also confirm similar things about '/full-prune-reappear/...',
489 # which is similar, except that later on it reappears, restored
490 # from pruneland, because a file gets added to it.
492 # And finally, a similar thing for '/partial-prune/...', except that
493 # in its case, a permanent file on the top level prevents the
494 # pruning from going farther than the subdirectory containing first
498 for path
in ('/trunk/full-prune/first',
499 '/trunk/full-prune-reappear/sub/first',
500 '/trunk/partial-prune/sub/first'):
501 if not (logs
[rev
].changed_paths
.get(path
) == 'D'):
502 print "Revision %d failed to remove '%s'." % (rev
, path
)
503 raise svntest
.Failure
506 for path
in ('/trunk/full-prune',
507 '/trunk/full-prune-reappear',
508 '/trunk/partial-prune/sub'):
509 if not (logs
[rev
].changed_paths
.get(path
) == 'D'):
510 print "Revision %d failed to remove '%s'." % (rev
, path
)
511 raise svntest
.Failure
514 for path
in ('/trunk/full-prune-reappear',
515 '/trunk/full-prune-reappear/appears-later'):
516 if not (logs
[rev
].changed_paths
.get(path
) == 'A'):
517 print "Revision %d failed to create path '%s'." % (rev
, path
)
518 raise svntest
.Failure
521 def interleaved_commits():
522 "two interleaved trunk commits, different log msgs"
523 # See test-data/main-cvsrepos/proj/README.
524 repos
, wc
, logs
= ensure_conversion('main')
526 # The initial import.
528 for path
in ('/trunk/interleaved',
529 '/trunk/interleaved/1',
530 '/trunk/interleaved/2',
531 '/trunk/interleaved/3',
532 '/trunk/interleaved/4',
533 '/trunk/interleaved/5',
534 '/trunk/interleaved/a',
535 '/trunk/interleaved/b',
536 '/trunk/interleaved/c',
537 '/trunk/interleaved/d',
538 '/trunk/interleaved/e',):
539 if not (logs
[rev
].changed_paths
.get(path
) == 'A'):
540 raise svntest
.Failure
542 if logs
[rev
].msg
.find('Initial revision') != 0:
543 raise svntest
.Failure
545 # This PEP explains why we pass the 'logs' parameter to these two
546 # nested functions, instead of just inheriting it from the enclosing
547 # scope: http://www.python.org/peps/pep-0227.html
549 def check_letters(rev
, logs
):
550 'Return 1 if REV is the rev where only letters were committed, else None.'
551 for path
in ('/trunk/interleaved/a',
552 '/trunk/interleaved/b',
553 '/trunk/interleaved/c',
554 '/trunk/interleaved/d',
555 '/trunk/interleaved/e',):
556 if not (logs
[rev
].changed_paths
.get(path
) == 'M'):
558 if logs
[rev
].msg
.find('Committing letters only.') != 0:
562 def check_numbers(rev
, logs
):
563 'Return 1 if REV is the rev where only numbers were committed, else None.'
564 for path
in ('/trunk/interleaved/1',
565 '/trunk/interleaved/2',
566 '/trunk/interleaved/3',
567 '/trunk/interleaved/4',
568 '/trunk/interleaved/5',):
569 if not (logs
[rev
].changed_paths
.get(path
) == 'M'):
571 if logs
[rev
].msg
.find('Committing numbers only.') != 0:
575 # One of the commits was letters only, the other was numbers only.
576 # But they happened "simultaneously", so we don't assume anything
577 # about which commit appeared first, we just try both ways.
579 if not ((check_letters(rev
, logs
) and check_numbers(rev
+ 1, logs
))
580 or (check_numbers(rev
, logs
) and check_letters(rev
+ 1, logs
))):
581 raise svntest
.Failure
584 def simple_commits():
585 "simple trunk commits"
586 # See test-data/main-cvsrepos/proj/README.
587 repos
, wc
, logs
= ensure_conversion('main')
589 # The initial import.
591 if not logs
[rev
].changed_paths
== {
593 '/trunk/proj/default': 'A',
594 '/trunk/proj/sub1': 'A',
595 '/trunk/proj/sub1/default': 'A',
596 '/trunk/proj/sub1/subsubA': 'A',
597 '/trunk/proj/sub1/subsubA/default': 'A',
598 '/trunk/proj/sub1/subsubB': 'A',
599 '/trunk/proj/sub1/subsubB/default': 'A',
600 '/trunk/proj/sub2': 'A',
601 '/trunk/proj/sub2/default': 'A',
602 '/trunk/proj/sub2/subsubA': 'A',
603 '/trunk/proj/sub2/subsubA/default': 'A',
604 '/trunk/proj/sub3': 'A',
605 '/trunk/proj/sub3/default': 'A',
607 raise svntest
.Failure
609 if logs
[rev
].msg
.find('Initial revision') != 0:
610 raise svntest
.Failure
614 if not logs
[rev
].changed_paths
== {
615 '/trunk/proj/sub1/subsubA/default': 'M',
616 '/trunk/proj/sub3/default': 'M',
618 raise svntest
.Failure
620 if logs
[rev
].msg
.find('First commit to proj, affecting two files.') != 0:
621 raise svntest
.Failure
625 if not logs
[rev
].changed_paths
== {
626 '/trunk/proj/default': 'M',
627 '/trunk/proj/sub1/default': 'M',
628 '/trunk/proj/sub1/subsubA/default': 'M',
629 '/trunk/proj/sub1/subsubB/default': 'M',
630 '/trunk/proj/sub2/default': 'M',
631 '/trunk/proj/sub2/subsubA/default': 'M',
632 '/trunk/proj/sub3/default': 'M'
634 raise svntest
.Failure
636 if logs
[rev
].msg
.find('Second commit to proj, affecting all 7 files.') != 0:
637 raise svntest
.Failure
641 "simple tags and branches with no commits"
642 # See test-data/main-cvsrepos/proj/README.
643 repos
, wc
, logs
= ensure_conversion('main')
645 # Verify the copy source for the tags we are about to check
646 # No need to verify the copyfrom revision, as simple_commits did that
647 check_rev(logs
, 24, sym_log_msg('vendorbranch'), {
648 '/branches/vendorbranch/proj (from /trunk/proj:23)': 'A',
651 fromstr
= ' (from /branches/vendorbranch:25)'
653 # Tag on rev 1.1.1.1 of all files in proj
654 rev
= find_tag_rev(logs
, 'T_ALL_INITIAL_FILES')
655 check_rev(logs
, rev
, sym_log_msg('T_ALL_INITIAL_FILES',1), {
656 '/tags/T_ALL_INITIAL_FILES'+fromstr
: 'A',
657 '/tags/T_ALL_INITIAL_FILES/single-files': 'D',
658 '/tags/T_ALL_INITIAL_FILES/partial-prune': 'D',
661 # The same, as a branch
662 check_rev(logs
, 26, sym_log_msg('B_FROM_INITIALS'), {
663 '/branches/B_FROM_INITIALS'+fromstr
: 'A',
664 '/branches/B_FROM_INITIALS/single-files': 'D',
665 '/branches/B_FROM_INITIALS/partial-prune': 'D',
668 # Tag on rev 1.1.1.1 of all files in proj, except one
669 rev
= find_tag_rev(logs
, 'T_ALL_INITIAL_FILES_BUT_ONE')
670 check_rev(logs
, rev
, sym_log_msg('T_ALL_INITIAL_FILES_BUT_ONE',1), {
671 '/tags/T_ALL_INITIAL_FILES_BUT_ONE'+fromstr
: 'A',
672 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/single-files': 'D',
673 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/partial-prune': 'D',
674 '/tags/T_ALL_INITIAL_FILES_BUT_ONE/proj/sub1/subsubB': 'D',
677 # The same, as a branch
678 check_rev(logs
, 27, sym_log_msg('B_FROM_INITIALS_BUT_ONE'), {
679 '/branches/B_FROM_INITIALS_BUT_ONE'+fromstr
: 'A',
680 '/branches/B_FROM_INITIALS_BUT_ONE/single-files': 'D',
681 '/branches/B_FROM_INITIALS_BUT_ONE/partial-prune': 'D',
682 '/branches/B_FROM_INITIALS_BUT_ONE/proj/sub1/subsubB': 'D',
686 def simple_branch_commits():
687 "simple branch commits"
688 # See test-data/main-cvsrepos/proj/README.
689 repos
, wc
, logs
= ensure_conversion('main')
692 if not logs
[rev
].changed_paths
== {
693 '/branches/B_MIXED/proj/default': 'M',
694 '/branches/B_MIXED/proj/sub1/default': 'M',
695 '/branches/B_MIXED/proj/sub2/subsubA/default': 'M',
697 raise svntest
.Failure
699 if logs
[rev
].msg
.find('Modify three files, on branch B_MIXED.') != 0:
700 raise svntest
.Failure
703 def mixed_time_tag():
705 # See test-data/main-cvsrepos/proj/README.
706 repos
, wc
, logs
= ensure_conversion('main')
708 rev
= find_tag_rev(logs
, 'T_MIXED')
710 '/tags/T_MIXED (from /trunk:31)': 'A',
711 '/tags/T_MIXED/partial-prune': 'D',
712 '/tags/T_MIXED/single-files': 'D',
713 '/tags/T_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)': 'R',
714 '/tags/T_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
717 expected
['/tags'] = 'A'
718 if not logs
[rev
].changed_paths
== expected
:
719 raise svntest
.Failure
722 def mixed_time_branch_with_added_file():
723 "mixed-time branch, and a file added to the branch"
724 # See test-data/main-cvsrepos/proj/README.
725 repos
, wc
, logs
= ensure_conversion('main')
727 # A branch from the same place as T_MIXED in the previous test,
728 # plus a file added directly to the branch
729 check_rev(logs
, 33, sym_log_msg('B_MIXED'), {
730 '/branches/B_MIXED (from /trunk:31)': 'A',
731 '/branches/B_MIXED/partial-prune': 'D',
732 '/branches/B_MIXED/single-files': 'D',
733 '/branches/B_MIXED/proj/sub2/subsubA (from /trunk/proj/sub2/subsubA:23)':
735 '/branches/B_MIXED/proj/sub3 (from /trunk/proj/sub3:30)': 'R',
738 check_rev(logs
, 34, 'Add a file on branch B_MIXED.', {
739 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'A',
744 "a commit affecting both trunk and a branch"
745 # See test-data/main-cvsrepos/proj/README.
746 repos
, wc
, logs
= ensure_conversion('main')
748 check_rev(logs
, 36, 'A single commit affecting one file on branch B_MIXED '
749 'and one on trunk.', {
750 '/trunk/proj/sub2/default': 'M',
751 '/branches/B_MIXED/proj/sub2/branch_B_MIXED_only': 'M',
755 def split_time_branch():
756 "branch some trunk files, and later branch the rest"
757 # See test-data/main-cvsrepos/proj/README.
758 repos
, wc
, logs
= ensure_conversion('main')
761 # First change on the branch, creating it
762 check_rev(logs
, rev
, sym_log_msg('B_SPLIT'), {
763 '/branches/B_SPLIT (from /trunk:36)': 'A',
764 '/branches/B_SPLIT/partial-prune': 'D',
765 '/branches/B_SPLIT/single-files': 'D',
766 '/branches/B_SPLIT/proj/sub1/subsubB': 'D',
769 check_rev(logs
, rev
+ 1, 'First change on branch B_SPLIT.', {
770 '/branches/B_SPLIT/proj/default': 'M',
771 '/branches/B_SPLIT/proj/sub1/default': 'M',
772 '/branches/B_SPLIT/proj/sub1/subsubA/default': 'M',
773 '/branches/B_SPLIT/proj/sub2/default': 'M',
774 '/branches/B_SPLIT/proj/sub2/subsubA/default': 'M',
777 # A trunk commit for the file which was not branched
778 check_rev(logs
, rev
+ 2, 'A trunk change to sub1/subsubB/default. '
779 'This was committed about an', {
780 '/trunk/proj/sub1/subsubB/default': 'M',
783 # Add the file not already branched to the branch, with modification:w
784 check_rev(logs
, rev
+ 3, sym_log_msg('B_SPLIT'), {
785 '/branches/B_SPLIT/proj/sub1/subsubB (from /trunk/proj/sub1/subsubB:44)': 'A',
788 check_rev(logs
, rev
+ 4, 'This change affects sub3/default and '
789 'sub1/subsubB/default, on branch', {
790 '/branches/B_SPLIT/proj/sub1/subsubB/default': 'M',
791 '/branches/B_SPLIT/proj/sub3/default': 'M',
796 "conversion of invalid symbolic names"
797 ret
, ign
, ign
= ensure_conversion('bogus-tag')
800 def overlapping_branch():
801 "ignore a file with a branch with two names"
802 repos
, wc
, logs
= ensure_conversion('overlapping-branch',
803 '.*cannot also have name \'vendorB\'')
804 nonlap_path
= '/trunk/nonoverlapping-branch'
805 lap_path
= '/trunk/overlapping-branch'
807 if not (logs
[rev
].changed_paths
.get('/branches/vendorA (from /trunk:3)')
809 raise svntest
.Failure
810 # We don't know what order the first two commits would be in, since
811 # they have different log messages but the same timestamps. As only
812 # one of the files would be on the vendorB branch in the regression
813 # case being tested here, we allow for either order.
814 if ((logs
[rev
].changed_paths
.get('/branches/vendorB (from /trunk:2)')
816 or (logs
[rev
].changed_paths
.get('/branches/vendorB (from /trunk:3)')
818 raise svntest
.Failure
819 if not logs
[rev
+ 1].changed_paths
== {}:
820 raise svntest
.Failure
821 if len(logs
) != rev
+ 1:
822 raise svntest
.Failure
825 def phoenix_branch():
826 "convert a branch file rooted in a 'dead' revision"
827 repos
, wc
, logs
= ensure_conversion('phoenix')
828 check_rev(logs
, 8, sym_log_msg('volsung_20010721'), {
829 '/branches/volsung_20010721 (from /trunk:7)': 'A',
830 '/branches/volsung_20010721/file.txt' : 'D'
832 check_rev(logs
, 9, 'This file was supplied by Jack Moffitt', {
833 '/branches/volsung_20010721/phoenix': 'A' })
836 ###TODO: We check for 4 changed paths here to accomodate creating tags
837 ###and branches in rev 1, but that will change, so this will
838 ###eventually change back.
839 def ctrl_char_in_log():
840 "handle a control char in a log message"
841 # This was issue #1106.
843 repos
, wc
, logs
= ensure_conversion('ctrl-char-in-log')
844 if not ((logs
[rev
].changed_paths
.get('/trunk/ctrl-char-in-log') == 'A')
845 and (len(logs
[rev
].changed_paths
) == 1)):
846 print "Revision 2 of 'ctrl-char-in-log,v' was not converted successfully."
847 raise svntest
.Failure
848 if logs
[rev
].msg
.find('\x04') < 0:
849 print "Log message of 'ctrl-char-in-log,v' (rev 2) is wrong."
850 raise svntest
.Failure
854 "handle tags rooted in a redeleted revision"
855 repos
, wc
, logs
= ensure_conversion('overdead')
858 def no_trunk_prune():
859 "ensure that trunk doesn't get pruned"
860 repos
, wc
, logs
= ensure_conversion('overdead')
861 for rev
in logs
.keys():
863 for changed_path
in rev_logs
.changed_paths
.keys():
864 if changed_path
== '/trunk' \
865 and rev_logs
.changed_paths
[changed_path
] == 'D':
866 raise svntest
.Failure
870 "file deleted twice, in the root of the repository"
871 # This really tests several things: how we handle a file that's
872 # removed (state 'dead') in two successive revisions; how we
873 # handle a file in the root of the repository (there were some
874 # bugs in cvs2svn's svn path construction for top-level files); and
875 # the --no-prune option.
876 repos
, wc
, logs
= ensure_conversion('double-delete', None, 1, 1)
878 path
= '/trunk/twice-removed'
880 if not (logs
[rev
].changed_paths
.get(path
) == 'A'):
881 raise svntest
.Failure
883 if logs
[rev
].msg
.find('Initial revision') != 0:
884 raise svntest
.Failure
886 if not (logs
[rev
+ 1].changed_paths
.get(path
) == 'D'):
887 raise svntest
.Failure
889 if logs
[rev
+ 1].msg
.find('Remove this file for the first time.') != 0:
890 raise svntest
.Failure
892 if logs
[rev
+ 1].changed_paths
.has_key('/trunk'):
893 raise svntest
.Failure
897 "branch created from both trunk and another branch"
898 # See test-data/split-branch-cvsrepos/README.
900 # The conversion will fail if the bug is present, and
901 # ensure_conversion would raise svntest.Failure.
902 repos
, wc
, logs
= ensure_conversion('split-branch')
905 def resync_misgroups():
906 "resyncing should not misorder commit groups"
907 # See test-data/resync-misgroups-cvsrepos/README.
909 # The conversion will fail if the bug is present, and
910 # ensure_conversion would raise svntest.Failure.
911 repos
, wc
, logs
= ensure_conversion('resync-misgroups')
914 def tagged_branch_and_trunk():
915 "allow tags with mixed trunk and branch sources"
916 repos
, wc
, logs
= ensure_conversion('tagged-branch-n-trunk')
917 a_path
= os
.path
.join(wc
, 'tags', 'some-tag', 'a.txt')
918 b_path
= os
.path
.join(wc
, 'tags', 'some-tag', 'b.txt')
919 if not (os
.path
.exists(a_path
) and os
.path
.exists(b_path
)):
920 raise svntest
.Failure
921 if (open(a_path
, 'r').read().find('1.24') == -1) \
922 or (open(b_path
, 'r').read().find('1.5') == -1):
923 raise svntest
.Failure
927 "never use the rev-in-progress as a copy source"
928 # See issue #1427 and r8544.
929 repos
, wc
, logs
= ensure_conversion('enroot-race')
931 if not logs
[rev
].changed_paths
== {
932 '/branches/mybranch (from /trunk:7)': 'A',
933 '/branches/mybranch/proj/a.txt': 'D',
934 '/branches/mybranch/proj/b.txt': 'D',
936 raise svntest
.Failure
937 if not logs
[rev
+ 1].changed_paths
== {
938 '/branches/mybranch/proj/c.txt': 'M',
939 '/trunk/proj/a.txt': 'M',
940 '/trunk/proj/b.txt': 'M',
942 raise svntest
.Failure
945 def enroot_race_obo():
946 "do use the last completed rev as a copy source"
947 repos
, wc
, logs
= ensure_conversion('enroot-race-obo')
948 if not ((len(logs
) == 3) and
949 (logs
[3].changed_paths
.get('/branches/BRANCH (from /trunk:2)') == 'A')):
950 raise svntest
.Failure
953 def branch_delete_first():
954 "correctly handle deletion as initial branch action"
955 # See test-data/branch-delete-first-cvsrepos/README.
957 # The conversion will fail if the bug is present, and
958 # ensure_conversion would raise svntest.Failure.
959 repos
, wc
, logs
= ensure_conversion('branch-delete-first')
961 # 'file' was deleted from branch-1 and branch-2, but not branch-3
962 if os
.path
.exists(os
.path
.join(wc
, 'branches', 'branch-1', 'file')):
963 raise svntest
.Failure
964 if os
.path
.exists(os
.path
.join(wc
, 'branches', 'branch-2', 'file')):
965 raise svntest
.Failure
966 if not os
.path
.exists(os
.path
.join(wc
, 'branches', 'branch-3', 'file')):
967 raise svntest
.Failure
970 def nonascii_filenames():
971 "non ascii files converted incorrectly"
974 # on a en_US.iso-8859-1 machine this test fails with
975 # svn: Can't recode ...
977 # as described in the issue
979 # on a en_US.UTF-8 machine this test fails with
980 # svn: Malformed XML ...
982 # which means at least it fails. Unfortunately it won't fail
983 # with the same error...
985 # mangle current locale settings so we know we're not running
986 # a UTF-8 locale (which does not exhibit this problem)
987 current_locale
= locale
.getlocale()
988 new_locale
= 'en_US.ISO8859-1'
989 locale_changed
= None
992 # change locale to non-UTF-8 locale to generate latin1 names
993 locale
.setlocale(locale
.LC_ALL
, # this might be too broad?
1000 testdata_path
= os
.path
.abspath('test-data')
1001 srcrepos_path
= os
.path
.join(testdata_path
,'main-cvsrepos')
1002 dstrepos_path
= os
.path
.join(testdata_path
,'non-ascii-cvsrepos')
1003 if not os
.path
.exists(dstrepos_path
):
1004 # create repos from existing main repos
1005 shutil
.copytree(srcrepos_path
, dstrepos_path
)
1006 base_path
= os
.path
.join(dstrepos_path
, 'single-files')
1007 shutil
.copyfile(os
.path
.join(base_path
, 'twoquick,v'),
1008 os
.path
.join(base_path
, 'two\366uick,v'))
1009 new_path
= os
.path
.join(dstrepos_path
, 'single\366files')
1010 os
.rename(base_path
, new_path
)
1012 # if ensure_conversion can generate a
1013 repos
, wc
, logs
= ensure_conversion('non-ascii', encoding
='latin1')
1016 locale
.setlocale(locale
.LC_ALL
, current_locale
)
1017 shutil
.rmtree(dstrepos_path
)
1020 def vendor_branch_sameness():
1021 "avoid spurious changes for initial revs "
1022 repos
, wc
, logs
= ensure_conversion('vendor-branch-sameness')
1024 # There are four files in the repository:
1026 # a.txt: Imported in the traditional way; 1.1 and 1.1.1.1 have
1027 # the same contents, the file's default branch is 1.1.1,
1028 # and both revisions are in state 'Exp'.
1030 # b.txt: Like a.txt, except that 1.1.1.1 has a real change from
1031 # 1.1 (the addition of a line of text).
1033 # c.txt: Like a.txt, except that 1.1.1.1 is in state 'dead'.
1035 # d.txt: This file was created by 'cvs add' instead of import, so
1036 # it has only 1.1 -- no 1.1.1.1, and no default branch.
1037 # The timestamp on the add is exactly the same as for the
1038 # imports of the other files.
1040 # (Log messages for the same revisions are the same in all files.)
1042 # What we expect to see is everyone added in r1, then trunk/proj
1043 # copied in r2. In the copy, only a.txt should be left untouched;
1044 # b.txt should be 'M'odified, and (for different reasons) c.txt and
1045 # d.txt should be 'D'eleted.
1048 ###TODO Convert to check_rev.
1049 if logs
[rev
].msg
.find('Initial revision') != 0:
1050 raise svntest
.Failure
1052 if not logs
[rev
].changed_paths
== {
1053 '/trunk/proj' : 'A',
1054 '/trunk/proj/a.txt' : 'A',
1055 '/trunk/proj/b.txt' : 'A',
1056 '/trunk/proj/c.txt' : 'A',
1057 '/trunk/proj/d.txt' : 'A',
1059 raise svntest
.Failure
1061 if logs
[rev
+ 1].msg
.find(sym_log_msg('vbranchA')) != 0:
1062 raise svntest
.Failure
1064 if not logs
[rev
+ 1].changed_paths
== {
1065 '/branches/vbranchA (from /trunk:2)' : 'A',
1066 '/branches/vbranchA/proj/d.txt' : 'D',
1068 raise svntest
.Failure
1070 if logs
[rev
+ 2].msg
.find('First vendor branch revision.') != 0:
1071 raise svntest
.Failure
1073 if not logs
[rev
+ 2].changed_paths
== {
1074 '/branches/vbranchA/proj/b.txt' : 'M',
1075 '/branches/vbranchA/proj/c.txt' : 'D',
1077 raise svntest
.Failure
1080 def default_branches():
1081 "handle default branches correctly "
1082 repos
, wc
, logs
= ensure_conversion('default-branches')
1084 # There are seven files in the repository:
1087 # Imported in the traditional way, so 1.1 and 1.1.1.1 are the
1088 # same. Then 1.1.1.2 and 1.1.1.3 were imported, then 1.2
1089 # committed (thus losing the default branch "1.1.1"), then
1090 # 1.1.1.4 was imported. All vendor import release tags are
1094 # Like a.txt, but without rev 1.2.
1097 # Exactly like b.txt, just s/b.txt/c.txt/ in content.
1100 # Same as the previous two, but 1.1.1 branch is unlabeled.
1103 # Same, but missing 1.1.1 label and all tags but 1.1.1.3.
1105 # deleted-on-vendor-branch.txt,v:
1106 # Like b.txt and c.txt, except that 1.1.1.3 is state 'dead'.
1108 # added-then-imported.txt,v:
1109 # Added with 'cvs add' to create 1.1, then imported with
1110 # completely different contents to create 1.1.1.1, therefore
1111 # never had a default branch.
1114 check_rev(logs
, 18, sym_log_msg('vtag-4',1), {
1115 '/tags/vtag-4 (from /branches/vbranchA:16)' : 'A',
1116 '/tags/vtag-4/proj/d.txt '
1117 '(from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'A',
1120 check_rev(logs
, 6, sym_log_msg('vtag-1',1), {
1121 '/tags/vtag-1 (from /branches/vbranchA:5)' : 'A',
1122 '/tags/vtag-1/proj/d.txt '
1123 '(from /branches/unlabeled-1.1.1/proj/d.txt:5)' : 'A',
1126 check_rev(logs
, 9, sym_log_msg('vtag-2',1), {
1127 '/tags/vtag-2 (from /branches/vbranchA:7)' : 'A',
1128 '/tags/vtag-2/proj/d.txt '
1129 '(from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'A',
1132 check_rev(logs
, 12, sym_log_msg('vtag-3',1), {
1133 '/tags/vtag-3 (from /branches/vbranchA:10)' : 'A',
1134 '/tags/vtag-3/proj/d.txt '
1135 '(from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'A',
1136 '/tags/vtag-3/proj/e.txt '
1137 '(from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'A',
1140 check_rev(logs
, 17, "This commit was generated by cvs2svn "
1141 "to compensate for changes in r16,", {
1142 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:16)' : 'R',
1143 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:16)' : 'R',
1144 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:16)' : 'R',
1145 '/trunk/proj/deleted-on-vendor-branch.txt '
1146 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:16)' : 'A',
1147 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:16)' : 'R',
1150 check_rev(logs
, 16, "Import (vbranchA, vtag-4).", {
1151 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1152 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1153 '/branches/vbranchA/proj/a.txt' : 'M',
1154 '/branches/vbranchA/proj/added-then-imported.txt': 'M', # CHECK!!!
1155 '/branches/vbranchA/proj/b.txt' : 'M',
1156 '/branches/vbranchA/proj/c.txt' : 'M',
1157 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'A',
1160 check_rev(logs
, 15, sym_log_msg('vbranchA'), {
1161 '/branches/vbranchA/proj/added-then-imported.txt '
1162 '(from /trunk/proj/added-then-imported.txt:14)' : 'A',
1165 check_rev(logs
, 14, "Add a file to the working copy.", {
1166 '/trunk/proj/added-then-imported.txt' : 'A',
1169 check_rev(logs
, 13, "First regular commit, to a.txt, on vtag-3.", {
1170 '/trunk/proj/a.txt' : 'M',
1173 check_rev(logs
, 11, "This commit was generated by cvs2svn "
1174 "to compensate for changes in r10,", {
1175 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:10)' : 'R',
1176 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:10)' : 'R',
1177 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:10)' : 'R',
1178 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:10)' : 'R',
1179 '/trunk/proj/deleted-on-vendor-branch.txt' : 'D',
1180 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:10)' : 'R',
1183 check_rev(logs
, 10, "Import (vbranchA, vtag-3).", {
1184 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1185 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1186 '/branches/vbranchA/proj/a.txt' : 'M',
1187 '/branches/vbranchA/proj/b.txt' : 'M',
1188 '/branches/vbranchA/proj/c.txt' : 'M',
1189 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'D',
1192 check_rev(logs
, 8, "This commit was generated by cvs2svn "
1193 "to compensate for changes in r7,", {
1194 '/trunk/proj/a.txt (from /branches/vbranchA/proj/a.txt:7)' : 'R',
1195 '/trunk/proj/b.txt (from /branches/vbranchA/proj/b.txt:7)' : 'R',
1196 '/trunk/proj/c.txt (from /branches/vbranchA/proj/c.txt:7)' : 'R',
1197 '/trunk/proj/d.txt (from /branches/unlabeled-1.1.1/proj/d.txt:7)' : 'R',
1198 '/trunk/proj/deleted-on-vendor-branch.txt '
1199 '(from /branches/vbranchA/proj/deleted-on-vendor-branch.txt:7)' : 'R',
1200 '/trunk/proj/e.txt (from /branches/unlabeled-1.1.1/proj/e.txt:7)' : 'R',
1203 check_rev(logs
, 7, "Import (vbranchA, vtag-2).", {
1204 '/branches/unlabeled-1.1.1/proj/d.txt' : 'M',
1205 '/branches/unlabeled-1.1.1/proj/e.txt' : 'M',
1206 '/branches/vbranchA/proj/a.txt' : 'M',
1207 '/branches/vbranchA/proj/b.txt' : 'M',
1208 '/branches/vbranchA/proj/c.txt' : 'M',
1209 '/branches/vbranchA/proj/deleted-on-vendor-branch.txt' : 'M',
1212 check_rev(logs
, 5, "Import (vbranchA, vtag-1).", {})
1214 check_rev(logs
, 4, sym_log_msg('vbranchA'), {
1215 '/branches/vbranchA (from /trunk:2)' : 'A',
1216 '/branches/vbranchA/proj/d.txt' : 'D',
1217 '/branches/vbranchA/proj/e.txt' : 'D',
1220 check_rev(logs
, 3, sym_log_msg('unlabeled-1.1.1'), {
1221 '/branches/unlabeled-1.1.1 (from /trunk:2)' : 'A',
1222 '/branches/unlabeled-1.1.1/proj/a.txt' : 'D',
1223 '/branches/unlabeled-1.1.1/proj/b.txt' : 'D',
1224 '/branches/unlabeled-1.1.1/proj/c.txt' : 'D',
1225 '/branches/unlabeled-1.1.1/proj/deleted-on-vendor-branch.txt' : 'D',
1228 check_rev(logs
, 2, "Initial revision", {
1229 '/trunk/proj' : 'A',
1230 '/trunk/proj/a.txt' : 'A',
1231 '/trunk/proj/b.txt' : 'A',
1232 '/trunk/proj/c.txt' : 'A',
1233 '/trunk/proj/d.txt' : 'A',
1234 '/trunk/proj/deleted-on-vendor-branch.txt' : 'A',
1235 '/trunk/proj/e.txt' : 'A',
1239 def compose_tag_three_sources():
1240 "compose a tag from three sources"
1241 repos
, wc
, logs
= ensure_conversion('compose-tag-three-sources')
1243 check_rev(logs
, 2, "Add on trunk", {
1244 '/trunk/tagged-on-trunk-1.2-a': 'A',
1245 '/trunk/tagged-on-trunk-1.2-b': 'A',
1246 '/trunk/tagged-on-trunk-1.1': 'A',
1247 '/trunk/tagged-on-b1': 'A',
1248 '/trunk/tagged-on-b2': 'A',
1251 check_rev(logs
, 3, sym_log_msg('b1'), {
1252 '/branches/b1 (from /trunk:2)': 'A',
1255 check_rev(logs
, 4, sym_log_msg('b2'), {
1256 '/branches/b2 (from /trunk:2)': 'A',
1259 check_rev(logs
, 5, "Commit on branch b1", {
1260 '/branches/b1/tagged-on-trunk-1.2-a': 'M',
1261 '/branches/b1/tagged-on-trunk-1.2-b': 'M',
1262 '/branches/b1/tagged-on-trunk-1.1': 'M',
1263 '/branches/b1/tagged-on-b1': 'M',
1264 '/branches/b1/tagged-on-b2': 'M',
1267 check_rev(logs
, 6, "Commit on branch b2", {
1268 '/branches/b2/tagged-on-trunk-1.2-a': 'M',
1269 '/branches/b2/tagged-on-trunk-1.2-b': 'M',
1270 '/branches/b2/tagged-on-trunk-1.1': 'M',
1271 '/branches/b2/tagged-on-b1': 'M',
1272 '/branches/b2/tagged-on-b2': 'M',
1275 check_rev(logs
, 7, "Commit again on trunk", {
1276 '/trunk/tagged-on-trunk-1.2-a': 'M',
1277 '/trunk/tagged-on-trunk-1.2-b': 'M',
1278 '/trunk/tagged-on-trunk-1.1': 'M',
1279 '/trunk/tagged-on-b1': 'M',
1280 '/trunk/tagged-on-b2': 'M',
1283 check_rev(logs
, 8, sym_log_msg('T',1), {
1284 '/tags/T (from /trunk:7)': 'A',
1285 '/tags/T/tagged-on-b2 (from /branches/b2/tagged-on-b2:7)': 'R',
1286 '/tags/T/tagged-on-trunk-1.1 (from /trunk/tagged-on-trunk-1.1:2)': 'R',
1287 '/tags/T/tagged-on-b1 (from /branches/b1/tagged-on-b1:7)': 'R',
1291 def pass5_when_to_fill():
1292 "reserve a svn revnum for a fill only when required"
1293 # The conversion will fail if the bug is present, and
1294 # ensure_conversion would raise svntest.Failure.
1295 repos
, wc
, logs
= ensure_conversion('pass5-when-to-fill')
1299 "don't break when the trunk is empty"
1300 # The conversion will fail if the bug is present, and
1301 # ensure_conversion would raise svntest.Failure.
1302 repos
, wc
, logs
= ensure_conversion('empty-trunk')
1304 def no_spurious_svn_commits():
1305 "ensure that we don't create any spurious commits"
1306 repos
, wc
, logs
= ensure_conversion('phoenix')
1308 # Check spurious commit that could be created in CVSCommit._pre_commit
1309 # (When you add a file on a branch, CVS creates a trunk revision
1310 # in state 'dead'. If the log message of that commit is equal to
1311 # the one that CVS generates, we do not ever create a 'fill'
1312 # SVNCommit for it.)
1314 # and spurious commit that could be created in CVSCommit._commit
1315 # (When you add a file on a branch, CVS creates a trunk revision
1316 # in state 'dead'. If the log message of that commit is equal to
1317 # the one that CVS generates, we do not create a primary SVNCommit
1319 check_rev(logs
, 18, 'File added on branch xiphophorus', {
1320 '/branches/xiphophorus/added-on-branch.txt' : 'A',
1323 # Check to make sure that a commit *is* generated:
1324 # (When you add a file on a branch, CVS creates a trunk revision
1325 # in state 'dead'. If the log message of that commit is NOT equal
1326 # to the one that CVS generates, we create a primary SVNCommit to
1327 # serve as a home for the log message in question.
1328 check_rev(logs
, 19, 'file added-on-branch2.txt was initially added on '
1329 + 'branch xiphophorus,\nand this log message was tweaked', {})
1331 # Check spurious commit that could be created in
1332 # CVSRevisionAggregator.attempt_to_commit_symbols
1333 # (We shouldn't consider a CVSRevision whose op is OP_DEAD as a
1334 # candidate for the LastSymbolicNameDatabase.
1335 check_rev(logs
, 20, 'This file was also added on branch xiphophorus,', {
1336 '/branches/xiphophorus/added-on-branch2.txt' : 'A',
1340 def peer_path_pruning():
1341 "make sure that filling prunes paths correctly"
1342 repos
, wc
, logs
= ensure_conversion('peer-path-pruning')
1343 check_rev(logs
, 8, sym_log_msg('BRANCH'), {
1344 '/branches/BRANCH (from /trunk:6)' : 'A',
1345 '/branches/BRANCH/bar' : 'D',
1346 '/branches/BRANCH/foo (from /trunk/foo:7)' : 'R',
1349 def invalid_closings_on_trunk():
1350 "verify correct revs are copied to default branches"
1351 # The conversion will fail if the bug is present, and
1352 # ensure_conversion would raise svntest.Failure.
1353 repos
, wc
, logs
= ensure_conversion('invalid-closings-on-trunk')
1356 def individual_passes():
1357 "run each pass individually"
1358 repos
, wc
, logs
= ensure_conversion('main')
1359 repos2
, wc2
, logs2
= ensure_conversion('main', passbypass
=1)
1362 raise svntest
.Failure
1366 "reveal a big bug in our resync algorithm"
1367 # This will fail if the bug is present
1368 repos
, wc
, logs
= ensure_conversion('resync-bug')
1371 #----------------------------------------------------------------------
1373 ########################################################################
1376 # list all tests here, starting with None:
1383 interleaved_commits
,
1386 simple_branch_commits
,
1388 mixed_time_branch_with_added_file
,
1400 tagged_branch_and_trunk
,
1403 branch_delete_first
,
1405 vendor_branch_sameness
,
1407 compose_tag_three_sources
,
1411 no_spurious_svn_commits
,
1412 invalid_closings_on_trunk
,
1417 if __name__
== '__main__':
1418 svntest
.main
.run_tests(test_list
)