Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / contrib / client-side / svnmerge / svnmerge_test.py
blob25fcd54a5948acfd7d8aaffcae9a3eda41c1e68b
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Copyright (c) 2005, Giovanni Bajo
4 # All rights reserved.
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 import sys, os
20 import types
21 import re
22 import unittest
23 from cStringIO import StringIO
24 import shutil
25 import svnmerge
26 import stat
27 import atexit
28 import getopt
30 ####
31 # IMPORTANT NOTE TO TEST AUTHORS
33 # Any quoted strings inside the arguments of the parameter "cmd" must
34 # be enclosed in double-, not single-quotes, so that the command parser
35 # knows to keep them together. For example, do not write this:
36 # launch("svn ci -m 'log comment'") # BAD
37 # ...but one of these:
38 # launch('svn ci -m "log comment"') # GOOD
39 # launch("svn ci -m \"log comment\"") # GOOD, but why?
40 # Otherwise, you get an error saying
41 # '<path>/comment' is not under version control
42 # ...when running the tests on Windows.
43 ####
45 # True/False constants are Python 2.2+
46 try:
47 True, False
48 except NameError:
49 True, False = 1, 0
51 class TestCase_kwextract(unittest.TestCase):
52 def test_basic(self):
53 self.assertEqual(svnmerge.kwextract("$Rev: 134 rasky $"), "134 rasky")
54 self.assertEqual(svnmerge.kwextract("$Date: 2005-09-25 13:45 CET+1$"),
55 "2005-09-25 13:45 CET+1")
57 def test_failure(self):
58 self.assertEqual(svnmerge.kwextract("$Rev: $"), "<unknown>")
59 self.assertEqual(svnmerge.kwextract("$Date$"), "<unknown>")
61 class TestCase_launch(unittest.TestCase):
62 if os.name == "nt":
63 cmd = "attrib"
64 else:
65 cmd = "ls"
67 def test_basic(self):
68 out = svnmerge.launch(self.cmd)
69 self.assert_(out)
70 for o in out:
71 self.assertEqual(o[-1], "\n")
73 def test_failure(self):
74 self.assertRaises(svnmerge.LaunchError, svnmerge.launch, self.cmd*10)
76 def test_failurecode(self):
77 try:
78 svnmerge.launch(self.cmd*10)
79 except svnmerge.LaunchError, (ret, cmd, out):
80 self.assertNotEqual(ret, 0)
81 self.assertNotEqual(ret, None)
82 self.assert_(out)
83 self.assertEqual(cmd, self.cmd*10)
84 else:
85 self.fail("svnmerge.launch did not cause a LaunchError as expected")
87 class TestCase_PrefixLines(unittest.TestCase):
88 def test_basic(self):
89 self.assertEqual("zz\n", svnmerge.prefix_lines("zz", "\n"))
90 self.assertEqual("zzfoo\n", svnmerge.prefix_lines("zz", "foo\n"))
91 self.assertEqual("zzfoo\nzzbar\n", svnmerge.prefix_lines("zz", "foo\nbar\n"))
92 self.assertEqual("zz\nzzfoo\n", svnmerge.prefix_lines("zz", "\nfoo\n"))
93 self.assertEqual("zz\nzzfoo\nzzbar\n", svnmerge.prefix_lines("zz", "\nfoo\nbar\n"))
95 class TestCase_RevisionSet(unittest.TestCase):
96 def test_constr_string(self):
97 rs = svnmerge.RevisionSet("10- 15, 12-48,2 ")
98 self.assert_(17 in rs)
99 self.assert_(2 in rs)
100 self.assert_(9 not in rs)
102 rs = svnmerge.RevisionSet("10: 15, 12:48,2 ")
103 self.assert_(17 in rs)
104 self.assert_(2 in rs)
105 self.assert_(9 not in rs)
107 def test_constr_dict(self):
108 rs = svnmerge.RevisionSet({18:1, 24:1, 25:1, 43:1})
109 self.assert_(24 in rs)
110 self.assert_(18 in rs)
111 self.assert_(44 not in rs)
113 def test_constr_error(self):
114 self.assertRaises(ValueError, svnmerge.RevisionSet, "10-12-15")
115 self.assertRaises(ValueError, svnmerge.RevisionSet, "10;12-15")
116 self.assertRaises(ValueError, svnmerge.RevisionSet, "10,foo,3-15")
118 self.assertRaises(ValueError, svnmerge.RevisionSet, "10:12:15")
119 self.assertRaises(ValueError, svnmerge.RevisionSet, "10;12:15")
120 self.assertRaises(ValueError, svnmerge.RevisionSet, "10,foo,3:15")
122 def test_normalized(self):
123 rs = svnmerge.RevisionSet("8-15,16-18, 4-6, 9, 18, 1-1, 3-3")
124 self.assertEqual(rs.normalized(), [(1,1), (3,6), (8,18)])
125 self.assertEqual(str(rs), "1,3-6,8-18")
127 rs = svnmerge.RevisionSet("8:15,16:18, 4:6, 9, 18, 1:1, 3:3")
128 self.assertEqual(rs.normalized(), [(1,1), (3,6), (8,18)])
129 self.assertEqual(str(rs), "1,3-6,8-18")
131 def test_sorted(self):
132 "Test the sorted() function of the RevisionSet class."
133 rs = svnmerge.RevisionSet("8-15,16-18, 4-6, 9, 18, 1-1, 3-3")
134 self.assertEqual(rs.sorted(), [1, 3, 4, 5, 6, 8, 9, 10, 11,
135 12, 13, 14, 15, 16, 17, 18])
137 rs = svnmerge.RevisionSet("8:15,16:18, 4:6, 9, 18, 1:1, 3:3")
138 self.assertEqual(rs.sorted(), [1, 3, 4, 5, 6, 8, 9, 10, 11,
139 12, 13, 14, 15, 16, 17, 18])
141 def test_length(self):
142 rs = svnmerge.RevisionSet("3-8")
143 self.assertEqual(len(rs), 6)
144 rs = svnmerge.RevisionSet("3-8,4-10")
145 self.assertEqual(len(rs), 8)
146 rs = svnmerge.RevisionSet("1,3,5")
147 self.assertEqual(len(rs), 3)
149 rs = svnmerge.RevisionSet("3:8")
150 self.assertEqual(len(rs), 6)
151 rs = svnmerge.RevisionSet("3:8,4:10")
152 self.assertEqual(len(rs), 8)
153 rs = svnmerge.RevisionSet("1,3,5")
154 self.assertEqual(len(rs), 3)
156 def test_iter(self):
157 try:
158 iter
159 except NameError:
160 pass
161 else:
162 rs = svnmerge.RevisionSet("4-13,1-5,34,20-22,18-21")
163 self.assertEqual(list(iter(rs)), range(1,14)+range(18,23)+[34])
165 rs = svnmerge.RevisionSet("4:13,1:5,34,20:22,18:21")
166 self.assertEqual(list(iter(rs)), range(1,14)+range(18,23)+[34])
168 def test_union(self):
169 rs = svnmerge.RevisionSet("3-8,4-10") | svnmerge.RevisionSet("7-14,1")
170 self.assertEqual(str(rs), "1,3-14")
172 rs = svnmerge.RevisionSet("3:8,4:10") | svnmerge.RevisionSet("7:14,1")
173 self.assertEqual(str(rs), "1,3-14")
175 def test_subtraction(self):
176 rs = svnmerge.RevisionSet("3-8,4-10") - svnmerge.RevisionSet("7-14,1")
177 self.assertEqual(str(rs), "3-6")
179 rs = svnmerge.RevisionSet("3:8,4:10") - svnmerge.RevisionSet("7:14,1")
180 self.assertEqual(str(rs), "3-6")
182 def test_constr_empty(self):
183 rs = svnmerge.RevisionSet("")
184 self.assertEqual(str(rs), "")
186 class TestCase_MinimalMergeIntervals(unittest.TestCase):
187 def test_basic(self):
188 rs = svnmerge.RevisionSet("4-8,12,18,24")
189 phantom = svnmerge.RevisionSet("8-11,13-16,19-23")
190 revs = svnmerge.minimal_merge_intervals(rs, phantom)
191 self.assertEqual(revs, [(4,12), (18,24)])
193 class TestCase_SvnMerge(unittest.TestCase):
194 def svnmerge(self, cmds, *args, **kwargs):
195 return self.svnmerge2(cmds.split(), *args, **kwargs)
197 def svnmerge2(self, args, error=False, match=None, nonmatch=None):
198 out = StringIO()
199 sys.stdout = sys.stderr = out
200 try:
201 try:
202 # Clear svnmerge's internal cache before running any
203 # commands.
204 svnmerge._cache_svninfo = {}
205 svnmerge._cache_reporoot = {}
207 ret = svnmerge.main(args)
208 except SystemExit, e:
209 ret = e.code
210 finally:
211 sys.stdout = sys.__stdout__
212 sys.stderr = sys.__stderr__
214 if ret is None:
215 ret = 0
217 if error:
218 self.assertNotEqual(ret, 0,
219 "svnmerge did not fail, with this output:\n%s" % out.getvalue())
220 else:
221 self.assertEqual(ret, 0,
222 "svnmerge failed, with this output:\n%s" % out.getvalue())
224 if match is not None:
225 self.assert_(re.search(match, out.getvalue()),
226 "pattern %r not found in output:\n%s" % (match, out.getvalue()))
227 if nonmatch is not None:
228 self.assert_(not re.search(nonmatch, out.getvalue()),
229 "pattern %r found in output:\n%s" % (nonmatch, out.getvalue()))
231 return out.getvalue()
233 def _parseoutput(self, ret, out, error=False, match=None, nonmatch=None):
234 if error:
235 self.assertNotEqual(ret,
237 "svnmerge did not fail, with this output:\n%s" % out)
238 else:
239 self.assertEqual(ret,
241 "svnmerge failed, with this output:\n%s" % out)
243 if match is not None:
244 self.assert_(re.search(match, out),
245 "pattern %r not found in output:\n%s" % (match, out))
247 if nonmatch is not None:
248 self.assert_(not re.search(nonmatch, out),
249 "pattern %r found in output:\n%s" % (nonmatch, out))
251 return out
253 def launch(self, cmd, **kwargs):
254 try:
255 out = svnmerge.launch(cmd, split_lines=False)
256 except svnmerge.LaunchError, (ret, cmd, out):
257 return self._parseoutput(ret, out, **kwargs)
258 return self._parseoutput(0, out, **kwargs)
260 class TestCase_CommandLineOptions(TestCase_SvnMerge):
261 def test_empty(self):
262 self.svnmerge("")
264 def test_help_commands(self):
265 self.svnmerge("help")
266 self.svnmerge("--help")
267 self.svnmerge("-h")
268 for cmd in svnmerge.command_table.keys():
269 self.svnmerge("help %s" % cmd)
270 self.svnmerge("%s --help" % cmd)
271 self.svnmerge("%s -h" % cmd)
273 def test_wrong_commands(self):
274 self.svnmerge("asijdoiasjd", error=True)
275 self.svnmerge("help asijdoiasjd", error=True)
277 def test_wrong_option(self):
278 self.svnmerge("--asdsad", error=True)
279 self.svnmerge("help --asdsad", error=True)
280 self.svnmerge("init --asdsad", error=True)
281 self.svnmerge("--asdsad init", error=True)
283 def test_version(self):
284 out = self.svnmerge("--version")
285 self.assert_(out.find("Giovanni Bajo") >= 0)
286 out = self.svnmerge("-V")
287 self.assert_(out.find("Giovanni Bajo") >= 0)
288 out = self.svnmerge("init -V")
289 self.assert_(out.find("Giovanni Bajo") >= 0)
291 def testOptionOrder(self):
292 """Make sure you can intermix command name, arguments and
293 options in any order."""
294 self.svnmerge("--log avail",
295 error=True,
296 match=r"no integration info") # accepted
297 self.svnmerge("-l avail",
298 error=True,
299 match=r"no integration info") # accepted
300 self.svnmerge("-r123 merge",
301 error=True,
302 match=r"no integration info") # accepted
303 self.svnmerge("-s -v -r92481 merge",
304 error=True,
305 match=r"no integration info") # accepted
306 self.svnmerge("--log merge",
307 error=True,
308 match=r"option --log not recognized")
309 self.svnmerge("--diff foobar", error=True, match=r"foobar")
311 # This requires gnu_getopt support to be parsed
312 if hasattr(getopt, "gnu_getopt"):
313 self.svnmerge("-r123 merge . --log",
314 error=True,
315 match=r"option --log not recognized")
317 def temp_path():
318 try:
319 return os.environ["TEMP"]
320 except KeyError:
321 pass
322 if os.name == "posix":
323 return "/tmp"
324 return "."
326 def rmtree(path):
327 def onerror(func, path, excinfo):
328 if func in [os.remove, os.rmdir]:
329 if os.path.exists(path):
330 os.chmod(path, stat.S_IWRITE)
331 func(path)
333 if os.path.isdir(path):
334 shutil.rmtree(path, onerror=onerror)
336 def get_template_path():
337 p = os.path.join(temp_path(), "__svnmerge_test_template")
338 return os.path.abspath(p)
340 def get_test_path():
341 p = os.path.join(temp_path(), "__svnmerge_test")
342 return os.path.abspath(p)
344 def abspath_to_url(path):
345 assert path == os.path.abspath(path)
346 path = path.replace("\\", "/")
347 if path[0] != '/':
348 path = '/' + path
349 return "file://" + path
351 class TestCase_TestRepo(TestCase_SvnMerge):
352 def setUp(self):
353 """Creates a working copy of a branch at r13 with the
354 following structure, containing revisions (3-6, 13):
356 test-branch/
357 test1
358 test2
359 test3
361 ...from a repository with the following structure:
363 Path Created rev
364 ---- -----------
366 trunk/ 3
367 test1 4
368 test2 5
369 test3 6
370 test4 9
371 test5 10
372 branches/ 1
373 testYYY-branch/ 11 (renamed from testXXX-branch in 12)
374 test1 4
375 test2 5
376 test3 6
377 test-branch/ 13 (copied from trunk@6)
378 test1 4
379 test2 5
380 test3 6
381 tags/ 2
383 self.cwd = os.getcwd()
385 test_path = get_test_path()
386 template_path = get_template_path()
388 self.template_path = template_path
389 self.test_path = test_path
391 self.template_repo_path = os.path.join(template_path, "repo")
392 self.template_repo_url = abspath_to_url(self.template_repo_path)
394 self.test_repo_path = os.path.join(test_path, "repo")
395 self.test_repo_url = abspath_to_url(self.test_repo_path)
397 if not os.path.isdir(template_path):
398 rmtree(template_path)
399 os.makedirs(template_path)
400 os.chdir(template_path)
402 self.multilaunch("""
403 svnadmin create --fs-type fsfs %(TEMPLATE_REPO_PATH)s
404 svn mkdir -m "create /branches" %(TEMPLATE_REPO_URL)s/branches
405 svn mkdir -m "create /tags" %(TEMPLATE_REPO_URL)s/tags
406 svn mkdir -m "create /trunk" %(TEMPLATE_REPO_URL)s/trunk
407 svn co %(TEMPLATE_REPO_URL)s/trunk trunk
408 """)
410 os.chdir("trunk")
411 open("test1", "w").write("test 1")
412 open("test2", "w").write("test 2")
413 open("test3", "w").write("test 3")
414 open("test4", "w").write("test 4")
415 open("test5", "w").write("test 5")
417 self.multilaunch("""
418 svn add test1
419 svn ci -m "add test1"
420 svn add test2
421 svn ci -m "add test2"
422 svn add test3
423 svn ci -m "add test3"
424 svn mkdir -m "create /foobar" %(TEMPLATE_REPO_URL)s/foobar
425 svn rm -m "remove /foobar" %(TEMPLATE_REPO_URL)s/foobar
426 svn add test4
427 svn ci -m "add test4"
428 svn add test5
429 svn ci -m "add test5"
430 svn cp -r6 -m "create branch" %(TEMPLATE_REPO_URL)s/trunk %(TEMPLATE_REPO_URL)s/branches/testXXX-branch
431 svn mv -m "rename branch" %(TEMPLATE_REPO_URL)s/branches/testXXX-branch %(TEMPLATE_REPO_URL)s/branches/testYYY-branch
432 svn cp -r6 -m "create branch" %(TEMPLATE_REPO_URL)s/trunk %(TEMPLATE_REPO_URL)s/branches/test-branch
433 """)
435 os.chdir("..")
437 self.launch("svn co %(TEMPLATE_REPO_URL)s/branches/test-branch")
439 os.chdir(self.cwd)
441 rmtree(self.test_path)
442 shutil.copytree(self.template_path, self.test_path)
443 os.chdir(self.test_path)
445 # Relocate the test working copies from using the template
446 # repository to the test repository so the template repository
447 # is not affected by commits.
448 self.launch("svn switch --relocate %(TEMPLATE_REPO_URL)s %(TEST_REPO_URL)s trunk test-branch")
450 os.chdir("test-branch")
452 # Always remove the template directory when the tests have
453 # completed.
454 atexit.register(lambda: rmtree(template_path))
456 def tearDown(self):
457 os.chdir(self.cwd)
458 rmtree(self.test_path)
460 def command_dict(self):
461 return {
462 "TEMPLATE_PATH": self.template_path,
463 "TEMPLATE_REPO_PATH": self.template_repo_path,
464 "TEMPLATE_REPO_URL": self.template_repo_url,
465 "TEST_PATH": self.test_path,
466 "TEST_REPO_PATH": self.test_repo_path,
467 "TEST_REPO_URL": self.test_repo_url,
470 def launch(self, cmd, *args, **kwargs):
471 cmd = cmd % self.command_dict()
472 return TestCase_SvnMerge.launch(self, cmd, *args, **kwargs)
474 def multilaunch(self, cmds):
475 for cmd in cmds.split("\n"):
476 cmd = cmd.strip()
477 if len(cmd) > 0:
478 svnmerge.launch(cmd % self.command_dict())
480 def revert(self):
481 self.multilaunch("svn revert -R .")
483 def getproperty(self):
484 out = svnmerge.launch("svn pg %s ." % svnmerge.opts["prop"])
485 if len(out) == 0:
486 return None
487 else:
488 return out[0].strip()
490 def getBlockedProperty(self):
491 out = svnmerge.launch("svn pg %s ." % svnmerge.opts["block-prop"])
492 if len(out) == 0:
493 return None
494 else:
495 return out[0].strip()
497 def testNoWc(self):
498 os.mkdir("foo")
499 os.chdir("foo")
500 self.svnmerge("init", error=True, match=r"working dir")
501 self.svnmerge("avail", error=True, match=r"working dir")
502 self.svnmerge("integrated", error=True, match=r"working dir")
503 self.svnmerge("merge", error=True, match=r"working dir")
504 self.svnmerge("block", error=True, match=r"working dir")
505 self.svnmerge("unblock", error=True, match=r"working dir")
507 def testCheckNoIntegrationInfo(self):
508 self.svnmerge("avail", error=True, match=r"no integration")
509 self.svnmerge("integrated", error=True, match=r"no integration")
510 self.svnmerge("merge", error=True, match=r"no integration")
511 self.svnmerge("block", error=True, match=r"no integration")
512 self.svnmerge("unblock", error=True, match=r"no integration")
514 def testSelfReferentialInit(self):
515 self.svnmerge2(["init", self.test_repo_url + "/branches/test-branch"],
516 error=True, match=r"cannot init integration source")
518 def testBlocked(self):
520 # Initialize svnmerge
521 self.svnmerge("init")
522 self.launch("svn commit -F svnmerge-commit-message.txt",
523 match=r"Committed revision")
525 # Block revisions that have already been merged
526 self.svnmerge("block -r5", error=True, match=r"no available revisions")
528 # Block phantom revisions
529 self.svnmerge("block -r8", error=True, match=r"no available revisions")
531 # Block available revisions
532 self.svnmerge("block -r9", match=r"'svnmerge-blocked' set")
533 self.launch("svn commit -F svnmerge-commit-message.txt",
534 match=r"Committed revision")
536 # Check that the revision is still available
537 self.svnmerge("avail", match=r"\A10$")
539 # Check that the revision was blocked correctly
540 self.svnmerge("avail -B", match=r"\A9$")
542 # Check that both revisions are available with avail -A
543 self.svnmerge("avail -A", match=r"\A9-10$")
545 # Block all remaining revisions
546 self.svnmerge("block", match=r"'svnmerge-blocked' set")
547 self.launch("svn commit -F svnmerge-commit-message.txt",
548 match=r"Committed revision")
550 # Check that all revisions were blocked correctly
551 self.svnmerge("avail -B", match=r"\A9-10$")
553 # Check that all revisions are available using avail -A
554 self.svnmerge("avail -A", match=r"\A9-10$")
556 # Check that no revisions are available, now that they have
557 # been blocked
558 self.svnmerge("avail", match=r"\A\Z")
560 # Unblock all revisions
561 self.svnmerge("unblock", match=r"'svnmerge-blocked' deleted")
562 self.launch("svn commit -F svnmerge-commit-message.txt",
563 match=r"Committed revision")
565 # Check that all revisions are available
566 self.svnmerge("avail", match=r"\A9-10$")
567 self.svnmerge("avail -A", match=r"\A9-10$")
569 # Check that no revisions are blocked
570 self.svnmerge("avail -B", match=r"\A$")
572 def testTransitiveMerge(self):
574 Test a merge of a change from testYYY-branch -> test-branch -> trunk
576 os.chdir("..")
577 self.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
579 os.chdir("trunk")
580 self.launch("svn up")
581 self.svnmerge("init ../test-branch")
582 self.launch('svn ci -m "init test-branch -> trunk"',
583 match=r"Committed revision 14")
585 os.chdir("../test-branch")
586 self.svnmerge("init -r 1-6 ../testYYY-branch")
587 self.launch('svn ci -m "init testYYY-branch -> test-branch"',
588 match=r"Committed revision 15")
590 os.chdir("../testYYY-branch")
591 open("test6", "w").write("test6")
592 self.launch("svn add test6")
593 self.launch('svn ci -m "add test6"',
594 match=r"Committed revision 16")
596 os.chdir("../test-branch")
597 self.svnmerge("merge -r 16")
598 self.launch('svn ci -m "merge r16"',
599 match=r"Committed revision 17")
601 #os.chdir("../test-branch")
602 open("test5", "w").write("test5")
603 self.launch("svn add test5")
604 self.launch('svn ci -m "add test5"',
605 match=r"Committed revision 18")
607 os.chdir("../trunk")
608 self.svnmerge("block -r 18")
609 self.launch('svn ci -m "block r18"',
610 match=r"Committed revision 19")
612 #os.chdir("../trunk")
613 out = self.svnmerge("merge -r 17")
615 self.launch('svn ci -m "merge r17 from test-branch"',
616 match=r"Committed revision 20")
618 p = self.getproperty()
619 self.assertEqual(p, '/branches/test-branch:1-13,17')
620 p = self.getBlockedProperty()
621 self.assertEqual(p, '/branches/test-branch:18')
623 def testTransitiveMergeWithBlock(self):
625 Test a merge of a change from testYYY-branch -> test-branch -> trunk
627 os.chdir("..")
628 self.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
630 os.chdir("trunk")
631 self.launch("svn up")
632 self.svnmerge("init ../test-branch")
633 self.launch('svn ci -m "init test-branch -> trunk"',
634 match=r"Committed revision 14")
636 os.chdir("../test-branch")
637 self.svnmerge("init -r 1-6 ../testYYY-branch")
638 self.launch('svn ci -m "init testYYY-branch -> test-branch"',
639 match=r"Committed revision 15")
641 os.chdir("../testYYY-branch")
642 open("test4", "w").write("test4")
643 self.launch("svn add test4")
644 self.launch('svn ci -m "add test4"',
645 match=r"Committed revision 16")
647 os.chdir("../test-branch")
648 self.svnmerge("block -r 16")
649 self.launch('svn ci -m "block r16"',
650 match=r"Committed revision 17")
652 #os.chdir("../test-branch")
653 open("test5", "w").write("test5")
654 self.launch("svn add test5")
655 self.launch('svn ci -m "add test5"',
656 match=r"Committed revision 18")
658 os.chdir("../trunk")
659 self.svnmerge("block -r 18")
660 self.launch('svn ci -m "block r18"',
661 match=r"Committed revision 19")
663 #os.chdir("../trunk")
664 self.svnmerge("merge -r 17")
665 self.launch('svn ci -m "merge r17 from test-branch"',
666 match=r"Committed revision 20")
668 p = self.getproperty()
669 self.assertEqual(p, '/branches/test-branch:1-13,17')
670 p = self.getBlockedProperty()
671 self.assertEqual(p, '/branches/test-branch:18')
673 def testBasic(self):
674 self.svnmerge("init")
675 p = self.getproperty()
676 self.assertEqual("/trunk:1-6", p)
678 self.svnmerge("avail", match=r"\A9-10$")
679 self.svnmerge("avail -v", match=r"phantom.*7-8")
681 self.svnmerge("avail -B", match=r"\A$")
682 self.svnmerge("avail -A", match=r"\A9-10$")
684 self.svnmerge("avail --log", match=r"| r7.*| r8")
685 self.svnmerge("avail --diff -r9", match=r"Index: test4")
687 self.svnmerge("avail --log -r5", match=r"\A\Z")
688 self.svnmerge("avail --diff -r5", match=r"\A\Z")
690 self.svnmerge("integrated", match=r"^3-6$")
691 self.svnmerge("integrated --log -r5", match=r"| r5 ")
692 self.svnmerge("integrated --diff -r5", match=r"Index: test2")
694 def test_log_msg_suggest(self):
695 self.svnmerge("init -vf commit-log.txt", match=r"wrote commit message")
696 self.assert_(os.path.exists("commit-log.txt"))
697 os.remove("commit-log.txt")
699 def testInitForce(self):
700 open("test1", "a").write("foo")
701 self.svnmerge("init", error=True, match=r"clean")
702 self.svnmerge("init -F")
703 p = self.getproperty()
704 self.assertEqual("/trunk:1-6", p)
706 def testUninit(self):
707 """Test that uninit works, for both merged and blocked revisions."""
708 os.chdir("..")
709 self.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
711 os.chdir("trunk")
712 # Not using switch, so must update to get latest repository rev.
713 self.launch("svn update", match=r"At revision 13")
714 self.svnmerge2(["init", "-r1-13",
715 self.test_repo_url + "/branches/test-branch"])
716 self.launch("svn commit -F svnmerge-commit-message.txt",
717 match=r"Committed revision 14")
719 self.svnmerge2(["init", self.test_repo_url + "/branches/testYYY-branch"])
720 self.launch("svn commit -F svnmerge-commit-message.txt",
721 match=r"Committed revision 15")
723 # Create changes on test-branch that we can block
724 os.chdir("..")
725 os.chdir("test-branch")
726 # Not using switch, so must update to get latest repository rev.
727 self.launch("svn update", match=r"At revision 15")
729 open("test1", "w").write("test 1-changed_on_test-branch")
731 self.launch("svn commit -m \"Change to test1 on test-branch\"",
732 match=r"Committed revision 16")
734 # Create changes on testYYY-branch that we can block
735 os.chdir("..")
736 os.chdir("testYYY-branch")
737 # Not using switch, so must update to get latest repository rev.
738 self.launch("svn update", match=r"At revision 16")
740 open("test2", "w").write("test 2-changed_on_testYYY-branch")
742 self.launch("svn commit -m \"Change to test2 on testYYY-branch\"",
743 match=r"Committed revision 17")
745 # Block changes from both branches on the trunk
746 os.chdir("..")
747 os.chdir("trunk")
748 # Not using switch, so must update to get latest repository rev.
749 self.launch("svn update", match=r"At revision 17")
750 self.svnmerge("block -S testYYY-branch", match=r"'svnmerge-blocked' set")
751 self.launch("svn commit -F svnmerge-commit-message.txt",
752 match=r"Committed revision 18")
754 self.svnmerge("block -S test-branch", match=r"'svnmerge-blocked' set")
755 self.launch("svn commit -F svnmerge-commit-message.txt",
756 match=r"Committed revision 19")
758 # Do the uninit
759 self.svnmerge2(["uninit", "--source", self.test_repo_url + "/branches/testYYY-branch"])
761 # Check that the merged property for testYYY-branch was removed, but
762 # not for test-branch
763 pmerged = self.getproperty()
764 self.assertEqual("/branches/test-branch:1-13", pmerged)
766 # Check that the blocked property for testYYY-branch was removed, but
767 # not for test-branch
768 pblocked = self.getBlockedProperty()
769 self.assertEqual("/branches/test-branch:16", pblocked)
771 self.launch("svn commit -F svnmerge-commit-message.txt",
772 match=r"Committed revision 20")
774 self.svnmerge2(["uninit", "--source", self.test_repo_url + "/branches/test-branch"])
776 # Check that the merged and blocked properties for test-branch have been removed too
777 pmerged = self.getproperty()
778 self.assertEqual(None, pmerged)
780 pblocked = self.getBlockedProperty()
781 self.assertEqual(None, pblocked)
783 def testUninitForce(self):
784 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
786 self.launch("svn commit -F svnmerge-commit-message.txt",
787 match=r"Committed revision")
789 self.svnmerge2(["init", self.test_repo_url + "/branches/testYYY-branch"])
791 self.launch("svn commit -F svnmerge-commit-message.txt",
792 match=r"Committed revision")
794 p = self.getproperty()
795 self.assert_(re.search("/branches/testYYY-branch:1-\d+? /trunk:1-\d+?", p))
797 open("test1", "a").write("foo")
799 self.svnmerge("uninit --source " + self.test_repo_url + "/branches/testYYY-branch",
800 error=True, match=r"clean")
802 self.svnmerge("uninit -F --source " + self.test_repo_url + "/branches/testYYY-branch")
803 p = self.getproperty()
804 self.assert_(re.search("^/trunk:1-\d+", p))
806 def testCheckNoCopyfrom(self):
807 os.chdir("..")
808 os.chdir("trunk")
809 self.svnmerge("init", error=True, match=r"no copyfrom")
811 def testInitScenarios(self):
812 """ Run various scenarios w/ svnmerge.py init and verify
813 the default values that are set as the integrated
814 revisions."""
816 # Run init with branch as merge source and trunk as merge target
817 os.chdir("..")
818 os.chdir("trunk")
819 self.svnmerge("init ../test-branch")
820 # Verify range ends at rev in which branch was created
821 self.launch("svn proplist -v", match=r":1-13")
822 self.revert()
824 # Run init with TRUNK as merge source and BRANCH as merge target
825 os.chdir("..")
826 os.chdir("test-branch")
827 self.svnmerge("init ../trunk")
828 # Verify range ends at rev of trunk which was copied to create branch
829 self.launch("svn proplist -v", match=r":1-6")
830 self.revert()
832 # Same thing, but with no explicit parameter (should work implicitly)
833 self.svnmerge("init")
834 # Verify range ends at rev of trunk which was copied to create branch
835 self.launch("svn proplist -v", match=r":1-6")
836 self.revert()
838 # Run init with TRUNK as merge src, & any other branch which is not
839 # a copy of trunk (or the source from which trunk was copied)
840 # as the merge target.
841 os.chdir("../trunk")
842 os.chdir("..")
843 self.launch('svn mkdir -m "create /other" %(TEST_REPO_URL)s/other') # creates r14
844 self.launch("svn co %(TEST_REPO_URL)s/other")
845 os.chdir("other")
846 self.svnmerge("init ../trunk")
847 # Verify integrated range ends with merge source's latest rev as of
848 # the time of initialization:
849 self.launch("svn proplist -v", match=r":1-14")
850 self.revert()
852 # Run init w/ explicit parms; verify them
853 self.svnmerge("init -r 1-999 ../trunk")
854 self.launch("svn proplist -v", match=r":1-999")
856 def testTrimmedAvailMerge(self):
857 """Check that both avail and merge do not search for phantom revs too hard."""
858 self.svnmerge("init")
859 self.svnmerge("avail -vv -r8-9", match=r"svn --non-interactive log.*-r8:9")
860 self.svnmerge("merge -F -vv -r8-9", match=r"svn --non-interactive log.*-r8:9")
861 self.svnmerge("avail -vv -r2", nonmatch=r"svn log")
862 self.svnmerge("integrated", match=r"^3-6,8-9$")
864 def testMergeRecordOnly(self):
865 """Check that flagging revisions as manually merged works."""
866 self.svnmerge("init")
867 self.svnmerge("avail -vv -r9", match=r"svn --non-interactive log.*-r9:9")
868 self.svnmerge("merge --record-only -F -vv -r9",
869 nonmatch=r"svn merge -r 8:9")
870 self.svnmerge("avail -r9", match=r"\A$")
871 self.svnmerge("integrated", match=r"^3-6,9$")
872 self.svnmerge("integrated -r9", match=r"^9$")
874 def testBidirectionalMerges(self):
875 """Check that reflected revisions are recognized properly for bidirectional merges."""
877 os.chdir("..")
878 os.chdir("test-branch")
880 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
881 self.launch("svn commit -F svnmerge-commit-message.txt",
882 match=r"Committed revision 14")
883 os.remove("svnmerge-commit-message.txt")
885 os.chdir("..")
886 os.chdir("trunk")
888 # Not using switch, so must update to get latest repository rev.
889 self.launch("svn update", match=r"At revision 14")
890 self.svnmerge2(["init", "-r1-14",
891 self.test_repo_url + "/branches/test-branch"])
892 self.launch("svn commit -F svnmerge-commit-message.txt",
893 match=r"Committed revision 15")
894 os.remove("svnmerge-commit-message.txt")
896 open("test1", "w").write("test 1-changed_on_trunk")
898 self.launch("svn commit -m \"Change to test1 on trunk\"",
899 match=r"Committed revision 16")
901 self.svnmerge("integrated", match=r"^13-14$")
903 os.chdir("..")
904 os.chdir("test-branch")
906 # Not using switch, so must update to get latest repository rev.
907 self.launch("svn update", match=r"At revision 16")
909 # test-branch was copied from trunk's r6. So non-phantom revs
910 # since that point should still be available to merge from
911 # trunk to test-branch:
912 self.svnmerge("avail -vv --bidirectional", match=r"\n9-10,16$")
913 self.svnmerge("merge -vv --bidirectional", match=r"svn --non-interactive merge --force -r 15:16")
914 p = self.getproperty()
915 self.assertEqual("/trunk:1-16", p)
916 self.svnmerge("integrated", match=r"^3-16$")
918 self.launch("svn commit -F svnmerge-commit-message.txt",
919 match=r"Committed revision 17")
920 os.remove("svnmerge-commit-message.txt")
922 open("test1", "w").write("test 1-changed_on_branch_after_merge_from_trunk")
924 self.launch("svn commit -m \"Change to test1 on branch\"",
925 match=r"Committed revision 18")
927 os.chdir("..")
928 os.chdir("trunk")
930 # Not using switch, so must update to get latest repository rev.
931 self.launch("svn update", match=r"At revision 18")
933 # Ensure default is not to check for reflected revisions.
934 self.svnmerge("avail -vv", match=r"\n17-18$")
936 # Now check reflected revision is excluded with --bidirectional flag.
937 self.svnmerge("avail -vv --bidirectional", match=r"\n18$")
939 self.svnmerge("merge -vv --bidirectional", match=r"svn --non-interactive merge --force -r 17:18")
940 p = self.getproperty()
941 self.assertEqual("/branches/test-branch:1-18", p)
943 self.svnmerge("integrated", match=r"^13-18$")
945 def testBidirectionalMergesMultiBranch(self):
946 """Check that merges from a second branch are not considered reflected for other branches."""
948 os.chdir("..")
950 self.multilaunch("""
951 svn cp -m "Create test-branch2" %(TEST_REPO_URL)s/trunk %(TEST_REPO_URL)s/branches/test-branch2
952 svn co %(TEST_REPO_URL)s/branches/test-branch2 test-branch2
953 """)
955 os.chdir("test-branch")
957 self.svnmerge2(["init", "-r1-13", self.test_repo_url + "/trunk"])
958 self.launch("svn commit -F svnmerge-commit-message.txt",
959 match=r"Committed revision 15")
960 os.remove("svnmerge-commit-message.txt")
962 os.chdir("..")
963 os.chdir("test-branch2")
965 self.svnmerge2(["init", "-r1-14", self.test_repo_url + "/trunk"])
966 self.launch("svn commit -F svnmerge-commit-message.txt",
967 match=r"Committed revision 16")
968 os.remove("svnmerge-commit-message.txt")
970 os.chdir("..")
971 os.chdir("trunk")
973 # Not using switch, so must update to get latest repository rev.
974 self.launch("svn update", match=r"At revision 16")
976 self.svnmerge2(["init", "-r1-16",
977 self.test_repo_url + "/branches/test-branch"])
978 self.launch("svn commit -F svnmerge-commit-message.txt",
979 match=r"Committed revision 17")
980 os.remove("svnmerge-commit-message.txt")
981 self.svnmerge2(["init", "-r1-17",
982 self.test_repo_url + "/branches/test-branch2"])
983 self.launch("svn commit -F svnmerge-commit-message.txt",
984 match=r"Committed revision 18")
985 os.remove("svnmerge-commit-message.txt")
987 os.chdir("..")
988 os.chdir("test-branch2")
990 open("test1", "w").write("test 1-changed_on_branch2")
992 self.launch("svn commit -m \"Change to test1 on branch2\"",
993 match=r"Committed revision 19")
995 os.chdir("..")
996 os.chdir("trunk")
998 # Not using switch, so must update to get latest repository rev.
999 self.launch("svn update", match=r"At revision 19")
1001 # Merge into trunk
1002 self.svnmerge("merge -vv -S branch2",
1003 match=r"merge --force -r 18:19")
1004 p = self.getproperty()
1005 self.assertEqual("/branches/test-branch:1-16 /branches/test-branch2:1-19", p)
1007 self.svnmerge("integrated -S branch2", match=r"^14-19$")
1008 self.svnmerge("integrated -S ../test-branch", match=r"^13-16$")
1010 self.launch("svn commit -F svnmerge-commit-message.txt",
1011 match=r"Committed revision 20")
1012 os.remove("svnmerge-commit-message.txt")
1014 os.chdir("..")
1015 os.chdir("test-branch")
1017 # Not using switch, so must update to get latest repository rev.
1018 self.launch("svn update", match=r"At revision 20")
1020 # Initialized revs should not be available for merge
1021 self.svnmerge("avail -v --bidirectional", match=r"initialized.*17-18")
1023 # Latest revision on trunk which was merged from test-branch2
1024 # should be available for test-branch with --bidirectional flag.
1025 self.svnmerge("avail -vv --bidirectional", match=r"merged are:\n20$")
1027 self.svnmerge("merge -vv --bidirectional", match=r"merge --force -r 19:20")
1028 p = self.getproperty()
1029 self.assertEqual("/trunk:1-20", p)
1031 self.svnmerge("integrated", match=r"^3-20$")
1033 def testRollbackWithoutInit(self):
1034 """Rollback should error out if invoked prior to init"""
1036 self.svnmerge("rollback -vv -S ../trunk",
1037 error = True,
1038 match = r"no integration info available for path")
1040 def testRollbackOutsidePossibleRange(self):
1041 """`svnmerge rollback' should error out if range contains revisions prior to
1042 SOURCE creation date."""
1044 # Initialize svnmerge
1045 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1046 self.launch("svn commit -F svnmerge-commit-message.txt",
1047 match = r"Committed revision 14")
1048 os.remove("svnmerge-commit-message.txt")
1050 expected_error = r"""Specified revision range falls out of the rollback range."""
1051 self.svnmerge("rollback -vv -S ../trunk -r 2-14",
1052 error = True,
1053 match = expected_error)
1055 def testRollbackWithoutRevisionOpt(self):
1056 """`svnmerge rollback' should error out if -r option is not given"""
1058 # Initialize svnmerge
1059 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1060 self.launch("svn commit -F svnmerge-commit-message.txt",
1061 match=r"Committed revision 14")
1062 os.remove("svnmerge-commit-message.txt")
1064 self.svnmerge("rollback -vv -S ../trunk",
1065 error = True,
1066 match = r"The '-r' option is mandatory for rollback")
1068 def testInitAndRollbackRecordOnly(self):
1069 """Init svnmerge, modify source head, merge, rollback --record-only."""
1071 # Initialize svnmerge
1072 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1073 self.launch("svn commit -F svnmerge-commit-message.txt",
1074 match = r"Committed revision 14")
1075 os.remove("svnmerge-commit-message.txt")
1077 # Rollback record-only
1078 expected_output = r"property 'svnmerge-integrated' set on '.'"
1079 detested_output = r"""
1080 D test2
1081 D test3"""
1082 self.svnmerge("rollback -vv --record-only -S ../trunk -r5-7",
1083 match = expected_output,
1084 nonmatch = detested_output)
1086 def testInitAndRollback(self):
1087 """Init svnmerge, modify source head, merge, rollback."""
1089 # Initialize svnmerge
1090 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1091 self.launch("svn commit -F svnmerge-commit-message.txt",
1092 match = r"Committed revision 14")
1093 os.remove("svnmerge-commit-message.txt")
1095 # Svnmerge rollback r5-7
1096 expected_output = "D\s+test2\s+D\s+test3"
1097 self.svnmerge("rollback -vv -S ../trunk -r5-7",
1098 match = expected_output)
1100 def testMergeAndRollbackEmptyRevisionRange(self):
1101 """Init svnmerge, modify source head, merge, rollback where no merge
1102 occured."""
1104 # Initialize svnmerge
1105 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1106 self.launch("svn commit -F svnmerge-commit-message.txt",
1107 match = r"Committed revision 14")
1108 os.remove("svnmerge-commit-message.txt")
1110 # Make changes to trunk
1111 os.chdir("../trunk")
1112 open("newfile", "w").close()
1113 self.launch("svn add newfile")
1114 self.launch('svn commit -m "Adding newfile"', match=r"Committed revision 15")
1115 open("anothernewfile", "w").close()
1116 self.launch("svn add anothernewfile")
1117 self.launch('svn commit -m "Adding anothernewfile"', match=r"Committed revision 16")
1119 # Svnmerge block r15,16
1120 os.chdir("../test-branch")
1121 self.launch("svn up ..",
1122 error = False)
1123 self.svnmerge("block -r 15,16 -S ../trunk")
1124 self.launch("svn commit -F svnmerge-commit-message.txt",
1125 match = r"Committed revision 17")
1126 self.svnmerge("merge -S ../trunk")
1127 self.launch("svn commit -F svnmerge-commit-message.txt")
1129 # Svnmerge rollback r15-16
1130 self.svnmerge("rollback -vv -S ../trunk -r15-16",
1131 error = False,
1132 match = r"Nothing to rollback in revision range r15-16")
1134 def testMergeAndRollback(self):
1135 """Init svnmerge, modify source head, merge, rollback."""
1137 # Initialize svnmerge
1138 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1139 self.launch("svn commit -F svnmerge-commit-message.txt",
1140 match = r"Committed revision 14")
1141 os.remove("svnmerge-commit-message.txt")
1143 # Make changes to trunk
1144 os.chdir("../trunk")
1145 open("newfile", "w").close()
1146 self.launch("svn add newfile")
1147 self.launch('svn commit -m "Adding newfile"', match=r"Committed revision 15")
1149 # Svnmerge merge r15
1150 os.chdir("../test-branch")
1151 self.svnmerge("merge -r 15 -S ../trunk")
1152 self.launch("svn commit -F svnmerge-commit-message.txt",
1153 match = r"Committed revision 16")
1155 # Svnmerge rollback r15
1156 self.svnmerge("rollback -vv -S ../trunk -r15",
1157 match = r"-r 15:14")
1159 def testBlockMergeAndRollback(self):
1160 """Init svnmerge, block, modify head, merge, rollback."""
1162 # Initialize svnmerge
1163 self.svnmerge2(["init", self.test_repo_url + "/trunk"])
1164 self.launch("svn commit -F svnmerge-commit-message.txt",
1165 match = r"Committed revision 14")
1166 os.remove("svnmerge-commit-message.txt")
1168 # Make changes to trunk
1169 os.chdir("../trunk")
1170 open("newfile", "w").close()
1171 self.launch("svn add newfile")
1172 self.launch('svn commit -m "Adding newfile"', match=r"Committed revision 15")
1173 open("anothernewfile", "w").close()
1174 self.launch("svn add anothernewfile")
1175 self.launch('svn commit -m "Adding anothernewfile"', match=r"Committed revision 16")
1177 # Svnmerge block r16, merge r15
1178 os.chdir("../test-branch")
1179 self.launch("svn up ..",
1180 error = False)
1181 self.svnmerge("block -r 16 -S ../trunk")
1182 self.launch("svn commit -F svnmerge-commit-message.txt",
1183 match = r"Committed revision 17")
1184 self.svnmerge("merge -S ../trunk",
1185 nonmatch = r"A anothernewfile",
1186 match = r"A newfile")
1187 self.launch("svn commit -F svnmerge-commit-message.txt",
1188 match = r"Committed revision 18")
1190 # Svnmerge rollback revision range 15-18 (in effect only 15,17)
1191 self.svnmerge("rollback -vv -S ../trunk -r15-18",
1192 nonmatch = r"D anothernewfile")
1194 def testMergeWithPotentialPropertyConflict(self):
1195 """Init branch B, merge changes from branch A to branch B,
1196 init branch C, and attempt a merge of changes from branch B to
1197 branch C."""
1199 # Initialize merge info for test-branch.
1200 self.svnmerge2(["init", "-r 3-6", self.test_repo_url + "/trunk"])
1201 self.launch("svn commit -F svnmerge-commit-message.txt",
1202 match = r"Committed revision 14")
1203 os.remove("svnmerge-commit-message.txt")
1205 # Make a change to trunk.
1206 os.chdir("../trunk")
1207 open("newfile", "w").close()
1208 self.launch("svn add newfile")
1209 self.launch('svn commit -m "Adding newfile"',
1210 match=r"Committed revision 15")
1212 # Merge a change from trunk to test-branch.
1213 os.chdir("../test-branch")
1214 self.svnmerge("merge -r 15 -S ../trunk")
1215 self.launch("svn commit -F svnmerge-commit-message.txt",
1216 match=r"Committed revision 16")
1218 # Get a WC for testYYY-branch.
1219 os.chdir("..")
1220 self.launch("svn co %s/branches/testYYY-branch" % self.test_repo_url)
1221 os.chdir("testYYY-branch")
1223 # Initialize merge info for testYYY-branch.
1224 self.svnmerge2(["init", "-r 13",
1225 self.test_repo_url + "/branches/test-branch"])
1226 self.launch("svn commit -F svnmerge-commit-message.txt",
1227 match=r"Committed revision 17")
1228 os.remove("svnmerge-commit-message.txt")
1230 ### FIXME: Unfortunately, we're getting a conflict for the
1231 ### merge info property in the following svnmerge invocation
1232 ### due to Subversion's (reasonable) lack of property value
1233 ### merging (which isn't possible without knowing a property's
1234 ### MIME type)
1236 # Attempt a merge of changes from test-branch to
1237 # testYYY-branch.
1238 self.svnmerge("merge -r 16 -S ../test-branch")
1239 try:
1240 self.launch("svn commit -F svnmerge-commit-message.txt",
1241 match=r"Committed revision 18")
1242 except AssertionError:
1243 self.assert_(os.path.isfile("dir_conflicts.prej"))
1245 if __name__ == "__main__":
1246 # If an existing template repository and working copy for testing
1247 # exists, then always remove it. This prevents any problems if
1248 # this test suite is modified and there exists an older template
1249 # directory that may be used. This will also prevent problems if
1250 # in a previous run of this script, the template was being created
1251 # when the script was canceled, leaving it in an inconsistent
1252 # state.
1253 template_path = get_template_path()
1254 if os.path.exists(template_path):
1255 rmtree(template_path)
1257 unittest.main()