Skip some mergeinfo-related tests in merge_authz, switch, and update
[svn.git] / subversion / tests / cmdline / log_tests.py
blobee8492f79cb04a64ba46fab935bd887c6cb51aa3
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) + '@HEAD')
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') + '@HEAD')
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) + '@HEAD')
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',
345 os.path.join('..', '..', 'trunk') + '@HEAD')
346 svntest.main.run_svn(None, 'ci', '-m',
347 "Bring branches/c up to date with trunk.",
348 '--username', svntest.main.wc_author2)
349 os.chdir(os.path.join('..', '..'))
351 # Modify a file on branches/c - r16
352 svntest.main.file_append_binary(os.path.join(branch_c, 'A', 'mu'),
353 "\nThis is yet more content in 'mu'.")
354 svntest.main.run_svn(None, 'ci', '-m',
355 "Modify 'mu' on branches/c.")
357 # Merge branches/c to trunk, which produces a conflict - r17
358 os.chdir('trunk')
359 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_c) + '@HEAD')
360 svntest.main.file_write(os.path.join('A', 'mu'),
361 "This is the file 'mu'.\n" +
362 "Don't forget to look at 'upsilon', as well.\n" +
363 "This is yet more content in 'mu'.",
364 "wb")
365 svntest.main.run_svn(None, 'resolved', os.path.join('A', 'mu'))
366 svntest.main.run_svn(None, 'ci', '-m',
367 "Merge branches/c to trunk, " +
368 "resolving a conflict in 'mu'.",
369 '--username', svntest.main.wc_author2)
370 os.chdir('..')
372 # Restore working directory
373 os.chdir(was_cwd)
376 # For errors seen while parsing log data.
377 class SVNLogParseError(Exception):
378 def __init__(self, args=None):
379 self.args = args
382 def parse_log_output(log_lines):
383 """Return a log chain derived from LOG_LINES.
384 A log chain is a list of hashes; each hash represents one log
385 message, in the order it appears in LOG_LINES (the first log
386 message in the data is also the first element of the list, and so
387 on).
389 Each hash contains the following keys/values:
391 'revision' ===> number
392 'author' ===> string
393 'date' ===> string
394 'msg' ===> string (the log message itself)
395 'lines' ===> number (so that it may be checked against rev)
396 If LOG_LINES contains changed-path information, then the hash
397 also contains
399 'paths' ===> list of tuples of the form (X, PATH), where X is the
400 first column of verbose output, and PATH is the affected path.
402 If LOG_LINES contains merge result information, then the hash also contains
404 'merges' ===> list of merging revisions that resulted in this log
405 being part of the list of messages.
408 # Here's some log output to look at while writing this function:
410 # ------------------------------------------------------------------------
411 # r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
413 # Log message for revision 5.
414 # ------------------------------------------------------------------------
415 # r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
417 # Log message for revision 4
418 # but with multiple lines
419 # to test the code.
420 # ------------------------------------------------------------------------
421 # r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
423 # Log message for revision 3.
424 # ------------------------------------------------------------------------
425 # r2 | kfogel | Tue 6 Nov 2001 17:18:16 | 3 lines
427 # Log message for revision 2
428 # but with multiple lines
429 # to test the code.
430 # ------------------------------------------------------------------------
431 # r1 | foo | Tue 6 Nov 2001 15:27:57 | 1 line
433 # Log message for revision 1.
434 # ------------------------------------------------------------------------
436 # Regular expression to match the header line of a log message, with
437 # these groups: (revision number), (author), (date), (num lines).
438 header_re = re.compile('^r([0-9]+) \| ' \
439 + '([^|]*) \| ([^|]*) \| ([0-9]+) lines?')
441 # The log chain to return.
442 chain = []
444 this_item = None
445 while 1:
446 try:
447 this_line = log_lines.pop(0)
448 except IndexError:
449 return chain
451 match = header_re.search(this_line)
452 if match and match.groups():
453 is_result = 0
454 this_item = {}
455 this_item['revision'] = int(match.group(1))
456 this_item['author'] = match.group(2)
457 this_item['date'] = match.group(3)
458 lines = int(match.group(4))
459 this_item['lines'] = lines
461 # Parse verbose output, starting with "Changed paths"
462 next_line = log_lines.pop(0)
463 if next_line.strip() == 'Changed paths:':
464 paths = []
465 path_line = log_lines.pop(0).strip()
467 # Stop on either a blank line or a "Merged via: ..." line
468 while path_line != '' and path_line[0:6] != 'Merged':
469 paths.append( (path_line[0], path_line[2:]) )
470 path_line = log_lines.pop(0).strip()
472 this_item['paths'] = paths
474 if path_line[0:6] == 'Merged':
475 is_result = 1
476 result_line = path_line
478 elif next_line[0:6] == 'Merged':
479 is_result = 1
480 result_line = next_line.strip()
482 # Parse output of "Merged via: ..." line
483 if is_result:
484 merges = []
485 prefix_len = len('Merged via: ')
486 for rev_str in result_line[prefix_len:].split(','):
487 merges.append(int(rev_str.strip()[1:]))
488 this_item['merges'] = merges
490 # Eat blank line
491 log_lines.pop(0)
493 # Accumulate the log message
494 msg = ''
495 for line in log_lines[0:lines]:
496 msg += line
497 del log_lines[0:lines]
498 elif this_line == msg_separator:
499 if this_item:
500 this_item['msg'] = msg
501 chain.append(this_item)
502 else: # if didn't see separator now, then something's wrong
503 print this_line
504 raise SVNLogParseError, "trailing garbage after log message"
506 return chain
509 class SVNUnexpectedLogs(svntest.Failure):
510 "Exception raised if a set of log messages doesn't meet expectations."
512 def __init__(self, msg, chain, field_selector = 'revision'):
513 """Stores the log chain for later use. FIELD_SELECTOR indicates
514 which individual field to display when turning the exception into
515 text."""
516 svntest.Failure.__init__(self, msg)
517 self.chain = chain
518 self.field_selector = field_selector
520 def __str__(self):
521 msg = svntest.Failure.__str__(self)
522 if self.chain:
523 chain_data = list(self.chain)
524 for i in range(0, len(self.chain)):
525 chain_data[i] = self.chain[i][self.field_selector]
526 msg = msg + ': Actual %s list was %s' % (self.field_selector, chain_data)
527 return msg
530 def check_log_chain(chain, revlist, path_counts=[]):
531 """Verify that log chain CHAIN contains the right log messages for
532 revisions START to END (see documentation for parse_log_output() for
533 more about log chains).
535 Do nothing if the log chain's messages run from revision START to END
536 and each log message contains a line of the form
538 'Log message for revision N'
540 where N is the revision number of that commit. Verify that
541 author and date are present and look sane, but don't check them too
542 carefully.
543 Also verify that even numbered commit messages have three lines.
545 If the length of PATH_COUNTS is greater than zero, make sure that each
546 log has that number of paths.
548 Raise an error if anything looks wrong.
551 nbr_expected = len(revlist)
552 if len(chain) != nbr_expected:
553 raise SVNUnexpectedLogs('Number of elements in log chain and revision ' +
554 'list %s not equal' % revlist, chain)
555 if path_counts and len(path_counts) != nbr_expected:
556 raise SVNUnexpectedLogs('Number of elements in log chain and path ' +
557 'counts %s not equal' % path_counts, chain)
558 missing_revs = []
559 for i in range(0, nbr_expected):
560 expect_rev = revlist[i]
561 log_item = chain[i]
562 saw_rev = log_item['revision']
563 date = log_item['date']
564 author = log_item['author']
565 msg = log_item['msg']
566 # The most important check is that the revision is right:
567 if expect_rev != saw_rev:
568 missing_revs.append(expect_rev)
569 continue
570 # Check that date looks at least vaguely right:
571 date_re = re.compile('[0-9]+')
572 if not date_re.search(date):
573 raise SVNUnexpectedLogs('Malformed date', chain, 'date')
574 # Authors are a little harder, since they might not exist over ra-dav.
575 # Well, it's not much of a check, but we'll do what we can.
576 author_re = re.compile('[a-zA-Z]+')
577 if (not (author_re.search(author)
578 or author == ''
579 or author == '(no author)')):
580 raise SVNUnexpectedLogs('Malformed author', chain, 'author')
582 # Verify the expectation that even-numbered revisions in the Greek
583 # tree tweaked by the log tests have 3-line log messages.
584 if (saw_rev % 2 == 0 and log_item['lines'] != 3):
585 raise SVNUnexpectedLogs('Malformed log line counts', chain, 'lines')
587 # Check that the log message looks right:
588 pattern = 'Log message for revision ' + `saw_rev`
589 msg_re = re.compile(pattern)
590 if not msg_re.search(msg):
591 raise SVNUnexpectedLogs("Malformed log message, expected '%s'" % msg,
592 chain)
594 # If path_counts, check the number of changed paths
595 if path_counts:
596 if (not 'paths' in log_item) or (not log_item['paths']):
597 raise SVNUnexpectedLogs("No changed path information", chain)
598 if path_counts[i] != len(log_item['paths']):
599 raise SVNUnexpectedLogs("Changed paths counts not equal for " +
600 "revision %d" % (i + 1), chain)
602 nbr_missing_revs = len(missing_revs)
603 if nbr_missing_revs > 0:
604 raise SVNUnexpectedLogs('Unable to find expected revision(s) %s' %
605 missing_revs, chain)
609 ######################################################################
610 # Tests
613 #----------------------------------------------------------------------
614 def plain_log(sbox):
615 "'svn log', no args, top of wc"
617 guarantee_repos_and_wc(sbox)
619 os.chdir(sbox.wc_dir)
621 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
622 'log')
624 log_chain = parse_log_output(output)
625 check_log_chain(log_chain, range(max_revision, 1 - 1, -1))
628 #----------------------------------------------------------------------
629 def log_with_empty_repos(sbox):
630 "'svn log' on an empty repository"
632 # Create virgin repos
633 svntest.main.safe_rmtree(sbox.repo_dir, 1)
634 svntest.main.create_repos(sbox.repo_dir)
636 svntest.actions.run_and_verify_svn(None, None, [],
637 'log',
638 sbox.repo_url)
640 #----------------------------------------------------------------------
641 def log_where_nothing_changed(sbox):
642 "'svn log -rN some_dir_unchanged_in_N'"
643 sbox.build()
645 # Fix bug whereby running 'svn log -rN SOMEPATH' would result in an
646 # xml protocol error if there were no changes in revision N
647 # underneath SOMEPATH. This problem was introduced in revision
648 # 3811, which didn't cover the case where svn_repos_get_logs might
649 # invoke log_receiver zero times. Since the receiver never ran, the
650 # lrb->needs_header flag never got cleared. Control would proceed
651 # without error to the end of dav_svn__log_report(), which would
652 # send a closing tag even though no opening tag had ever been sent.
654 rho_path = os.path.join(sbox.wc_dir, 'A', 'D', 'G', 'rho')
655 svntest.main.file_append(rho_path, "some new material in rho")
656 svntest.actions.run_and_verify_svn(None, None, [],
657 'ci', '-m',
658 'log msg', rho_path)
660 # Now run 'svn log -r2' on a directory unaffected by revision 2.
661 H_path = os.path.join(sbox.wc_dir, 'A', 'D', 'H')
662 svntest.actions.run_and_verify_svn(None, None, [],
663 'log', '-r', '2', H_path)
666 #----------------------------------------------------------------------
667 def log_to_revision_zero(sbox):
668 "'svn log -v -r 1:0 wc_root'"
669 sbox.build(read_only = True)
671 # This used to segfault the server.
673 svntest.actions.run_and_verify_svn(None, None, [],
674 'log', '-v',
675 '-r', '1:0', sbox.wc_dir)
677 #----------------------------------------------------------------------
678 def log_with_path_args(sbox):
679 "'svn log', with args, top of wc"
681 guarantee_repos_and_wc(sbox)
683 os.chdir(sbox.wc_dir)
685 exit_code, output, err = svntest.actions.run_and_verify_svn(
686 None, None, [],
687 'log', sbox.repo_url, 'A/D/G', 'A/D/H')
689 log_chain = parse_log_output(output)
690 check_log_chain(log_chain, [8, 6, 5, 3, 1])
692 #----------------------------------------------------------------------
693 def dynamic_revision(sbox):
694 "'svn log -r COMMITTED' of dynamic/local WC rev"
696 guarantee_repos_and_wc(sbox)
697 os.chdir(sbox.wc_dir)
699 revprops = [{'svn:author': 'jrandom',
700 'svn:date': '', 'svn:log': 'Log message for revision 9'}]
701 for rev in ('HEAD', 'BASE', 'COMMITTED'):
702 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
703 args=['-r', rev])
704 revprops[0]['svn:log'] = ('Log message for revision 8\n'
705 ' but with multiple lines\n'
706 ' to test the code')
707 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
708 args=['-r', 'PREV'])
710 #----------------------------------------------------------------------
711 def log_wc_with_peg_revision(sbox):
712 "'svn log wc_target@N'"
713 guarantee_repos_and_wc(sbox)
714 my_path = os.path.join(sbox.wc_dir, "A", "B", "E", "beta") + "@8"
715 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
716 'log', my_path)
717 check_log_chain(parse_log_output(output), [1])
719 #----------------------------------------------------------------------
720 def url_missing_in_head(sbox):
721 "'svn log target@N' when target removed from HEAD"
723 guarantee_repos_and_wc(sbox)
725 my_url = sbox.repo_url + "/A/B/E/alpha" + "@8"
727 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
728 'log', my_url)
729 check_log_chain(parse_log_output(output), [3, 1])
731 #----------------------------------------------------------------------
732 def log_through_copyfrom_history(sbox):
733 "'svn log TGT' with copyfrom history"
734 sbox.build()
735 wc_dir = sbox.wc_dir
736 msg_file=os.path.join(sbox.repo_dir, 'log-msg')
737 msg_file=os.path.abspath(msg_file)
739 mu_path = os.path.join(wc_dir, 'A', 'mu')
740 mu2_path = os.path.join(wc_dir, 'A', 'mu2')
741 mu_URL = sbox.repo_url + '/A/mu'
742 mu2_URL = sbox.repo_url + '/A/mu2'
744 msg2=""" Log message for revision 2
745 but with multiple lines
746 to test the code"""
748 msg4=""" Log message for revision 4
749 but with multiple lines
750 to test the code"""
752 msg6=""" Log message for revision 6
753 but with multiple lines
754 to test the code"""
756 svntest.main.file_write(msg_file, msg2)
757 svntest.main.file_append(mu_path, "2")
758 svntest.actions.run_and_verify_svn(None, None, [],
759 'ci', wc_dir,
760 '-F', msg_file)
761 svntest.main.file_append(mu2_path, "this is mu2")
762 svntest.actions.run_and_verify_svn(None, None, [], 'add', mu2_path)
763 svntest.actions.run_and_verify_svn(None, None, [],
764 'ci', wc_dir,
765 '-m', "Log message for revision 3")
766 svntest.actions.run_and_verify_svn(None, None, [], 'rm', mu2_path)
767 svntest.main.file_write(msg_file, msg4)
768 svntest.actions.run_and_verify_svn(None, None, [],
769 'ci', wc_dir,
770 '-F', msg_file)
771 svntest.main.file_append(mu_path, "5")
772 svntest.actions.run_and_verify_svn(None, None, [],
773 'ci', wc_dir,
774 '-m', "Log message for revision 5")
776 svntest.main.file_write(msg_file, msg6)
777 svntest.actions.run_and_verify_svn(None, None, [],
778 'cp', '-r', '5', mu_URL, mu2_URL,
779 '-F', msg_file)
780 svntest.actions.run_and_verify_svn(None, None, [],
781 'up', wc_dir)
783 # The full log for mu2 is relatively unsurprising
784 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
785 'log', mu2_path)
786 log_chain = parse_log_output(output)
787 check_log_chain(log_chain, [6, 5, 2, 1])
789 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
790 'log', mu2_URL)
791 log_chain = parse_log_output(output)
792 check_log_chain(log_chain, [6, 5, 2, 1])
794 # First "oddity", the full log for mu2 doesn't include r3, but the -r3
795 # log works!
796 peg_mu2_path = mu2_path + "@3"
797 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
798 'log', '-r', '3',
799 peg_mu2_path)
800 log_chain = parse_log_output(output)
801 check_log_chain(log_chain, [3])
803 peg_mu2_URL = mu2_URL + "@3"
804 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
805 'log', '-r', '3',
806 peg_mu2_URL)
807 log_chain = parse_log_output(output)
808 check_log_chain(log_chain, [3])
809 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
810 'log', '-r', '2',
811 mu2_path)
812 log_chain = parse_log_output(output)
813 check_log_chain(log_chain, [2])
815 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
816 'log', '-r', '2',
817 mu2_URL)
818 log_chain = parse_log_output(output)
819 check_log_chain(log_chain, [2])
821 #----------------------------------------------------------------------
822 def escape_control_chars(sbox):
823 "mod_dav_svn must escape invalid XML control chars"
825 dump_str = """SVN-fs-dump-format-version: 2
827 UUID: ffcae364-69ee-0310-a980-ca5f10462af2
829 Revision-number: 0
830 Prop-content-length: 56
831 Content-length: 56
834 svn:date
835 V 27
836 2005-01-24T10:09:21.759592Z
837 PROPS-END
839 Revision-number: 1
840 Prop-content-length: 128
841 Content-length: 128
844 svn:log
845 V 100
846 This msg contains a Ctrl-T (\x14) and a Ctrl-I (\t).
847 The former might be escaped, but the latter never.
849 K 10
850 svn:author
852 jrandom
854 svn:date
855 V 27
856 2005-01-24T10:09:22.012524Z
857 PROPS-END
860 # load dumpfile with control character into repos to get
861 # a log with control char content
862 svntest.actions.load_repo(sbox, dump_str=dump_str)
864 URL = sbox.repo_url
866 # run log
867 exit_code, output, errput = svntest.actions.run_and_verify_svn(
868 None, None, [], 'log', URL)
870 # Verify the output contains either the expected fuzzy escape
871 # sequence, or the literal control char.
872 match_unescaped_ctrl_re = "This msg contains a Ctrl-T \(.\) " \
873 "and a Ctrl-I \(\t\)\."
874 match_escaped_ctrl_re = "^This msg contains a Ctrl-T \(\?\\\\020\) " \
875 "and a Ctrl-I \(\t\)\."
876 matched = None
877 for line in output:
878 if re.match(match_unescaped_ctrl_re, line) \
879 or re.match(match_escaped_ctrl_re, line):
880 matched = 1
882 if not matched:
883 raise svntest.Failure("log message not transmitted properly:" +
884 str(output) + "\n" + "error: " + str(errput))
886 #----------------------------------------------------------------------
887 def log_xml_empty_date(sbox):
888 "svn log --xml must not print empty date elements"
889 sbox.build()
891 # Create the revprop-change hook for this test
892 svntest.actions.enable_revprop_changes(sbox.repo_dir)
894 date_re = re.compile('<date');
896 # Ensure that we get a date before we delete the property.
897 exit_code, output, errput = svntest.actions.run_and_verify_svn(
898 None, None, [], 'log', '--xml', '-r1', sbox.wc_dir)
900 matched = 0
901 for line in output:
902 if date_re.search(line):
903 matched = 1
904 if not matched:
905 raise svntest.Failure("log contains no date element")
907 # Set the svn:date revprop to the empty string on revision 1.
908 svntest.actions.run_and_verify_svn(None, None, [],
909 'pdel', '--revprop', '-r1', 'svn:date',
910 sbox.wc_dir)
912 exit_code, output, errput = svntest.actions.run_and_verify_svn(
913 None, None, [], 'log', '--xml', '-r1', sbox.wc_dir)
915 for line in output:
916 if date_re.search(line):
917 raise svntest.Failure("log contains date element when svn:date is empty")
919 #----------------------------------------------------------------------
920 def log_limit(sbox):
921 "svn log --limit"
922 guarantee_repos_and_wc(sbox)
924 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
925 'log',
926 '--limit', '2',
927 sbox.repo_url)
928 log_chain = parse_log_output(out)
929 check_log_chain(log_chain, [9, 8])
931 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
932 'log',
933 '--limit', '2',
934 sbox.repo_url,
935 'A/B')
936 log_chain = parse_log_output(out)
937 check_log_chain(log_chain, [9, 6])
939 exit_code, out, err = svntest.actions.run_and_verify_svn(
940 None, None, [],
941 'log', '--limit', '2', '--revision', '2:HEAD', sbox.repo_url, 'A/B')
943 log_chain = parse_log_output(out)
944 check_log_chain(log_chain, [3, 6])
946 # Use -l instead of --limit to test both option forms.
947 exit_code, out, err = svntest.actions.run_and_verify_svn(
948 None, None, [],
949 'log', '-l', '2', '--revision', '1', sbox.repo_url, 'A/B')
951 log_chain = parse_log_output(out)
952 check_log_chain(log_chain, [1])
954 must_be_positive = ".*Argument to --limit must be positive.*"
956 # error expected when limit <= 0
957 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
958 'log', '--limit', '0', '--revision', '1',
959 sbox.repo_url, 'A/B')
961 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
962 'log', '--limit', '-1', '--revision', '1',
963 sbox.repo_url, 'A/B')
965 def log_base_peg(sbox):
966 "run log on an @BASE target"
967 guarantee_repos_and_wc(sbox)
969 target = os.path.join(sbox.wc_dir, 'A', 'B', 'E', 'beta') + '@BASE'
971 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
972 'log', target)
974 log_chain = parse_log_output(out)
975 check_log_chain(log_chain, [9, 1])
977 svntest.actions.run_and_verify_svn(None, None, [], 'update', '-r', '1',
978 sbox.wc_dir)
980 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
981 'log', target)
983 log_chain = parse_log_output(out)
984 check_log_chain(log_chain, [1])
987 def log_verbose(sbox):
988 "run log with verbose output"
989 guarantee_repos_and_wc(sbox)
991 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
992 'log', '-v',
993 sbox.wc_dir)
995 log_chain = parse_log_output(output)
996 path_counts = [2, 2, 1, 2, 2, 2, 4, 1, 20]
997 check_log_chain(log_chain, range(max_revision, 1 - 1, -1), path_counts)
1000 def log_parser(sbox):
1001 "meta-test for the log parser"
1003 logs = ['''------------------------------------------------------------------------
1004 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1005 Changed paths:
1006 M /trunk/death-ray.c
1007 M /trunk/frobnicator/frapnalyzer.c
1009 Merge r12 and r14 from branch to trunk.
1010 ------------------------------------------------------------------------
1011 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1012 Changed paths:
1013 M /trunk/death-ray.c
1014 Merged via: r24
1016 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1017 ------------------------------------------------------------------------
1018 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1019 Changed paths:
1020 M /trunk/frobnicator/frapnalyzer.c
1021 M /trunk/death-ray.c
1022 Merged via: r24
1024 Fix frapnalyzer bug in frobnicator.
1025 ------------------------------------------------------------------------''',
1026 '''------------------------------------------------------------------------
1027 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1029 Merge r12 and r14 from branch to trunk.
1030 ------------------------------------------------------------------------
1031 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1032 Merged via: r24
1034 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1035 ------------------------------------------------------------------------
1036 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1037 Merged via: r24
1039 Fix frapnalyzer bug in frobnicator.
1040 ------------------------------------------------------------------------
1041 r10 | alice | 2007-04-16 19:02:28 -0500 (Mon, 16 Apr 2007) | 1 line
1042 Merged via: r12, r24
1044 Fix frapnalyzer documentation.
1045 ------------------------------------------------------------------------
1046 r9 | bob | 2007-04-16 19:01:48 -0500 (Mon, 16 Apr 2007) | 1 line
1047 Merged via: r12, r24
1049 Whitespace fixes. No functional change.
1050 ------------------------------------------------------------------------''',
1051 '''------------------------------------------------------------------------
1052 r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
1054 Log message for revision 5.
1055 ------------------------------------------------------------------------
1056 r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
1058 Log message for revision 4
1059 but with multiple lines
1060 to test the code.
1061 ------------------------------------------------------------------------
1062 r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
1064 Log message for revision 3.
1065 ------------------------------------------------------------------------''',
1066 ] # end of log list
1068 for log in logs:
1069 log_chain = parse_log_output([line+"\n" for line in log.split("\n")])
1072 def check_merge_results(log_chain, expected_merges):
1073 '''Check LOG_CHAIN to see if the log information contains 'Merged via'
1074 information indicated by EXPECTED_MERGES. EXPECTED_MERGES is a dictionary
1075 whose key is the merged revision, and whose value is the merging revision.'''
1077 # Check to see if the number and values of the revisions is correct
1078 for log in log_chain:
1079 if log['revision'] not in expected_merges:
1080 raise SVNUnexpectedLogs("Found unexpected revision %d" %
1081 log['revision'], log_chain)
1083 # Check to see that each rev in expected_merges contains the correct data
1084 for rev in expected_merges:
1085 try:
1086 log = [x for x in log_chain if x['revision'] == rev][0]
1087 if 'merges' in log.keys():
1088 actual = log['merges']
1089 else:
1090 actual = []
1091 expected = expected_merges[rev]
1093 if actual != expected:
1094 raise SVNUnexpectedLogs(("Merging revisions in rev %d not correct; " +
1095 "expecting %s, found %s") %
1096 (rev, str(expected), str(actual)), log_chain)
1097 except IndexError:
1098 raise SVNUnexpectedLogs("Merged revision '%d' missing" % rev, log_chain)
1101 def merge_sensitive_log_single_revision(sbox):
1102 "test sensitive log on a single revision"
1104 merge_history_repos(sbox)
1106 # Paths we care about
1107 wc_dir = sbox.wc_dir
1108 TRUNK_path = os.path.join(wc_dir, "trunk")
1109 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1111 # Run the merge sensitive log, and compare results
1112 saved_cwd = os.getcwd()
1114 os.chdir(TRUNK_path)
1115 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1116 'log', '-g',
1117 '-r14')
1120 log_chain = parse_log_output(output)
1121 expected_merges = {
1122 14: [], 13 : [14], 12 : [14], 11 : [14, 12],
1124 check_merge_results(log_chain, expected_merges)
1126 os.chdir(saved_cwd)
1128 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1129 'log', '-g',
1130 '-r12',
1131 BRANCH_B_path)
1132 log_chain = parse_log_output(output)
1133 expected_merges = {
1134 12: [], 11 : [12],
1136 check_merge_results(log_chain, expected_merges)
1139 def merge_sensitive_log_branching_revision(sbox):
1140 "test 'svn log -g' on a branching revision"
1142 merge_history_repos(sbox)
1144 # Paths we care about
1145 wc_dir = sbox.wc_dir
1146 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1148 # Run log on a copying revision
1149 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1150 'log', '-g',
1151 '-r10',
1152 BRANCH_B_path)
1154 # Parse and check output. There should be no extra revisions.
1155 log_chain = parse_log_output(output)
1156 expected_merges = {
1157 10: [],
1159 check_merge_results(log_chain, expected_merges)
1162 def merge_sensitive_log_non_branching_revision(sbox):
1163 "test 'svn log -g' on a non-branching revision"
1165 merge_history_repos(sbox)
1167 TRUNK_path = os.path.join(sbox.wc_dir, "trunk")
1169 # Run log on a non-copying revision that adds mergeinfo
1170 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1171 'log', '-g',
1172 '-r6',
1173 TRUNK_path)
1175 # Parse and check output. There should be one extra revision.
1176 log_chain = parse_log_output(output)
1177 expected_merges = {
1178 6: [], 4 : [6], 3: [6],
1180 check_merge_results(log_chain, expected_merges)
1183 def merge_sensitive_log_added_path(sbox):
1184 "test 'svn log -g' a path added before merge"
1186 merge_history_repos(sbox)
1188 XI_path = os.path.join(sbox.wc_dir, "trunk", "A", "xi")
1190 # Run log on a non-copying revision that adds mergeinfo
1191 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1192 'log', '-g',
1193 XI_path)
1195 # Parse and check output. There should be one extra revision.
1196 log_chain = parse_log_output(output)
1197 expected_merges = {
1198 14: [], 12 : [], 11 : [],
1200 check_merge_results(log_chain, expected_merges)
1202 revprops = [{'svn:author': 'jconstant', 'svn:date': '',
1203 'svn:log': 'Merged branches/b to trunk.'},
1204 {'svn:author': 'jconstant', 'svn:date': '',
1205 'svn:log': 'Merged branches/a to branches/b.'},
1206 {'svn:author': 'jrandom', 'svn:date': '',
1207 'svn:log': "Added 'xi' to branches/a,"
1208 ' made a few other changes.'}]
1209 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
1210 args=['-g', XI_path])
1213 def log_single_change(sbox):
1214 "test log -c for a single change"
1216 guarantee_repos_and_wc(sbox)
1217 repo_url = sbox.repo_url
1219 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1220 'log', '-c',
1221 4, repo_url)
1222 log_chain = parse_log_output(output)
1223 check_log_chain(log_chain, [4])
1225 def log_changes_range(sbox):
1226 "test log -c on range of changes"
1228 guarantee_repos_and_wc(sbox)
1229 repo_url = sbox.repo_url
1231 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1232 'log', '-c',
1233 '2:5', repo_url)
1235 log_chain = parse_log_output(output)
1236 check_log_chain(log_chain, [2, 3, 4, 5])
1238 def log_changes_list(sbox):
1239 "test log -c on comma-separated list of changes"
1241 guarantee_repos_and_wc(sbox)
1242 repo_url = sbox.repo_url
1244 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1245 'log', '-c',
1246 '2,5,7',
1247 repo_url)
1249 log_chain = parse_log_output(output)
1250 check_log_chain(log_chain, [2, 5, 7])
1252 #----------------------------------------------------------------------
1253 def only_one_wc_path(sbox):
1254 "svn log of two wc paths is disallowed"
1256 sbox.build(read_only = True)
1257 os.chdir(sbox.wc_dir)
1259 svntest.actions.run_and_verify_log_xml(
1260 expected_stderr=('.*When specifying working copy paths,'
1261 ' only one target may be given'),
1262 args=['A/mu', 'iota'])
1264 #----------------------------------------------------------------------
1265 def retrieve_revprops(sbox):
1266 "test revprop retrieval"
1268 sbox.build()
1269 svntest.actions.enable_revprop_changes(sbox.repo_dir)
1271 # test properties
1272 author = 'jrandom'
1273 msg1 = 'Log message for revision 1.'
1274 msg2 = 'Log message for revision 2.'
1275 custom_name = 'retrieve_revprops'
1276 custom_value = 'foo bar'
1278 # Commit a change.
1279 wc_dir = sbox.wc_dir
1280 cwd = os.getcwd()
1281 os.chdir(wc_dir)
1282 svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
1283 os.chdir(cwd)
1284 omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
1285 expected_output = svntest.wc.State(wc_dir, {
1286 'A/D/H/omega' : Item(verb='Sending'),
1288 expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1289 expected_status.tweak('A/D/H/omega', wc_rev=2, status=' ')
1290 svntest.actions.run_and_verify_commit(wc_dir,
1291 expected_output,
1292 expected_status,
1293 None,
1294 '-m', msg2,
1295 omega_path)
1297 os.chdir(wc_dir)
1299 # Set custom property on r1 and r2.
1300 svntest.actions.run_and_verify_svn(
1301 None, None, [], # message, expected_stdout, expected_stderr
1302 'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
1303 svntest.actions.run_and_verify_svn(
1304 None, None, [], # message, expected_stdout, expected_stderr
1305 'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
1307 # Can't set revprops with log.
1308 svntest.actions.run_and_verify_log_xml(
1309 expected_stderr=(".*cannot assign with 'with-revprop' option"
1310 " \(drop the '='\)"),
1311 args=['--with-revprop=foo=bar'])
1313 # basic test without revprop options
1314 svntest.actions.run_and_verify_log_xml(
1315 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1}],
1316 args=['-r1'])
1318 # basic test without revprop options, with multiple revisions
1319 svntest.actions.run_and_verify_log_xml(
1320 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1},
1321 {'svn:author': author, 'svn:date': '', 'svn:log': msg2}])
1323 # -q with no revprop options must suppress svn:log only.
1324 svntest.actions.run_and_verify_log_xml(
1325 expected_revprops=[{'svn:author': author, 'svn:date': ''}],
1326 args=['-q', '-r1'])
1328 # Request svn:date, svn:log, and a non-existent property.
1329 svntest.actions.run_and_verify_log_xml(
1330 expected_revprops=[{'svn:date': '', 'svn:log': msg1}],
1331 args=['-r1', '--with-revprop=svn:date', '--with-revprop', 'svn:log',
1332 '--with-revprop', 'nosuchprop'])
1334 # Get all revprops.
1335 svntest.actions.run_and_verify_log_xml(
1336 expected_revprops=[{'svn:author': author, 'svn:date': '',
1337 'svn:log': msg1, custom_name: custom_value}],
1338 args=['-r1', '--with-all-revprops'])
1340 # Get all revprops, with multiple revisions.
1341 svntest.actions.run_and_verify_log_xml(
1342 expected_revprops=[{'svn:author': author, 'svn:date': '',
1343 'svn:log': msg1, custom_name: custom_value},
1344 {'svn:author': author, 'svn:date': '',
1345 'svn:log': msg2, custom_name: custom_value}],
1346 args=['--with-all-revprops'])
1348 # Get only the custom property.
1349 svntest.actions.run_and_verify_log_xml(
1350 expected_revprops=[{custom_name: custom_value}],
1351 args=['-r1', '--with-revprop', custom_name])
1354 def log_xml_with_bad_data(sbox):
1355 "log --xml escapes non-utf8 data (issue #2866)"
1356 svntest.actions.load_repo(sbox, os.path.join(os.path.dirname(sys.argv[0]),
1357 'log_tests_data',
1358 'xml-invalid-chars.dump'))
1359 r0_props = {
1360 'svn:date' : '',
1361 'svn:log' : 'After the colon are a space, 3 bad chars, '
1362 + '2 good chars, and a period: '
1363 + '?\\021?\\022?\\017\t\n.' }
1364 svntest.actions.run_and_verify_log_xml(
1365 expected_revprops=(r0_props,), args=[sbox.repo_url])
1368 ########################################################################
1369 # Run the tests
1372 # list all tests here, starting with None:
1373 test_list = [ None,
1374 plain_log,
1375 log_with_empty_repos,
1376 log_where_nothing_changed,
1377 log_to_revision_zero,
1378 dynamic_revision,
1379 log_with_path_args,
1380 log_wc_with_peg_revision,
1381 url_missing_in_head,
1382 log_through_copyfrom_history,
1383 escape_control_chars,
1384 log_xml_empty_date,
1385 log_limit,
1386 log_base_peg,
1387 log_verbose,
1388 log_parser,
1389 SkipUnless(merge_sensitive_log_single_revision,
1390 server_has_mergeinfo),
1391 SkipUnless(merge_sensitive_log_branching_revision,
1392 server_has_mergeinfo),
1393 SkipUnless(merge_sensitive_log_non_branching_revision,
1394 server_has_mergeinfo),
1395 SkipUnless(merge_sensitive_log_added_path,
1396 server_has_mergeinfo),
1397 log_single_change,
1398 XFail(log_changes_range),
1399 XFail(log_changes_list),
1400 only_one_wc_path,
1401 retrieve_revprops,
1402 log_xml_with_bad_data,
1405 if __name__ == '__main__':
1406 svntest.main.run_tests(test_list)
1407 # NOTREACHED
1410 ### End of file.