Add a little more to the svn_rangelist_intersect test to test the
[svn.git] / subversion / tests / cmdline / log_tests.py
blobc833a63e5c0bd37d299133cbe2085ccde9a64a69
1 #!/usr/bin/env python
3 # log_tests.py: testing "svn log"
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Copyright (c) 2000-2008 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 ######################################################################
19 # General modules
20 import re, os, sys
22 # Our testing module
23 import svntest
24 from svntest import wc
26 from svntest.main import server_has_mergeinfo
28 ######################################################################
30 # The Plan:
32 # Get a repository, commit about 6 or 7 revisions to it, each
33 # involving different kinds of operations. Make sure to have some
34 # add, del, mv, cp, as well as file modifications, and make sure that
35 # some files are modified more than once.
37 # Give each commit a recognizable log message. Test all combinations
38 # of -r options, including none. Then test with -v, which will
39 # (presumably) show changed paths as well.
41 ######################################################################
45 ######################################################################
46 # Globals
49 # These variables are set by guarantee_repos_and_wc().
50 max_revision = 0 # Highest revision in the repos
52 # What separates log msgs from one another in raw log output.
53 msg_separator = '------------------------------------' \
54 + '------------------------------------\n'
57 # (abbreviation)
58 Skip = svntest.testcase.Skip
59 SkipUnless = svntest.testcase.SkipUnless
60 XFail = svntest.testcase.XFail
61 Item = svntest.wc.StateItem
64 ######################################################################
65 # Utilities
68 def guarantee_repos_and_wc(sbox):
69 "Make a repos and wc, commit max_revision revs."
70 global max_revision
72 sbox.build()
73 wc_path = sbox.wc_dir
74 msg_file=os.path.join(sbox.repo_dir, 'log-msg')
75 msg_file=os.path.abspath(msg_file)
77 # Now we have a repos and wc at revision 1.
79 was_cwd = os.getcwd()
80 os.chdir(wc_path)
82 # Set up the paths we'll be using most often.
83 iota_path = os.path.join('iota')
84 mu_path = os.path.join('A', 'mu')
85 B_path = os.path.join('A', 'B')
86 omega_path = os.path.join('A', 'D', 'H', 'omega')
87 pi_path = os.path.join('A', 'D', 'G', 'pi')
88 rho_path = os.path.join('A', 'D', 'G', 'rho')
89 alpha_path = os.path.join('A', 'B', 'E', 'alpha')
90 beta_path = os.path.join('A', 'B', 'E', 'beta')
91 psi_path = os.path.join('A', 'D', 'H', 'psi')
92 epsilon_path = os.path.join('A', 'C', 'epsilon')
94 # Do a varied bunch of commits. No copies yet, we'll wait till Ben
95 # is done for that.
97 # Revision 2: edit iota
98 msg=""" Log message for revision 2
99 but with multiple lines
100 to test the code"""
101 svntest.main.file_write(msg_file, msg)
102 svntest.main.file_append(iota_path, "2")
103 svntest.main.run_svn(None,
104 'ci', '-F', msg_file)
105 svntest.main.run_svn(None,
106 'up')
108 # Revision 3: edit A/D/H/omega, A/D/G/pi, A/D/G/rho, and A/B/E/alpha
109 svntest.main.file_append(omega_path, "3")
110 svntest.main.file_append(pi_path, "3")
111 svntest.main.file_append(rho_path, "3")
112 svntest.main.file_append(alpha_path, "3")
113 svntest.main.run_svn(None,
114 'ci', '-m', "Log message for revision 3")
115 svntest.main.run_svn(None,
116 'up')
118 # Revision 4: edit iota again, add A/C/epsilon
119 msg=""" Log message for revision 4
120 but with multiple lines
121 to test the code"""
122 svntest.main.file_write(msg_file, msg)
123 svntest.main.file_append(iota_path, "4")
124 svntest.main.file_append(epsilon_path, "4")
125 svntest.main.run_svn(None, 'add', epsilon_path)
126 svntest.main.run_svn(None,
127 'ci', '-F', msg_file)
128 svntest.main.run_svn(None,
129 'up')
131 # Revision 5: edit A/C/epsilon, delete A/D/G/rho
132 svntest.main.file_append(epsilon_path, "5")
133 svntest.main.run_svn(None, 'rm', rho_path)
134 svntest.main.run_svn(None,
135 'ci', '-m', "Log message for revision 5")
136 svntest.main.run_svn(None,
137 'up')
139 # Revision 6: prop change on A/B, edit A/D/H/psi
140 msg=""" Log message for revision 6
141 but with multiple lines
142 to test the code"""
143 svntest.main.file_write(msg_file, msg)
144 svntest.main.run_svn(None, 'ps', 'blue', 'azul', B_path)
145 svntest.main.file_append(psi_path, "6")
146 svntest.main.run_svn(None,
147 'ci', '-F', msg_file)
148 svntest.main.run_svn(None,
149 'up')
151 # Revision 7: edit A/mu, prop change on A/mu
152 svntest.main.file_append(mu_path, "7")
153 svntest.main.run_svn(None, 'ps', 'red', 'burgundy', mu_path)
154 svntest.main.run_svn(None,
155 'ci', '-m', "Log message for revision 7")
156 svntest.main.run_svn(None,
157 'up')
159 # Revision 8: edit iota yet again, re-add A/D/G/rho
160 msg=""" Log message for revision 8
161 but with multiple lines
162 to test the code"""
163 svntest.main.file_write(msg_file, msg)
164 svntest.main.file_append(iota_path, "8")
165 svntest.main.file_append(rho_path, "8")
166 svntest.main.run_svn(None, 'add', rho_path)
167 svntest.main.run_svn(None,
168 'ci', '-F', msg_file)
169 svntest.main.run_svn(None,
170 'up')
172 # Revision 9: edit A/B/E/beta, delete A/B/E/alpha
173 svntest.main.file_append(beta_path, "9")
174 svntest.main.run_svn(None, 'rm', alpha_path)
175 svntest.main.run_svn(None,
176 'ci', '-m', "Log message for revision 9")
177 svntest.main.run_svn(None,
178 'up')
180 max_revision = 9
182 # Restore.
183 os.chdir(was_cwd)
185 # Let's run 'svn status' and make sure the working copy looks
186 # exactly the way we think it should. Start with a generic
187 # greek-tree-list, where every local and repos revision is at 9.
188 expected_status = svntest.actions.get_virginal_state(wc_path, 9)
189 expected_status.remove('A/B/E/alpha')
190 expected_status.add({
191 'A/C/epsilon' : Item(status=' ', wc_rev=9),
194 # props exist on A/B and A/mu
195 expected_status.tweak('A/B', 'A/mu', status=' ')
197 # Run 'svn st -uv' and compare the actual results with our tree.
198 svntest.actions.run_and_verify_status(wc_path, expected_status)
201 def merge_history_repos(sbox):
202 """Make a repos with varied and interesting merge history, similar
203 to the repos found at:
204 http://merge-tracking.open.collab.net/servlets/ProjectProcess?documentContainer=c2__Sample%20repository"""
206 upsilon_path = os.path.join('A', 'upsilon')
207 omicron_path = os.path.join('blocked', 'omicron')
208 branch_a = os.path.join('branches', 'a')
209 branch_b = os.path.join('branches', 'b')
210 branch_c = os.path.join('branches', 'c')
212 # Create an empty repository - r0
213 svntest.main.safe_rmtree(sbox.repo_dir, 1)
214 svntest.main.safe_rmtree(sbox.wc_dir, 1)
215 svntest.main.create_repos(sbox.repo_dir)
217 svntest.actions.run_and_verify_svn(None, None, [], "co", sbox.repo_url,
218 sbox.wc_dir)
219 was_cwd = os.getcwd()
220 os.chdir(sbox.wc_dir)
222 # Create trunk/tags/branches - r1
223 svntest.main.run_svn(None, 'mkdir', 'trunk')
224 svntest.main.run_svn(None, 'mkdir', 'tags')
225 svntest.main.run_svn(None, 'mkdir', 'branches')
226 svntest.main.run_svn(None, 'ci', '-m',
227 'Add trunk/tags/branches structure.')
229 # Import greek tree to trunk - r2
230 svntest.main.greek_state.write_to_disk('trunk')
231 svntest.main.run_svn(None, 'add', os.path.join('trunk', 'A'),
232 os.path.join('trunk', 'iota'))
233 svntest.main.run_svn(None, 'ci', '-m',
234 'Import greek tree into trunk.')
236 # Update from the repository to avoid a mix-rev working copy
237 svntest.main.run_svn(None, 'up')
239 # Create a branch - r3
240 svntest.main.run_svn(None, 'cp', 'trunk', branch_a)
241 svntest.main.run_svn(None, 'ci', '-m',
242 'Create branches/a from trunk.',
243 '--username', svntest.main.wc_author2)
245 # Some changes on the branch - r4
246 svntest.main.file_append_binary(os.path.join(branch_a, 'iota'),
247 "'A' has changed a bit.\n")
248 svntest.main.file_append_binary(os.path.join(branch_a, 'A', 'mu'),
249 "Don't forget to look at 'upsilon', too.")
250 svntest.main.file_write(os.path.join(branch_a, upsilon_path),
251 "This is the file 'upsilon'.\n", "wb")
252 svntest.main.run_svn(None, 'add',
253 os.path.join(branch_a, upsilon_path))
254 svntest.main.run_svn(None, 'ci', '-m',
255 "Add the file 'upsilon', and change some other files.")
257 # Create another branch - r5
258 svntest.main.run_svn(None, 'cp', 'trunk', branch_c)
259 svntest.main.run_svn(None, 'ci', '-m',
260 'Create branches/c from trunk.',
261 '--username', svntest.main.wc_author2)
263 # Do some mergeing - r6
264 os.chdir('trunk')
265 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_a))
266 svntest.main.run_svn(None, 'ci', '-m',
267 'Merged branches/a to trunk.',
268 '--username', svntest.main.wc_author2)
269 os.chdir('..')
271 # Add omicron to branches/a - r7
272 svntest.main.run_svn(None, 'mkdir', os.path.join(branch_a, 'blocked'))
273 svntest.main.file_write(os.path.join(branch_a, omicron_path),
274 "This is the file 'omicron'.\n")
275 svntest.main.run_svn(None, 'add',
276 os.path.join(branch_a, omicron_path))
277 svntest.main.run_svn(None, 'ci', '-m',
278 "Add omicron to branches/a. " +
279 "It will be blocked from merging in r8.")
281 # Block r7 from being merged to trunk - r8
282 os.chdir('trunk')
283 svntest.main.run_svn(None, 'merge', '--record-only', '-r6:7',
284 os.path.join('..', branch_a))
285 svntest.main.run_svn(None, 'ci', '-m',
286 "Block r7 from merging to trunk.",
287 '--username', svntest.main.wc_author2)
288 os.chdir('..')
290 # Wording change in mu - r9
291 svntest.main.file_write(os.path.join('trunk', 'A', 'mu'),
292 "This is the file 'mu'.\n" +
293 "Don't forget to look at 'upsilon', as well.", "wb")
294 svntest.main.run_svn(None, 'ci', '-m',
295 "Wording change in mu.")
297 # Update from the repository to avoid a mix-rev working copy
298 svntest.main.run_svn(None, 'up')
300 # Create another branch - r10
301 svntest.main.run_svn(None, 'cp', 'trunk', branch_b)
302 svntest.main.run_svn(None, 'ci', '-m',
303 "Create branches/b from trunk",
304 '--username', svntest.main.wc_author2)
306 # Add another file, make some changes on branches/a - r11
307 svntest.main.file_append_binary(os.path.join(branch_a, upsilon_path),
308 "There is also the file 'xi'.")
309 svntest.main.file_write(os.path.join(branch_a, 'A', 'xi'),
310 "This is the file 'xi'.\n", "wb")
311 svntest.main.run_svn(None, 'add',
312 os.path.join(branch_a, 'A', 'xi'))
313 svntest.main.file_write(os.path.join(branch_a, 'iota'),
314 "This is the file 'iota'.\n" +
315 "'A' has changed a bit, with 'upsilon', and 'xi'.",
316 "wb")
317 svntest.main.run_svn(None, 'ci', '-m',
318 "Added 'xi' to branches/a, made a few other changes.")
320 # Merge branches/a to branches/b - r12
321 os.chdir(branch_b)
322 svntest.main.run_svn(None, 'merge', os.path.join('..', 'a'))
323 svntest.main.run_svn(None, 'ci', '-m',
324 "Merged branches/a to branches/b.",
325 '--username', svntest.main.wc_author2)
326 os.chdir(os.path.join('..', '..'))
328 # More wording changes - r13
329 svntest.main.file_append_binary(os.path.join(branch_b, 'A', 'D', 'gamma'),
330 "Watch out for the rays!")
331 svntest.main.run_svn(None, 'ci', '-m',
332 "Modify 'gamma' on branches/b.")
334 # More merging - r14
335 os.chdir('trunk')
336 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_b))
337 svntest.main.run_svn(None, 'ci', '-m',
338 "Merged branches/b to trunk.",
339 '--username', svntest.main.wc_author2)
340 os.chdir('..')
342 # Even more merging - r15
343 os.chdir(branch_c)
344 svntest.main.run_svn(None, 'merge', os.path.join('..', '..', 'trunk'))
345 svntest.main.run_svn(None, 'ci', '-m',
346 "Bring branches/c up to date with trunk.",
347 '--username', svntest.main.wc_author2)
348 os.chdir(os.path.join('..', '..'))
350 # Modify a file on branches/c - r16
351 svntest.main.file_append_binary(os.path.join(branch_c, 'A', 'mu'),
352 "\nThis is yet more content in 'mu'.")
353 svntest.main.run_svn(None, 'ci', '-m',
354 "Modify 'mu' on branches/c.")
356 # Merge branches/c to trunk, which produces a conflict - r17
357 os.chdir('trunk')
358 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_c))
359 svntest.main.file_write(os.path.join('A', 'mu'),
360 "This is the file 'mu'.\n" +
361 "Don't forget to look at 'upsilon', as well.\n" +
362 "This is yet more content in 'mu'.",
363 "wb")
364 svntest.main.run_svn(None, 'resolved', os.path.join('A', 'mu'))
365 svntest.main.run_svn(None, 'ci', '-m',
366 "Merge branches/c to trunk, " +
367 "resolving a conflict in 'mu'.",
368 '--username', svntest.main.wc_author2)
369 os.chdir('..')
371 # Restore working directory
372 os.chdir(was_cwd)
375 # For errors seen while parsing log data.
376 class SVNLogParseError(Exception):
377 def __init__(self, args=None):
378 self.args = args
381 def parse_log_output(log_lines):
382 """Return a log chain derived from LOG_LINES.
383 A log chain is a list of hashes; each hash represents one log
384 message, in the order it appears in LOG_LINES (the first log
385 message in the data is also the first element of the list, and so
386 on).
388 Each hash contains the following keys/values:
390 'revision' ===> number
391 'author' ===> string
392 'date' ===> string
393 'msg' ===> string (the log message itself)
394 'lines' ===> number (so that it may be checked against rev)
395 If LOG_LINES contains changed-path information, then the hash
396 also contains
398 'paths' ===> list of tuples of the form (X, PATH), where X is the
399 first column of verbose output, and PATH is the affected path.
401 If LOG_LINES contains merge result information, then the hash also contains
403 'merges' ===> list of merging revisions that resulted in this log
404 being part of the list of messages.
407 # Here's some log output to look at while writing this function:
409 # ------------------------------------------------------------------------
410 # r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
412 # Log message for revision 5.
413 # ------------------------------------------------------------------------
414 # r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
416 # Log message for revision 4
417 # but with multiple lines
418 # to test the code.
419 # ------------------------------------------------------------------------
420 # r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
422 # Log message for revision 3.
423 # ------------------------------------------------------------------------
424 # r2 | kfogel | Tue 6 Nov 2001 17:18:16 | 3 lines
426 # Log message for revision 2
427 # but with multiple lines
428 # to test the code.
429 # ------------------------------------------------------------------------
430 # r1 | foo | Tue 6 Nov 2001 15:27:57 | 1 line
432 # Log message for revision 1.
433 # ------------------------------------------------------------------------
435 # Regular expression to match the header line of a log message, with
436 # these groups: (revision number), (author), (date), (num lines).
437 header_re = re.compile('^r([0-9]+) \| ' \
438 + '([^|]*) \| ([^|]*) \| ([0-9]+) lines?')
440 # The log chain to return.
441 chain = []
443 this_item = None
444 while 1:
445 try:
446 this_line = log_lines.pop(0)
447 except IndexError:
448 return chain
450 match = header_re.search(this_line)
451 if match and match.groups():
452 is_result = 0
453 this_item = {}
454 this_item['revision'] = int(match.group(1))
455 this_item['author'] = match.group(2)
456 this_item['date'] = match.group(3)
457 lines = int(match.group(4))
458 this_item['lines'] = lines
460 # Parse verbose output, starting with "Changed paths"
461 next_line = log_lines.pop(0)
462 if next_line.strip() == 'Changed paths:':
463 paths = []
464 path_line = log_lines.pop(0).strip()
466 # Stop on either a blank line or a "Merged via: ..." line
467 while path_line != '' and path_line[0:6] != 'Merged':
468 paths.append( (path_line[0], path_line[2:]) )
469 path_line = log_lines.pop(0).strip()
471 this_item['paths'] = paths
473 if path_line[0:6] == 'Merged':
474 is_result = 1
475 result_line = path_line
477 elif next_line[0:6] == 'Merged':
478 is_result = 1
479 result_line = next_line.strip()
481 # Parse output of "Merged via: ..." line
482 if is_result:
483 merges = []
484 prefix_len = len('Merged via: ')
485 for rev_str in result_line[prefix_len:].split(','):
486 merges.append(int(rev_str.strip()[1:]))
487 this_item['merges'] = merges
489 # Eat blank line
490 log_lines.pop(0)
492 # Accumulate the log message
493 msg = ''
494 for line in log_lines[0:lines]:
495 msg += line
496 del log_lines[0:lines]
497 elif this_line == msg_separator:
498 if this_item:
499 this_item['msg'] = msg
500 chain.append(this_item)
501 else: # if didn't see separator now, then something's wrong
502 print this_line
503 raise SVNLogParseError, "trailing garbage after log message"
505 return chain
508 class SVNUnexpectedLogs(svntest.Failure):
509 "Exception raised if a set of log messages doesn't meet expectations."
511 def __init__(self, msg, chain, field_selector = 'revision'):
512 """Stores the log chain for later use. FIELD_SELECTOR indicates
513 which individual field to display when turning the exception into
514 text."""
515 svntest.Failure.__init__(self, msg)
516 self.chain = chain
517 self.field_selector = field_selector
519 def __str__(self):
520 msg = svntest.Failure.__str__(self)
521 if self.chain:
522 chain_data = list(self.chain)
523 for i in range(0, len(self.chain)):
524 chain_data[i] = self.chain[i][self.field_selector]
525 msg = msg + ': Actual %s list was %s' % (self.field_selector, chain_data)
526 return msg
529 def check_log_chain(chain, revlist, path_counts=[]):
530 """Verify that log chain CHAIN contains the right log messages for
531 revisions START to END (see documentation for parse_log_output() for
532 more about log chains).
534 Do nothing if the log chain's messages run from revision START to END
535 and each log message contains a line of the form
537 'Log message for revision N'
539 where N is the revision number of that commit. Verify that
540 author and date are present and look sane, but don't check them too
541 carefully.
542 Also verify that even numbered commit messages have three lines.
544 If the length of PATH_COUNTS is greater than zero, make sure that each
545 log has that number of paths.
547 Raise an error if anything looks wrong.
550 nbr_expected = len(revlist)
551 if len(chain) != nbr_expected:
552 raise SVNUnexpectedLogs('Number of elements in log chain and revision ' +
553 'list %s not equal' % revlist, chain)
554 if path_counts and len(path_counts) != nbr_expected:
555 raise SVNUnexpectedLogs('Number of elements in log chain and path ' +
556 'counts %s not equal' % path_counts, chain)
557 missing_revs = []
558 for i in range(0, nbr_expected):
559 expect_rev = revlist[i]
560 log_item = chain[i]
561 saw_rev = log_item['revision']
562 date = log_item['date']
563 author = log_item['author']
564 msg = log_item['msg']
565 # The most important check is that the revision is right:
566 if expect_rev != saw_rev:
567 missing_revs.append(expect_rev)
568 continue
569 # Check that date looks at least vaguely right:
570 date_re = re.compile('[0-9]+')
571 if not date_re.search(date):
572 raise SVNUnexpectedLogs('Malformed date', chain, 'date')
573 # Authors are a little harder, since they might not exist over ra-dav.
574 # Well, it's not much of a check, but we'll do what we can.
575 author_re = re.compile('[a-zA-Z]+')
576 if (not (author_re.search(author)
577 or author == ''
578 or author == '(no author)')):
579 raise SVNUnexpectedLogs('Malformed author', chain, 'author')
581 # Verify the expectation that even-numbered revisions in the Greek
582 # tree tweaked by the log tests have 3-line log messages.
583 if (saw_rev % 2 == 0 and log_item['lines'] != 3):
584 raise SVNUnexpectedLogs('Malformed log line counts', chain, 'lines')
586 # Check that the log message looks right:
587 pattern = 'Log message for revision ' + `saw_rev`
588 msg_re = re.compile(pattern)
589 if not msg_re.search(msg):
590 raise SVNUnexpectedLogs("Malformed log message, expected '%s'" % msg,
591 chain)
593 # If path_counts, check the number of changed paths
594 if path_counts:
595 if (not 'paths' in log_item) or (not log_item['paths']):
596 raise SVNUnexpectedLogs("No changed path information", chain)
597 if path_counts[i] != len(log_item['paths']):
598 raise SVNUnexpectedLogs("Changed paths counts not equal for " +
599 "revision %d" % (i + 1), chain)
601 nbr_missing_revs = len(missing_revs)
602 if nbr_missing_revs > 0:
603 raise SVNUnexpectedLogs('Unable to find expected revision(s) %s' %
604 missing_revs, chain)
608 ######################################################################
609 # Tests
612 #----------------------------------------------------------------------
613 def plain_log(sbox):
614 "'svn log', no args, top of wc"
616 guarantee_repos_and_wc(sbox)
618 os.chdir(sbox.wc_dir)
620 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log')
622 log_chain = parse_log_output(output)
623 check_log_chain(log_chain, range(max_revision, 1 - 1, -1))
626 #----------------------------------------------------------------------
627 def log_with_empty_repos(sbox):
628 "'svn log' on an empty repository"
630 # Create virgin repos
631 svntest.main.safe_rmtree(sbox.repo_dir, 1)
632 svntest.main.create_repos(sbox.repo_dir)
634 svntest.actions.run_and_verify_svn(None, None, [],
635 'log',
636 sbox.repo_url)
638 #----------------------------------------------------------------------
639 def log_where_nothing_changed(sbox):
640 "'svn log -rN some_dir_unchanged_in_N'"
641 sbox.build()
643 # Fix bug whereby running 'svn log -rN SOMEPATH' would result in an
644 # xml protocol error if there were no changes in revision N
645 # underneath SOMEPATH. This problem was introduced in revision
646 # 3811, which didn't cover the case where svn_repos_get_logs might
647 # invoke log_receiver zero times. Since the receiver never ran, the
648 # lrb->needs_header flag never got cleared. Control would proceed
649 # without error to the end of dav_svn__log_report(), which would
650 # send a closing tag even though no opening tag had ever been sent.
652 rho_path = os.path.join(sbox.wc_dir, 'A', 'D', 'G', 'rho')
653 svntest.main.file_append(rho_path, "some new material in rho")
654 svntest.actions.run_and_verify_svn(None, None, [],
655 'ci', '-m',
656 'log msg', rho_path)
658 # Now run 'svn log -r2' on a directory unaffected by revision 2.
659 H_path = os.path.join(sbox.wc_dir, 'A', 'D', 'H')
660 svntest.actions.run_and_verify_svn(None, None, [],
661 'log', '-r', '2', H_path)
664 #----------------------------------------------------------------------
665 def log_to_revision_zero(sbox):
666 "'svn log -v -r 1:0 wc_root'"
667 sbox.build(read_only = True)
669 # This used to segfault the server.
671 svntest.actions.run_and_verify_svn(None, None, [],
672 'log', '-v',
673 '-r', '1:0', sbox.wc_dir)
675 #----------------------------------------------------------------------
676 def log_with_path_args(sbox):
677 "'svn log', with args, top of wc"
679 guarantee_repos_and_wc(sbox)
681 os.chdir(sbox.wc_dir)
683 output, err = svntest.actions.run_and_verify_svn(
684 None, None, [],
685 'log', sbox.repo_url, 'A/D/G', 'A/D/H')
687 log_chain = parse_log_output(output)
688 check_log_chain(log_chain, [8, 6, 5, 3, 1])
690 #----------------------------------------------------------------------
691 def dynamic_revision(sbox):
692 "'svn log -r COMMITTED' of dynamic/local WC rev"
694 guarantee_repos_and_wc(sbox)
695 os.chdir(sbox.wc_dir)
697 revprops = [{'svn:author': 'jrandom',
698 'svn:date': '', 'svn:log': 'Log message for revision 9'}]
699 for rev in ('HEAD', 'BASE', 'COMMITTED'):
700 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
701 args=['-r', rev])
702 revprops[0]['svn:log'] = ('Log message for revision 8\n'
703 ' but with multiple lines\n'
704 ' to test the code')
705 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
706 args=['-r', 'PREV'])
708 #----------------------------------------------------------------------
709 def log_wc_with_peg_revision(sbox):
710 "'svn log wc_target@N'"
711 guarantee_repos_and_wc(sbox)
712 my_path = os.path.join(sbox.wc_dir, "A", "B", "E", "beta") + "@8"
713 output, err = svntest.actions.run_and_verify_svn(None, None, [],
714 'log', my_path)
715 check_log_chain(parse_log_output(output), [1])
717 #----------------------------------------------------------------------
718 def url_missing_in_head(sbox):
719 "'svn log target@N' when target removed from HEAD"
721 guarantee_repos_and_wc(sbox)
723 my_url = sbox.repo_url + "/A/B/E/alpha" + "@8"
725 output, err = svntest.actions.run_and_verify_svn(None, None, [],
726 'log', my_url)
727 check_log_chain(parse_log_output(output), [3, 1])
729 #----------------------------------------------------------------------
730 def log_through_copyfrom_history(sbox):
731 "'svn log TGT' with copyfrom history"
732 sbox.build()
733 wc_dir = sbox.wc_dir
734 msg_file=os.path.join(sbox.repo_dir, 'log-msg')
735 msg_file=os.path.abspath(msg_file)
737 mu_path = os.path.join(wc_dir, 'A', 'mu')
738 mu2_path = os.path.join(wc_dir, 'A', 'mu2')
739 mu_URL = sbox.repo_url + '/A/mu'
740 mu2_URL = sbox.repo_url + '/A/mu2'
742 msg2=""" Log message for revision 2
743 but with multiple lines
744 to test the code"""
746 msg4=""" Log message for revision 4
747 but with multiple lines
748 to test the code"""
750 msg6=""" Log message for revision 6
751 but with multiple lines
752 to test the code"""
754 svntest.main.file_write(msg_file, msg2)
755 svntest.main.file_append(mu_path, "2")
756 svntest.actions.run_and_verify_svn(None, None, [],
757 'ci', wc_dir,
758 '-F', msg_file)
759 svntest.main.file_append(mu2_path, "this is mu2")
760 svntest.actions.run_and_verify_svn(None, None, [], 'add', mu2_path)
761 svntest.actions.run_and_verify_svn(None, None, [],
762 'ci', wc_dir,
763 '-m', "Log message for revision 3")
764 svntest.actions.run_and_verify_svn(None, None, [], 'rm', mu2_path)
765 svntest.main.file_write(msg_file, msg4)
766 svntest.actions.run_and_verify_svn(None, None, [],
767 'ci', wc_dir,
768 '-F', msg_file)
769 svntest.main.file_append(mu_path, "5")
770 svntest.actions.run_and_verify_svn(None, None, [],
771 'ci', wc_dir,
772 '-m', "Log message for revision 5")
774 svntest.main.file_write(msg_file, msg6)
775 svntest.actions.run_and_verify_svn(None, None, [],
776 'cp', '-r', '5', mu_URL, mu2_URL,
777 '-F', msg_file)
778 svntest.actions.run_and_verify_svn(None, None, [],
779 'up', wc_dir)
781 # The full log for mu2 is relatively unsurprising
782 output, err = svntest.actions.run_and_verify_svn(None, None, [],
783 'log', mu2_path)
784 log_chain = parse_log_output(output)
785 check_log_chain(log_chain, [6, 5, 2, 1])
787 output, err = svntest.actions.run_and_verify_svn(None, None, [],
788 'log', mu2_URL)
789 log_chain = parse_log_output(output)
790 check_log_chain(log_chain, [6, 5, 2, 1])
792 # First "oddity", the full log for mu2 doesn't include r3, but the -r3
793 # log works!
794 peg_mu2_path = mu2_path + "@3"
795 output, err = svntest.actions.run_and_verify_svn(None, None, [],
796 'log', '-r', '3',
797 peg_mu2_path)
798 log_chain = parse_log_output(output)
799 check_log_chain(log_chain, [3])
801 peg_mu2_URL = mu2_URL + "@3"
802 output, err = svntest.actions.run_and_verify_svn(None, None, [],
803 'log', '-r', '3',
804 peg_mu2_URL)
805 log_chain = parse_log_output(output)
806 check_log_chain(log_chain, [3])
807 output, err = svntest.actions.run_and_verify_svn(None, None, [],
808 'log', '-r', '2',
809 mu2_path)
810 log_chain = parse_log_output(output)
811 check_log_chain(log_chain, [2])
813 output, err = svntest.actions.run_and_verify_svn(None, None, [],
814 'log', '-r', '2',
815 mu2_URL)
816 log_chain = parse_log_output(output)
817 check_log_chain(log_chain, [2])
819 #----------------------------------------------------------------------
820 def escape_control_chars(sbox):
821 "mod_dav_svn must escape invalid XML control chars"
823 dump_str = """SVN-fs-dump-format-version: 2
825 UUID: ffcae364-69ee-0310-a980-ca5f10462af2
827 Revision-number: 0
828 Prop-content-length: 56
829 Content-length: 56
832 svn:date
833 V 27
834 2005-01-24T10:09:21.759592Z
835 PROPS-END
837 Revision-number: 1
838 Prop-content-length: 128
839 Content-length: 128
842 svn:log
843 V 100
844 This msg contains a Ctrl-T (\x14) and a Ctrl-I (\t).
845 The former might be escaped, but the latter never.
847 K 10
848 svn:author
850 jrandom
852 svn:date
853 V 27
854 2005-01-24T10:09:22.012524Z
855 PROPS-END
858 # load dumpfile with control character into repos to get
859 # a log with control char content
860 svntest.actions.load_repo(sbox, dump_str=dump_str)
862 URL = sbox.repo_url
864 # run log
865 output, errput = svntest.actions.run_and_verify_svn(None, None, [], 'log',
866 URL)
868 # Verify the output contains either the expected fuzzy escape
869 # sequence, or the literal control char.
870 match_unescaped_ctrl_re = "This msg contains a Ctrl-T \(.\) " \
871 "and a Ctrl-I \(\t\)\."
872 match_escaped_ctrl_re = "^This msg contains a Ctrl-T \(\?\\\\020\) " \
873 "and a Ctrl-I \(\t\)\."
874 matched = None
875 for line in output:
876 if re.match(match_unescaped_ctrl_re, line) \
877 or re.match(match_escaped_ctrl_re, line):
878 matched = 1
880 if not matched:
881 raise svntest.Failure("log message not transmitted properly:" +
882 str(output) + "\n" + "error: " + str(errput))
884 #----------------------------------------------------------------------
885 def log_xml_empty_date(sbox):
886 "svn log --xml must not print empty date elements"
887 sbox.build()
889 # Create the revprop-change hook for this test
890 svntest.actions.enable_revprop_changes(sbox.repo_dir)
892 date_re = re.compile('<date');
894 # Ensure that we get a date before we delete the property.
895 output, errput = svntest.actions.run_and_verify_svn(None, None, [],
896 'log', '--xml', '-r1',
897 sbox.wc_dir)
898 matched = 0
899 for line in output:
900 if date_re.search(line):
901 matched = 1
902 if not matched:
903 raise svntest.Failure("log contains no date element")
905 # Set the svn:date revprop to the empty string on revision 1.
906 svntest.actions.run_and_verify_svn(None, None, [],
907 'pdel', '--revprop', '-r1', 'svn:date',
908 sbox.wc_dir)
910 output, errput = svntest.actions.run_and_verify_svn(None, None, [],
911 'log', '--xml', '-r1',
912 sbox.wc_dir)
913 for line in output:
914 if date_re.search(line):
915 raise svntest.Failure("log contains date element when svn:date is empty")
917 #----------------------------------------------------------------------
918 def log_limit(sbox):
919 "svn log --limit"
920 guarantee_repos_and_wc(sbox)
922 out, err = svntest.actions.run_and_verify_svn(None, None, [],
923 'log', '--limit', '2',
924 sbox.repo_url)
925 log_chain = parse_log_output(out)
926 check_log_chain(log_chain, [9, 8])
928 out, err = svntest.actions.run_and_verify_svn(None, None, [],
929 'log', '--limit', '2',
930 sbox.repo_url,
931 'A/B')
932 log_chain = parse_log_output(out)
933 check_log_chain(log_chain, [9, 6])
935 out, err = svntest.actions.run_and_verify_svn(None, None, [],
936 'log', '--limit', '2',
937 '--revision', '2:HEAD',
938 sbox.repo_url,
939 'A/B')
940 log_chain = parse_log_output(out)
941 check_log_chain(log_chain, [3, 6])
943 # Use -l instead of --limit to test both option forms.
944 out, err = svntest.actions.run_and_verify_svn(None, None, [],
945 'log', '-l', '2',
946 '--revision', '1',
947 sbox.repo_url,
948 'A/B')
949 log_chain = parse_log_output(out)
950 check_log_chain(log_chain, [1])
952 must_be_positive = ".*Argument to --limit must be positive.*"
954 # error expected when limit <= 0
955 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
956 'log', '--limit', '0', '--revision', '1',
957 sbox.repo_url, 'A/B')
959 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
960 'log', '--limit', '-1', '--revision', '1',
961 sbox.repo_url, 'A/B')
963 def log_base_peg(sbox):
964 "run log on an @BASE target"
965 guarantee_repos_and_wc(sbox)
967 target = os.path.join(sbox.wc_dir, 'A', 'B', 'E', 'beta') + '@BASE'
969 out, err = svntest.actions.run_and_verify_svn(None, None, [],
970 'log', target)
972 log_chain = parse_log_output(out)
973 check_log_chain(log_chain, [9, 1])
975 svntest.actions.run_and_verify_svn(None, None, [], 'update', '-r', '1',
976 sbox.wc_dir)
978 out, err = svntest.actions.run_and_verify_svn(None, None, [],
979 'log', target)
981 log_chain = parse_log_output(out)
982 check_log_chain(log_chain, [1])
985 def log_verbose(sbox):
986 "run log with verbose output"
987 guarantee_repos_and_wc(sbox)
989 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
990 '-v',
991 sbox.wc_dir)
993 log_chain = parse_log_output(output)
994 path_counts = [2, 2, 1, 2, 2, 2, 4, 1, 20]
995 check_log_chain(log_chain, range(max_revision, 1 - 1, -1), path_counts)
998 def log_parser(sbox):
999 "meta-test for the log parser"
1001 logs = ['''------------------------------------------------------------------------
1002 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1003 Changed paths:
1004 M /trunk/death-ray.c
1005 M /trunk/frobnicator/frapnalyzer.c
1007 Merge r12 and r14 from branch to trunk.
1008 ------------------------------------------------------------------------
1009 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1010 Changed paths:
1011 M /trunk/death-ray.c
1012 Merged via: r24
1014 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1015 ------------------------------------------------------------------------
1016 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1017 Changed paths:
1018 M /trunk/frobnicator/frapnalyzer.c
1019 M /trunk/death-ray.c
1020 Merged via: r24
1022 Fix frapnalyzer bug in frobnicator.
1023 ------------------------------------------------------------------------''',
1024 '''------------------------------------------------------------------------
1025 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1027 Merge r12 and r14 from branch to trunk.
1028 ------------------------------------------------------------------------
1029 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1030 Merged via: r24
1032 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1033 ------------------------------------------------------------------------
1034 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1035 Merged via: r24
1037 Fix frapnalyzer bug in frobnicator.
1038 ------------------------------------------------------------------------
1039 r10 | alice | 2007-04-16 19:02:28 -0500 (Mon, 16 Apr 2007) | 1 line
1040 Merged via: r12, r24
1042 Fix frapnalyzer documentation.
1043 ------------------------------------------------------------------------
1044 r9 | bob | 2007-04-16 19:01:48 -0500 (Mon, 16 Apr 2007) | 1 line
1045 Merged via: r12, r24
1047 Whitespace fixes. No functional change.
1048 ------------------------------------------------------------------------''',
1049 '''------------------------------------------------------------------------
1050 r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
1052 Log message for revision 5.
1053 ------------------------------------------------------------------------
1054 r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
1056 Log message for revision 4
1057 but with multiple lines
1058 to test the code.
1059 ------------------------------------------------------------------------
1060 r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
1062 Log message for revision 3.
1063 ------------------------------------------------------------------------''',
1064 ] # end of log list
1066 for log in logs:
1067 log_chain = parse_log_output([line+"\n" for line in log.split("\n")])
1070 def check_merge_results(log_chain, expected_merges):
1071 '''Check LOG_CHAIN to see if the log information contains 'Merged via'
1072 information indicated by EXPECTED_MERGES. EXPECTED_MERGES is a dictionary
1073 whose key is the merged revision, and whose value is the merging revision.'''
1075 # Check to see if the number and values of the revisions is correct
1076 for log in log_chain:
1077 if log['revision'] not in expected_merges:
1078 raise SVNUnexpectedLogs("Found unexpected revision %d" %
1079 log['revision'], log_chain)
1081 # Check to see that each rev in expected_merges contains the correct data
1082 for rev in expected_merges:
1083 try:
1084 log = [x for x in log_chain if x['revision'] == rev][0]
1085 if 'merges' in log.keys():
1086 actual = log['merges']
1087 else:
1088 actual = []
1089 expected = expected_merges[rev]
1091 if actual != expected:
1092 raise SVNUnexpectedLogs(("Merging revisions in rev %d not correct; " +
1093 "expecting %s, found %s") %
1094 (rev, str(expected), str(actual)), log_chain)
1095 except IndexError:
1096 raise SVNUnexpectedLogs("Merged revision '%d' missing" % rev, log_chain)
1099 def merge_sensitive_log_single_revision(sbox):
1100 "test sensitive log on a single revision"
1102 merge_history_repos(sbox)
1104 # Paths we care about
1105 wc_dir = sbox.wc_dir
1106 TRUNK_path = os.path.join(wc_dir, "trunk")
1107 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1109 # Run the merge sensitive log, and compare results
1110 saved_cwd = os.getcwd()
1112 os.chdir(TRUNK_path)
1113 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1114 '-g', '-r14')
1116 log_chain = parse_log_output(output)
1117 expected_merges = {
1118 14: [], 13 : [14], 12 : [14], 11 : [14, 12],
1120 check_merge_results(log_chain, expected_merges)
1122 os.chdir(saved_cwd)
1124 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1125 '-g', '-r12', BRANCH_B_path)
1126 log_chain = parse_log_output(output)
1127 expected_merges = {
1128 12: [], 11 : [12],
1130 check_merge_results(log_chain, expected_merges)
1133 def merge_sensitive_log_branching_revision(sbox):
1134 "test 'svn log -g' on a branching revision"
1136 merge_history_repos(sbox)
1138 # Paths we care about
1139 wc_dir = sbox.wc_dir
1140 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1142 # Run log on a copying revision
1143 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1144 '-g', '-r10', BRANCH_B_path)
1146 # Parse and check output. There should be no extra revisions.
1147 log_chain = parse_log_output(output)
1148 expected_merges = {
1149 10: [],
1151 check_merge_results(log_chain, expected_merges)
1154 def merge_sensitive_log_non_branching_revision(sbox):
1155 "test 'svn log -g' on a non-branching revision"
1157 merge_history_repos(sbox)
1159 TRUNK_path = os.path.join(sbox.wc_dir, "trunk")
1161 # Run log on a non-copying revision that adds mergeinfo
1162 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1163 '-g', '-r6', TRUNK_path)
1165 # Parse and check output. There should be one extra revision.
1166 log_chain = parse_log_output(output)
1167 expected_merges = {
1168 6: [], 4 : [6],
1170 check_merge_results(log_chain, expected_merges)
1173 def merge_sensitive_log_added_path(sbox):
1174 "test 'svn log -g' a path added before merge"
1176 merge_history_repos(sbox)
1178 XI_path = os.path.join(sbox.wc_dir, "trunk", "A", "xi")
1180 # Run log on a non-copying revision that adds mergeinfo
1181 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1182 '-g', XI_path)
1184 # Parse and check output. There should be one extra revision.
1185 log_chain = parse_log_output(output)
1186 expected_merges = {
1187 14: [], 12 : [], 11 : [],
1189 check_merge_results(log_chain, expected_merges)
1191 revprops = [{'svn:author': 'jrandom', 'svn:date': '',
1192 'svn:log': 'Merged branches/b to trunk.'},
1193 {'svn:author': 'jrandom', 'svn:date': '',
1194 'svn:log': 'Merged branches/a to branches/b.'},
1195 {'svn:author': 'jrandom', 'svn:date': '',
1196 'svn:log': "Added 'xi' to branches/a,"
1197 ' made a few other changes.'}]
1198 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
1199 args=['-g', XI_path])
1202 def log_single_change(sbox):
1203 "test log -c for a single change"
1205 guarantee_repos_and_wc(sbox)
1206 repo_url = sbox.repo_url
1208 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1209 '-c', 4, repo_url)
1210 log_chain = parse_log_output(output)
1211 check_log_chain(log_chain, [4])
1213 def log_changes_range(sbox):
1214 "test log -c on range of changes"
1216 guarantee_repos_and_wc(sbox)
1217 repo_url = sbox.repo_url
1219 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1220 '-c', '2:5', repo_url)
1222 log_chain = parse_log_output(output)
1223 check_log_chain(log_chain, [2, 3, 4, 5])
1225 def log_changes_list(sbox):
1226 "test log -c on comma-separated list of changes"
1228 guarantee_repos_and_wc(sbox)
1229 repo_url = sbox.repo_url
1231 output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log',
1232 '-c', '2,5,7', repo_url)
1234 log_chain = parse_log_output(output)
1235 check_log_chain(log_chain, [2, 5, 7])
1237 #----------------------------------------------------------------------
1238 def only_one_wc_path(sbox):
1239 "svn log of two wc paths is disallowed"
1241 sbox.build(read_only = True)
1242 os.chdir(sbox.wc_dir)
1244 svntest.actions.run_and_verify_log_xml(
1245 expected_stderr=('.*When specifying working copy paths,'
1246 ' only one target may be given'),
1247 args=['A/mu', 'iota'])
1249 #----------------------------------------------------------------------
1250 def retrieve_revprops(sbox):
1251 "test revprop retrieval"
1253 sbox.build()
1254 svntest.actions.enable_revprop_changes(sbox.repo_dir)
1256 # test properties
1257 author = 'jrandom'
1258 msg1 = 'Log message for revision 1.'
1259 msg2 = 'Log message for revision 2.'
1260 custom_name = 'retrieve_revprops'
1261 custom_value = 'foo bar'
1263 # Commit a change.
1264 wc_dir = sbox.wc_dir
1265 cwd = os.getcwd()
1266 os.chdir(wc_dir)
1267 svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
1268 os.chdir(cwd)
1269 omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
1270 expected_output = svntest.wc.State(wc_dir, {
1271 'A/D/H/omega' : Item(verb='Sending'),
1273 expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1274 expected_status.tweak('A/D/H/omega', wc_rev=2, status=' ')
1275 svntest.actions.run_and_verify_commit(wc_dir,
1276 expected_output,
1277 expected_status,
1278 None,
1279 '-m', msg2,
1280 omega_path)
1282 os.chdir(wc_dir)
1284 # Set custom property on r1 and r2.
1285 svntest.actions.run_and_verify_svn(
1286 None, None, [], # message, expected_stdout, expected_stderr
1287 'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
1288 svntest.actions.run_and_verify_svn(
1289 None, None, [], # message, expected_stdout, expected_stderr
1290 'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
1292 # Can't set revprops with log.
1293 svntest.actions.run_and_verify_log_xml(
1294 expected_stderr=(".*cannot assign with 'with-revprop' option"
1295 " \(drop the '='\)"),
1296 args=['--with-revprop=foo=bar'])
1298 # basic test without revprop options
1299 svntest.actions.run_and_verify_log_xml(
1300 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1}],
1301 args=['-r1'])
1303 # basic test without revprop options, with multiple revisions
1304 svntest.actions.run_and_verify_log_xml(
1305 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1},
1306 {'svn:author': author, 'svn:date': '', 'svn:log': msg2}])
1308 # -q with no revprop options must suppress svn:log only.
1309 svntest.actions.run_and_verify_log_xml(
1310 expected_revprops=[{'svn:author': author, 'svn:date': ''}],
1311 args=['-q', '-r1'])
1313 # Request svn:date, svn:log, and a non-existent property.
1314 svntest.actions.run_and_verify_log_xml(
1315 expected_revprops=[{'svn:date': '', 'svn:log': msg1}],
1316 args=['-r1', '--with-revprop=svn:date', '--with-revprop', 'svn:log',
1317 '--with-revprop', 'nosuchprop'])
1319 # Get all revprops.
1320 svntest.actions.run_and_verify_log_xml(
1321 expected_revprops=[{'svn:author': author, 'svn:date': '',
1322 'svn:log': msg1, custom_name: custom_value}],
1323 args=['-r1', '--with-all-revprops'])
1325 # Get all revprops, with multiple revisions.
1326 svntest.actions.run_and_verify_log_xml(
1327 expected_revprops=[{'svn:author': author, 'svn:date': '',
1328 'svn:log': msg1, custom_name: custom_value},
1329 {'svn:author': author, 'svn:date': '',
1330 'svn:log': msg2, custom_name: custom_value}],
1331 args=['--with-all-revprops'])
1333 # Get only the custom property.
1334 svntest.actions.run_and_verify_log_xml(
1335 expected_revprops=[{custom_name: custom_value}],
1336 args=['-r1', '--with-revprop', custom_name])
1339 def log_xml_with_bad_data(sbox):
1340 "log --xml escapes non-utf8 data (issue #2866)"
1341 svntest.actions.load_repo(sbox, os.path.join(os.path.dirname(sys.argv[0]),
1342 'log_tests_data',
1343 'xml-invalid-chars.dump'))
1344 r0_props = {
1345 'svn:date' : '',
1346 'svn:log' : 'After the colon are a space, 3 bad chars, '
1347 + '2 good chars, and a period: '
1348 + '?\\021?\\022?\\017\t\n.' }
1349 svntest.actions.run_and_verify_log_xml(
1350 expected_revprops=(r0_props,), args=[sbox.repo_url])
1353 ########################################################################
1354 # Run the tests
1357 # list all tests here, starting with None:
1358 test_list = [ None,
1359 plain_log,
1360 log_with_empty_repos,
1361 log_where_nothing_changed,
1362 log_to_revision_zero,
1363 dynamic_revision,
1364 log_with_path_args,
1365 log_wc_with_peg_revision,
1366 url_missing_in_head,
1367 log_through_copyfrom_history,
1368 escape_control_chars,
1369 log_xml_empty_date,
1370 log_limit,
1371 log_base_peg,
1372 log_verbose,
1373 log_parser,
1374 XFail(SkipUnless(merge_sensitive_log_single_revision,
1375 server_has_mergeinfo)),
1376 XFail(SkipUnless(merge_sensitive_log_branching_revision,
1377 server_has_mergeinfo)),
1378 XFail(SkipUnless(merge_sensitive_log_non_branching_revision,
1379 server_has_mergeinfo)),
1380 XFail(SkipUnless(merge_sensitive_log_added_path,
1381 server_has_mergeinfo)),
1382 log_single_change,
1383 XFail(log_changes_range),
1384 XFail(log_changes_list),
1385 only_one_wc_path,
1386 retrieve_revprops,
1387 log_xml_with_bad_data,
1390 if __name__ == '__main__':
1391 svntest.main.run_tests(test_list)
1392 # NOTREACHED
1395 ### End of file.