Fix compiler warning due to missing function prototype.
[svn.git] / subversion / tests / cmdline / log_tests.py
blob4b8873b2434823f0eb75a8eb0f043532008ee59d
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
27 from svntest.main import SVN_PROP_MERGEINFO
29 ######################################################################
31 # The Plan:
33 # Get a repository, commit about 6 or 7 revisions to it, each
34 # involving different kinds of operations. Make sure to have some
35 # add, del, mv, cp, as well as file modifications, and make sure that
36 # some files are modified more than once.
38 # Give each commit a recognizable log message. Test all combinations
39 # of -r options, including none. Then test with -v, which will
40 # (presumably) show changed paths as well.
42 ######################################################################
46 ######################################################################
47 # Globals
50 # These variables are set by guarantee_repos_and_wc().
51 max_revision = 0 # Highest revision in the repos
53 # What separates log msgs from one another in raw log output.
54 msg_separator = '------------------------------------' \
55 + '------------------------------------\n'
58 # (abbreviation)
59 Skip = svntest.testcase.Skip
60 SkipUnless = svntest.testcase.SkipUnless
61 XFail = svntest.testcase.XFail
62 Item = svntest.wc.StateItem
65 ######################################################################
66 # Utilities
69 def guarantee_repos_and_wc(sbox):
70 "Make a repos and wc, commit max_revision revs."
71 global max_revision
73 sbox.build()
74 wc_path = sbox.wc_dir
75 msg_file=os.path.join(sbox.repo_dir, 'log-msg')
76 msg_file=os.path.abspath(msg_file)
78 # Now we have a repos and wc at revision 1.
80 was_cwd = os.getcwd()
81 os.chdir(wc_path)
83 # Set up the paths we'll be using most often.
84 iota_path = os.path.join('iota')
85 mu_path = os.path.join('A', 'mu')
86 B_path = os.path.join('A', 'B')
87 omega_path = os.path.join('A', 'D', 'H', 'omega')
88 pi_path = os.path.join('A', 'D', 'G', 'pi')
89 rho_path = os.path.join('A', 'D', 'G', 'rho')
90 alpha_path = os.path.join('A', 'B', 'E', 'alpha')
91 beta_path = os.path.join('A', 'B', 'E', 'beta')
92 psi_path = os.path.join('A', 'D', 'H', 'psi')
93 epsilon_path = os.path.join('A', 'C', 'epsilon')
95 # Do a varied bunch of commits. No copies yet, we'll wait till Ben
96 # is done for that.
98 # Revision 2: edit iota
99 msg=""" Log message for revision 2
100 but with multiple lines
101 to test the code"""
102 svntest.main.file_write(msg_file, msg)
103 svntest.main.file_append(iota_path, "2")
104 svntest.main.run_svn(None,
105 'ci', '-F', msg_file)
106 svntest.main.run_svn(None,
107 'up')
109 # Revision 3: edit A/D/H/omega, A/D/G/pi, A/D/G/rho, and A/B/E/alpha
110 svntest.main.file_append(omega_path, "3")
111 svntest.main.file_append(pi_path, "3")
112 svntest.main.file_append(rho_path, "3")
113 svntest.main.file_append(alpha_path, "3")
114 svntest.main.run_svn(None,
115 'ci', '-m', "Log message for revision 3")
116 svntest.main.run_svn(None,
117 'up')
119 # Revision 4: edit iota again, add A/C/epsilon
120 msg=""" Log message for revision 4
121 but with multiple lines
122 to test the code"""
123 svntest.main.file_write(msg_file, msg)
124 svntest.main.file_append(iota_path, "4")
125 svntest.main.file_append(epsilon_path, "4")
126 svntest.main.run_svn(None, 'add', epsilon_path)
127 svntest.main.run_svn(None,
128 'ci', '-F', msg_file)
129 svntest.main.run_svn(None,
130 'up')
132 # Revision 5: edit A/C/epsilon, delete A/D/G/rho
133 svntest.main.file_append(epsilon_path, "5")
134 svntest.main.run_svn(None, 'rm', rho_path)
135 svntest.main.run_svn(None,
136 'ci', '-m', "Log message for revision 5")
137 svntest.main.run_svn(None,
138 'up')
140 # Revision 6: prop change on A/B, edit A/D/H/psi
141 msg=""" Log message for revision 6
142 but with multiple lines
143 to test the code"""
144 svntest.main.file_write(msg_file, msg)
145 svntest.main.run_svn(None, 'ps', 'blue', 'azul', B_path)
146 svntest.main.file_append(psi_path, "6")
147 svntest.main.run_svn(None,
148 'ci', '-F', msg_file)
149 svntest.main.run_svn(None,
150 'up')
152 # Revision 7: edit A/mu, prop change on A/mu
153 svntest.main.file_append(mu_path, "7")
154 svntest.main.run_svn(None, 'ps', 'red', 'burgundy', mu_path)
155 svntest.main.run_svn(None,
156 'ci', '-m', "Log message for revision 7")
157 svntest.main.run_svn(None,
158 'up')
160 # Revision 8: edit iota yet again, re-add A/D/G/rho
161 msg=""" Log message for revision 8
162 but with multiple lines
163 to test the code"""
164 svntest.main.file_write(msg_file, msg)
165 svntest.main.file_append(iota_path, "8")
166 svntest.main.file_append(rho_path, "8")
167 svntest.main.run_svn(None, 'add', rho_path)
168 svntest.main.run_svn(None,
169 'ci', '-F', msg_file)
170 svntest.main.run_svn(None,
171 'up')
173 # Revision 9: edit A/B/E/beta, delete A/B/E/alpha
174 svntest.main.file_append(beta_path, "9")
175 svntest.main.run_svn(None, 'rm', alpha_path)
176 svntest.main.run_svn(None,
177 'ci', '-m', "Log message for revision 9")
178 svntest.main.run_svn(None,
179 'up')
181 max_revision = 9
183 # Restore.
184 os.chdir(was_cwd)
186 # Let's run 'svn status' and make sure the working copy looks
187 # exactly the way we think it should. Start with a generic
188 # greek-tree-list, where every local and repos revision is at 9.
189 expected_status = svntest.actions.get_virginal_state(wc_path, 9)
190 expected_status.remove('A/B/E/alpha')
191 expected_status.add({
192 'A/C/epsilon' : Item(status=' ', wc_rev=9),
195 # props exist on A/B and A/mu
196 expected_status.tweak('A/B', 'A/mu', status=' ')
198 # Run 'svn st -uv' and compare the actual results with our tree.
199 svntest.actions.run_and_verify_status(wc_path, expected_status)
202 def merge_history_repos(sbox):
203 """Make a repos with varied and interesting merge history, similar
204 to the repos found at:
205 http://merge-tracking.open.collab.net/servlets/ProjectProcess?documentContainer=c2__Sample%20repository"""
207 upsilon_path = os.path.join('A', 'upsilon')
208 omicron_path = os.path.join('blocked', 'omicron')
209 branch_a = os.path.join('branches', 'a')
210 branch_b = os.path.join('branches', 'b')
211 branch_c = os.path.join('branches', 'c')
213 # Create an empty repository - r0
214 svntest.main.safe_rmtree(sbox.repo_dir, 1)
215 svntest.main.safe_rmtree(sbox.wc_dir, 1)
216 svntest.main.create_repos(sbox.repo_dir)
218 svntest.actions.run_and_verify_svn(None, None, [], "co", sbox.repo_url,
219 sbox.wc_dir)
220 was_cwd = os.getcwd()
221 os.chdir(sbox.wc_dir)
223 # Create trunk/tags/branches - r1
224 svntest.main.run_svn(None, 'mkdir', 'trunk')
225 svntest.main.run_svn(None, 'mkdir', 'tags')
226 svntest.main.run_svn(None, 'mkdir', 'branches')
227 svntest.main.run_svn(None, 'ci', '-m',
228 'Add trunk/tags/branches structure.')
230 # Import greek tree to trunk - r2
231 svntest.main.greek_state.write_to_disk('trunk')
232 svntest.main.run_svn(None, 'add', os.path.join('trunk', 'A'),
233 os.path.join('trunk', 'iota'))
234 svntest.main.run_svn(None, 'ci', '-m',
235 'Import greek tree into trunk.')
237 # Update from the repository to avoid a mix-rev working copy
238 svntest.main.run_svn(None, 'up')
240 # Create a branch - r3
241 svntest.main.run_svn(None, 'cp', 'trunk', branch_a)
242 svntest.main.run_svn(None, 'ci', '-m',
243 'Create branches/a from trunk.',
244 '--username', svntest.main.wc_author2)
246 # Some changes on the branch - r4
247 svntest.main.file_append_binary(os.path.join(branch_a, 'iota'),
248 "'A' has changed a bit.\n")
249 svntest.main.file_append_binary(os.path.join(branch_a, 'A', 'mu'),
250 "Don't forget to look at 'upsilon', too.")
251 svntest.main.file_write(os.path.join(branch_a, upsilon_path),
252 "This is the file 'upsilon'.\n", "wb")
253 svntest.main.run_svn(None, 'add',
254 os.path.join(branch_a, upsilon_path))
255 svntest.main.run_svn(None, 'ci', '-m',
256 "Add the file 'upsilon', and change some other files.")
258 # Create another branch - r5
259 svntest.main.run_svn(None, 'cp', 'trunk', branch_c)
260 svntest.main.run_svn(None, 'ci', '-m',
261 'Create branches/c from trunk.',
262 '--username', svntest.main.wc_author2)
264 # Do some mergeing - r6
265 os.chdir('trunk')
266 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_a) + '@HEAD')
267 svntest.main.run_svn(None, 'ci', '-m',
268 'Merged branches/a to trunk.',
269 '--username', svntest.main.wc_author2)
270 os.chdir('..')
272 # Add omicron to branches/a - r7
273 svntest.main.run_svn(None, 'mkdir', os.path.join(branch_a, 'blocked'))
274 svntest.main.file_write(os.path.join(branch_a, omicron_path),
275 "This is the file 'omicron'.\n")
276 svntest.main.run_svn(None, 'add',
277 os.path.join(branch_a, omicron_path))
278 svntest.main.run_svn(None, 'ci', '-m',
279 "Add omicron to branches/a. " +
280 "It will be blocked from merging in r8.")
282 # Block r7 from being merged to trunk - r8
283 os.chdir('trunk')
284 svntest.main.run_svn(None, 'merge', '--record-only', '-r6:7',
285 os.path.join('..', branch_a))
286 svntest.main.run_svn(None, 'ci', '-m',
287 "Block r7 from merging to trunk.",
288 '--username', svntest.main.wc_author2)
289 os.chdir('..')
291 # Wording change in mu - r9
292 svntest.main.file_write(os.path.join('trunk', 'A', 'mu'),
293 "This is the file 'mu'.\n" +
294 "Don't forget to look at 'upsilon', as well.", "wb")
295 svntest.main.run_svn(None, 'ci', '-m',
296 "Wording change in mu.")
298 # Update from the repository to avoid a mix-rev working copy
299 svntest.main.run_svn(None, 'up')
301 # Create another branch - r10
302 svntest.main.run_svn(None, 'cp', 'trunk', branch_b)
303 svntest.main.run_svn(None, 'ci', '-m',
304 "Create branches/b from trunk",
305 '--username', svntest.main.wc_author2)
307 # Add another file, make some changes on branches/a - r11
308 svntest.main.file_append_binary(os.path.join(branch_a, upsilon_path),
309 "There is also the file 'xi'.")
310 svntest.main.file_write(os.path.join(branch_a, 'A', 'xi'),
311 "This is the file 'xi'.\n", "wb")
312 svntest.main.run_svn(None, 'add',
313 os.path.join(branch_a, 'A', 'xi'))
314 svntest.main.file_write(os.path.join(branch_a, 'iota'),
315 "This is the file 'iota'.\n" +
316 "'A' has changed a bit, with 'upsilon', and 'xi'.",
317 "wb")
318 svntest.main.run_svn(None, 'ci', '-m',
319 "Added 'xi' to branches/a, made a few other changes.")
321 # Merge branches/a to branches/b - r12
322 os.chdir(branch_b)
323 svntest.main.run_svn(None, 'merge', os.path.join('..', 'a') + '@HEAD')
324 svntest.main.run_svn(None, 'ci', '-m',
325 "Merged branches/a to branches/b.",
326 '--username', svntest.main.wc_author2)
327 os.chdir(os.path.join('..', '..'))
329 # More wording changes - r13
330 svntest.main.file_append_binary(os.path.join(branch_b, 'A', 'D', 'gamma'),
331 "Watch out for the rays!")
332 svntest.main.run_svn(None, 'ci', '-m',
333 "Modify 'gamma' on branches/b.")
335 # More merging - r14
336 os.chdir('trunk')
337 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_b) + '@HEAD')
338 svntest.main.run_svn(None, 'ci', '-m',
339 "Merged branches/b to trunk.",
340 '--username', svntest.main.wc_author2)
341 os.chdir('..')
343 # Even more merging - r15
344 os.chdir(branch_c)
345 svntest.main.run_svn(None, 'merge',
346 os.path.join('..', '..', 'trunk') + '@HEAD')
347 svntest.main.run_svn(None, 'ci', '-m',
348 "Bring branches/c up to date with trunk.",
349 '--username', svntest.main.wc_author2)
350 os.chdir(os.path.join('..', '..'))
352 # Modify a file on branches/c - r16
353 svntest.main.file_append_binary(os.path.join(branch_c, 'A', 'mu'),
354 "\nThis is yet more content in 'mu'.")
355 svntest.main.run_svn(None, 'ci', '-m',
356 "Modify 'mu' on branches/c.")
358 # Merge branches/c to trunk, which produces a conflict - r17
359 os.chdir('trunk')
360 svntest.main.run_svn(None, 'merge', os.path.join('..', branch_c) + '@HEAD')
361 svntest.main.file_write(os.path.join('A', 'mu'),
362 "This is the file 'mu'.\n" +
363 "Don't forget to look at 'upsilon', as well.\n" +
364 "This is yet more content in 'mu'.",
365 "wb")
366 svntest.main.run_svn(None, 'resolved', os.path.join('A', 'mu'))
367 svntest.main.run_svn(None, 'ci', '-m',
368 "Merge branches/c to trunk, " +
369 "resolving a conflict in 'mu'.",
370 '--username', svntest.main.wc_author2)
371 os.chdir('..')
373 # Restore working directory
374 os.chdir(was_cwd)
377 # For errors seen while parsing log data.
378 class SVNLogParseError(Exception):
379 def __init__(self, args=None):
380 self.args = args
383 def parse_log_output(log_lines):
384 """Return a log chain derived from LOG_LINES.
385 A log chain is a list of hashes; each hash represents one log
386 message, in the order it appears in LOG_LINES (the first log
387 message in the data is also the first element of the list, and so
388 on).
390 Each hash contains the following keys/values:
392 'revision' ===> number
393 'author' ===> string
394 'date' ===> string
395 'msg' ===> string (the log message itself)
396 'lines' ===> number (so that it may be checked against rev)
397 If LOG_LINES contains changed-path information, then the hash
398 also contains
400 'paths' ===> list of tuples of the form (X, PATH), where X is the
401 first column of verbose output, and PATH is the affected path.
403 If LOG_LINES contains merge result information, then the hash also contains
405 'merges' ===> list of merging revisions that resulted in this log
406 being part of the list of messages.
409 # Here's some log output to look at while writing this function:
411 # ------------------------------------------------------------------------
412 # r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
414 # Log message for revision 5.
415 # ------------------------------------------------------------------------
416 # r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
418 # Log message for revision 4
419 # but with multiple lines
420 # to test the code.
421 # ------------------------------------------------------------------------
422 # r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
424 # Log message for revision 3.
425 # ------------------------------------------------------------------------
426 # r2 | kfogel | Tue 6 Nov 2001 17:18:16 | 3 lines
428 # Log message for revision 2
429 # but with multiple lines
430 # to test the code.
431 # ------------------------------------------------------------------------
432 # r1 | foo | Tue 6 Nov 2001 15:27:57 | 1 line
434 # Log message for revision 1.
435 # ------------------------------------------------------------------------
437 # Regular expression to match the header line of a log message, with
438 # these groups: (revision number), (author), (date), (num lines).
439 header_re = re.compile('^r([0-9]+) \| ' \
440 + '([^|]*) \| ([^|]*) \| ([0-9]+) lines?')
442 # The log chain to return.
443 chain = []
445 this_item = None
446 while 1:
447 try:
448 this_line = log_lines.pop(0)
449 except IndexError:
450 return chain
452 match = header_re.search(this_line)
453 if match and match.groups():
454 is_result = 0
455 this_item = {}
456 this_item['revision'] = int(match.group(1))
457 this_item['author'] = match.group(2)
458 this_item['date'] = match.group(3)
459 lines = int(match.group(4))
460 this_item['lines'] = lines
462 # Parse verbose output, starting with "Changed paths"
463 next_line = log_lines.pop(0)
464 if next_line.strip() == 'Changed paths:':
465 paths = []
466 path_line = log_lines.pop(0).strip()
468 # Stop on either a blank line or a "Merged via: ..." line
469 while path_line != '' and path_line[0:6] != 'Merged':
470 paths.append( (path_line[0], path_line[2:]) )
471 path_line = log_lines.pop(0).strip()
473 this_item['paths'] = paths
475 if path_line[0:6] == 'Merged':
476 is_result = 1
477 result_line = path_line
479 elif next_line[0:6] == 'Merged':
480 is_result = 1
481 result_line = next_line.strip()
483 # Parse output of "Merged via: ..." line
484 if is_result:
485 merges = []
486 prefix_len = len('Merged via: ')
487 for rev_str in result_line[prefix_len:].split(','):
488 merges.append(int(rev_str.strip()[1:]))
489 this_item['merges'] = merges
491 # Eat blank line
492 log_lines.pop(0)
494 # Accumulate the log message
495 msg = ''
496 for line in log_lines[0:lines]:
497 msg += line
498 del log_lines[0:lines]
499 elif this_line == msg_separator:
500 if this_item:
501 this_item['msg'] = msg
502 chain.append(this_item)
503 else: # if didn't see separator now, then something's wrong
504 print this_line
505 raise SVNLogParseError, "trailing garbage after log message"
507 return chain
510 class SVNUnexpectedLogs(svntest.Failure):
511 "Exception raised if a set of log messages doesn't meet expectations."
513 def __init__(self, msg, chain, field_selector = 'revision'):
514 """Stores the log chain for later use. FIELD_SELECTOR indicates
515 which individual field to display when turning the exception into
516 text."""
517 svntest.Failure.__init__(self, msg)
518 self.chain = chain
519 self.field_selector = field_selector
521 def __str__(self):
522 msg = svntest.Failure.__str__(self)
523 if self.chain:
524 chain_data = list(self.chain)
525 for i in range(0, len(self.chain)):
526 chain_data[i] = self.chain[i][self.field_selector]
527 msg = msg + ': Actual %s list was %s' % (self.field_selector, chain_data)
528 return msg
531 def check_log_chain(chain, revlist, path_counts=[]):
532 """Verify that log chain CHAIN contains the right log messages for
533 revisions START to END (see documentation for parse_log_output() for
534 more about log chains).
536 Do nothing if the log chain's messages run from revision START to END
537 and each log message contains a line of the form
539 'Log message for revision N'
541 where N is the revision number of that commit. Verify that
542 author and date are present and look sane, but don't check them too
543 carefully.
544 Also verify that even numbered commit messages have three lines.
546 If the length of PATH_COUNTS is greater than zero, make sure that each
547 log has that number of paths.
549 Raise an error if anything looks wrong.
552 nbr_expected = len(revlist)
553 if len(chain) != nbr_expected:
554 raise SVNUnexpectedLogs('Number of elements in log chain and revision ' +
555 'list %s not equal' % revlist, chain)
556 if path_counts and len(path_counts) != nbr_expected:
557 raise SVNUnexpectedLogs('Number of elements in log chain and path ' +
558 'counts %s not equal' % path_counts, chain)
559 missing_revs = []
560 for i in range(0, nbr_expected):
561 expect_rev = revlist[i]
562 log_item = chain[i]
563 saw_rev = log_item['revision']
564 date = log_item['date']
565 author = log_item['author']
566 msg = log_item['msg']
567 # The most important check is that the revision is right:
568 if expect_rev != saw_rev:
569 missing_revs.append(expect_rev)
570 continue
571 # Check that date looks at least vaguely right:
572 date_re = re.compile('[0-9]+')
573 if not date_re.search(date):
574 raise SVNUnexpectedLogs('Malformed date', chain, 'date')
575 # Authors are a little harder, since they might not exist over ra-dav.
576 # Well, it's not much of a check, but we'll do what we can.
577 author_re = re.compile('[a-zA-Z]+')
578 if (not (author_re.search(author)
579 or author == ''
580 or author == '(no author)')):
581 raise SVNUnexpectedLogs('Malformed author', chain, 'author')
583 # Verify the expectation that even-numbered revisions in the Greek
584 # tree tweaked by the log tests have 3-line log messages.
585 if (saw_rev % 2 == 0 and log_item['lines'] != 3):
586 raise SVNUnexpectedLogs('Malformed log line counts', chain, 'lines')
588 # Check that the log message looks right:
589 pattern = 'Log message for revision ' + `saw_rev`
590 msg_re = re.compile(pattern)
591 if not msg_re.search(msg):
592 raise SVNUnexpectedLogs("Malformed log message, expected '%s'" % msg,
593 chain)
595 # If path_counts, check the number of changed paths
596 if path_counts:
597 if (not 'paths' in log_item) or (not log_item['paths']):
598 raise SVNUnexpectedLogs("No changed path information", chain)
599 if path_counts[i] != len(log_item['paths']):
600 raise SVNUnexpectedLogs("Changed paths counts not equal for " +
601 "revision %d" % (i + 1), chain)
603 nbr_missing_revs = len(missing_revs)
604 if nbr_missing_revs > 0:
605 raise SVNUnexpectedLogs('Unable to find expected revision(s) %s' %
606 missing_revs, chain)
610 ######################################################################
611 # Tests
614 #----------------------------------------------------------------------
615 def plain_log(sbox):
616 "'svn log', no args, top of wc"
618 guarantee_repos_and_wc(sbox)
620 os.chdir(sbox.wc_dir)
622 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
623 'log')
625 log_chain = parse_log_output(output)
626 check_log_chain(log_chain, range(max_revision, 1 - 1, -1))
629 #----------------------------------------------------------------------
630 def log_with_empty_repos(sbox):
631 "'svn log' on an empty repository"
633 # Create virgin repos
634 svntest.main.safe_rmtree(sbox.repo_dir, 1)
635 svntest.main.create_repos(sbox.repo_dir)
637 svntest.actions.run_and_verify_svn(None, None, [],
638 'log',
639 sbox.repo_url)
641 #----------------------------------------------------------------------
642 def log_where_nothing_changed(sbox):
643 "'svn log -rN some_dir_unchanged_in_N'"
644 sbox.build()
646 # Fix bug whereby running 'svn log -rN SOMEPATH' would result in an
647 # xml protocol error if there were no changes in revision N
648 # underneath SOMEPATH. This problem was introduced in revision
649 # 3811, which didn't cover the case where svn_repos_get_logs might
650 # invoke log_receiver zero times. Since the receiver never ran, the
651 # lrb->needs_header flag never got cleared. Control would proceed
652 # without error to the end of dav_svn__log_report(), which would
653 # send a closing tag even though no opening tag had ever been sent.
655 rho_path = os.path.join(sbox.wc_dir, 'A', 'D', 'G', 'rho')
656 svntest.main.file_append(rho_path, "some new material in rho")
657 svntest.actions.run_and_verify_svn(None, None, [],
658 'ci', '-m',
659 'log msg', rho_path)
661 # Now run 'svn log -r2' on a directory unaffected by revision 2.
662 H_path = os.path.join(sbox.wc_dir, 'A', 'D', 'H')
663 svntest.actions.run_and_verify_svn(None, None, [],
664 'log', '-r', '2', H_path)
667 #----------------------------------------------------------------------
668 def log_to_revision_zero(sbox):
669 "'svn log -v -r 1:0 wc_root'"
670 sbox.build(read_only = True)
672 # This used to segfault the server.
674 svntest.actions.run_and_verify_svn(None, None, [],
675 'log', '-v',
676 '-r', '1:0', sbox.wc_dir)
678 #----------------------------------------------------------------------
679 def log_with_path_args(sbox):
680 "'svn log', with args, top of wc"
682 guarantee_repos_and_wc(sbox)
684 os.chdir(sbox.wc_dir)
686 exit_code, output, err = svntest.actions.run_and_verify_svn(
687 None, None, [],
688 'log', sbox.repo_url, 'A/D/G', 'A/D/H')
690 log_chain = parse_log_output(output)
691 check_log_chain(log_chain, [8, 6, 5, 3, 1])
693 #----------------------------------------------------------------------
694 def dynamic_revision(sbox):
695 "'svn log -r COMMITTED' of dynamic/local WC rev"
697 guarantee_repos_and_wc(sbox)
698 os.chdir(sbox.wc_dir)
700 revprops = [{'svn:author': 'jrandom',
701 'svn:date': '', 'svn:log': 'Log message for revision 9'}]
702 for rev in ('HEAD', 'BASE', 'COMMITTED'):
703 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
704 args=['-r', rev])
705 revprops[0]['svn:log'] = ('Log message for revision 8\n'
706 ' but with multiple lines\n'
707 ' to test the code')
708 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
709 args=['-r', 'PREV'])
711 #----------------------------------------------------------------------
712 def log_wc_with_peg_revision(sbox):
713 "'svn log wc_target@N'"
714 guarantee_repos_and_wc(sbox)
715 my_path = os.path.join(sbox.wc_dir, "A", "B", "E", "beta") + "@8"
716 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
717 'log', my_path)
718 check_log_chain(parse_log_output(output), [1])
720 #----------------------------------------------------------------------
721 def url_missing_in_head(sbox):
722 "'svn log target@N' when target removed from HEAD"
724 guarantee_repos_and_wc(sbox)
726 my_url = sbox.repo_url + "/A/B/E/alpha" + "@8"
728 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
729 'log', my_url)
730 check_log_chain(parse_log_output(output), [3, 1])
732 #----------------------------------------------------------------------
733 def log_through_copyfrom_history(sbox):
734 "'svn log TGT' with copyfrom history"
735 sbox.build()
736 wc_dir = sbox.wc_dir
737 msg_file=os.path.join(sbox.repo_dir, 'log-msg')
738 msg_file=os.path.abspath(msg_file)
740 mu_path = os.path.join(wc_dir, 'A', 'mu')
741 mu2_path = os.path.join(wc_dir, 'A', 'mu2')
742 mu_URL = sbox.repo_url + '/A/mu'
743 mu2_URL = sbox.repo_url + '/A/mu2'
745 msg2=""" Log message for revision 2
746 but with multiple lines
747 to test the code"""
749 msg4=""" Log message for revision 4
750 but with multiple lines
751 to test the code"""
753 msg6=""" Log message for revision 6
754 but with multiple lines
755 to test the code"""
757 svntest.main.file_write(msg_file, msg2)
758 svntest.main.file_append(mu_path, "2")
759 svntest.actions.run_and_verify_svn(None, None, [],
760 'ci', wc_dir,
761 '-F', msg_file)
762 svntest.main.file_append(mu2_path, "this is mu2")
763 svntest.actions.run_and_verify_svn(None, None, [], 'add', mu2_path)
764 svntest.actions.run_and_verify_svn(None, None, [],
765 'ci', wc_dir,
766 '-m', "Log message for revision 3")
767 svntest.actions.run_and_verify_svn(None, None, [], 'rm', mu2_path)
768 svntest.main.file_write(msg_file, msg4)
769 svntest.actions.run_and_verify_svn(None, None, [],
770 'ci', wc_dir,
771 '-F', msg_file)
772 svntest.main.file_append(mu_path, "5")
773 svntest.actions.run_and_verify_svn(None, None, [],
774 'ci', wc_dir,
775 '-m', "Log message for revision 5")
777 svntest.main.file_write(msg_file, msg6)
778 svntest.actions.run_and_verify_svn(None, None, [],
779 'cp', '-r', '5', mu_URL, mu2_URL,
780 '-F', msg_file)
781 svntest.actions.run_and_verify_svn(None, None, [],
782 'up', wc_dir)
784 # The full log for mu2 is relatively unsurprising
785 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
786 'log', mu2_path)
787 log_chain = parse_log_output(output)
788 check_log_chain(log_chain, [6, 5, 2, 1])
790 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
791 'log', mu2_URL)
792 log_chain = parse_log_output(output)
793 check_log_chain(log_chain, [6, 5, 2, 1])
795 # First "oddity", the full log for mu2 doesn't include r3, but the -r3
796 # log works!
797 peg_mu2_path = mu2_path + "@3"
798 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
799 'log', '-r', '3',
800 peg_mu2_path)
801 log_chain = parse_log_output(output)
802 check_log_chain(log_chain, [3])
804 peg_mu2_URL = mu2_URL + "@3"
805 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
806 'log', '-r', '3',
807 peg_mu2_URL)
808 log_chain = parse_log_output(output)
809 check_log_chain(log_chain, [3])
810 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
811 'log', '-r', '2',
812 mu2_path)
813 log_chain = parse_log_output(output)
814 check_log_chain(log_chain, [2])
816 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
817 'log', '-r', '2',
818 mu2_URL)
819 log_chain = parse_log_output(output)
820 check_log_chain(log_chain, [2])
822 #----------------------------------------------------------------------
823 def escape_control_chars(sbox):
824 "mod_dav_svn must escape invalid XML control chars"
826 dump_str = """SVN-fs-dump-format-version: 2
828 UUID: ffcae364-69ee-0310-a980-ca5f10462af2
830 Revision-number: 0
831 Prop-content-length: 56
832 Content-length: 56
835 svn:date
836 V 27
837 2005-01-24T10:09:21.759592Z
838 PROPS-END
840 Revision-number: 1
841 Prop-content-length: 128
842 Content-length: 128
845 svn:log
846 V 100
847 This msg contains a Ctrl-T (\x14) and a Ctrl-I (\t).
848 The former might be escaped, but the latter never.
850 K 10
851 svn:author
853 jrandom
855 svn:date
856 V 27
857 2005-01-24T10:09:22.012524Z
858 PROPS-END
861 # load dumpfile with control character into repos to get
862 # a log with control char content
863 svntest.actions.load_repo(sbox, dump_str=dump_str)
865 URL = sbox.repo_url
867 # run log
868 exit_code, output, errput = svntest.actions.run_and_verify_svn(
869 None, None, [], 'log', URL)
871 # Verify the output contains either the expected fuzzy escape
872 # sequence, or the literal control char.
873 match_unescaped_ctrl_re = "This msg contains a Ctrl-T \(.\) " \
874 "and a Ctrl-I \(\t\)\."
875 match_escaped_ctrl_re = "^This msg contains a Ctrl-T \(\?\\\\020\) " \
876 "and a Ctrl-I \(\t\)\."
877 matched = None
878 for line in output:
879 if re.match(match_unescaped_ctrl_re, line) \
880 or re.match(match_escaped_ctrl_re, line):
881 matched = 1
883 if not matched:
884 raise svntest.Failure("log message not transmitted properly:" +
885 str(output) + "\n" + "error: " + str(errput))
887 #----------------------------------------------------------------------
888 def log_xml_empty_date(sbox):
889 "svn log --xml must not print empty date elements"
890 sbox.build()
892 # Create the revprop-change hook for this test
893 svntest.actions.enable_revprop_changes(sbox.repo_dir)
895 date_re = re.compile('<date');
897 # Ensure that we get a date before we delete the property.
898 exit_code, output, errput = svntest.actions.run_and_verify_svn(
899 None, None, [], 'log', '--xml', '-r1', sbox.wc_dir)
901 matched = 0
902 for line in output:
903 if date_re.search(line):
904 matched = 1
905 if not matched:
906 raise svntest.Failure("log contains no date element")
908 # Set the svn:date revprop to the empty string on revision 1.
909 svntest.actions.run_and_verify_svn(None, None, [],
910 'pdel', '--revprop', '-r1', 'svn:date',
911 sbox.wc_dir)
913 exit_code, output, errput = svntest.actions.run_and_verify_svn(
914 None, None, [], 'log', '--xml', '-r1', sbox.wc_dir)
916 for line in output:
917 if date_re.search(line):
918 raise svntest.Failure("log contains date element when svn:date is empty")
920 #----------------------------------------------------------------------
921 def log_limit(sbox):
922 "svn log --limit"
923 guarantee_repos_and_wc(sbox)
925 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
926 'log',
927 '--limit', '2',
928 sbox.repo_url)
929 log_chain = parse_log_output(out)
930 check_log_chain(log_chain, [9, 8])
932 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
933 'log',
934 '--limit', '2',
935 sbox.repo_url,
936 'A/B')
937 log_chain = parse_log_output(out)
938 check_log_chain(log_chain, [9, 6])
940 exit_code, out, err = svntest.actions.run_and_verify_svn(
941 None, None, [],
942 'log', '--limit', '2', '--revision', '2:HEAD', sbox.repo_url, 'A/B')
944 log_chain = parse_log_output(out)
945 check_log_chain(log_chain, [3, 6])
947 # Use -l instead of --limit to test both option forms.
948 exit_code, out, err = svntest.actions.run_and_verify_svn(
949 None, None, [],
950 'log', '-l', '2', '--revision', '1', sbox.repo_url, 'A/B')
952 log_chain = parse_log_output(out)
953 check_log_chain(log_chain, [1])
955 must_be_positive = ".*Argument to --limit must be positive.*"
957 # error expected when limit <= 0
958 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
959 'log', '--limit', '0', '--revision', '1',
960 sbox.repo_url, 'A/B')
962 svntest.actions.run_and_verify_svn(None, None, must_be_positive,
963 'log', '--limit', '-1', '--revision', '1',
964 sbox.repo_url, 'A/B')
966 def log_base_peg(sbox):
967 "run log on an @BASE target"
968 guarantee_repos_and_wc(sbox)
970 target = os.path.join(sbox.wc_dir, 'A', 'B', 'E', 'beta') + '@BASE'
972 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
973 'log', target)
975 log_chain = parse_log_output(out)
976 check_log_chain(log_chain, [9, 1])
978 svntest.actions.run_and_verify_svn(None, None, [], 'update', '-r', '1',
979 sbox.wc_dir)
981 exit_code, out, err = svntest.actions.run_and_verify_svn(None, None, [],
982 'log', target)
984 log_chain = parse_log_output(out)
985 check_log_chain(log_chain, [1])
988 def log_verbose(sbox):
989 "run log with verbose output"
990 guarantee_repos_and_wc(sbox)
992 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
993 'log', '-v',
994 sbox.wc_dir)
996 log_chain = parse_log_output(output)
997 path_counts = [2, 2, 1, 2, 2, 2, 4, 1, 20]
998 check_log_chain(log_chain, range(max_revision, 1 - 1, -1), path_counts)
1001 def log_parser(sbox):
1002 "meta-test for the log parser"
1004 logs = ['''------------------------------------------------------------------------
1005 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1006 Changed paths:
1007 M /trunk/death-ray.c
1008 M /trunk/frobnicator/frapnalyzer.c
1010 Merge r12 and r14 from branch to trunk.
1011 ------------------------------------------------------------------------
1012 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1013 Changed paths:
1014 M /trunk/death-ray.c
1015 Merged via: r24
1017 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1018 ------------------------------------------------------------------------
1019 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1020 Changed paths:
1021 M /trunk/frobnicator/frapnalyzer.c
1022 M /trunk/death-ray.c
1023 Merged via: r24
1025 Fix frapnalyzer bug in frobnicator.
1026 ------------------------------------------------------------------------''',
1027 '''------------------------------------------------------------------------
1028 r24 | chuck | 2007-04-30 10:18:01 -0500 (Mon, 16 Apr 2007) | 1 line
1030 Merge r12 and r14 from branch to trunk.
1031 ------------------------------------------------------------------------
1032 r14 | bob | 2007-04-16 18:50:29 -0500 (Mon, 16 Apr 2007) | 1 line
1033 Merged via: r24
1035 Remove inadvertent changes to Death-Ray-o-Matic introduced in r12.
1036 ------------------------------------------------------------------------
1037 r12 | alice | 2007-04-16 19:02:48 -0500 (Mon, 16 Apr 2007) | 1 line
1038 Merged via: r24
1040 Fix frapnalyzer bug in frobnicator.
1041 ------------------------------------------------------------------------
1042 r10 | alice | 2007-04-16 19:02:28 -0500 (Mon, 16 Apr 2007) | 1 line
1043 Merged via: r12, r24
1045 Fix frapnalyzer documentation.
1046 ------------------------------------------------------------------------
1047 r9 | bob | 2007-04-16 19:01:48 -0500 (Mon, 16 Apr 2007) | 1 line
1048 Merged via: r12, r24
1050 Whitespace fixes. No functional change.
1051 ------------------------------------------------------------------------''',
1052 '''------------------------------------------------------------------------
1053 r5 | kfogel | Tue 6 Nov 2001 17:18:19 | 1 line
1055 Log message for revision 5.
1056 ------------------------------------------------------------------------
1057 r4 | kfogel | Tue 6 Nov 2001 17:18:18 | 3 lines
1059 Log message for revision 4
1060 but with multiple lines
1061 to test the code.
1062 ------------------------------------------------------------------------
1063 r3 | kfogel | Tue 6 Nov 2001 17:18:17 | 1 line
1065 Log message for revision 3.
1066 ------------------------------------------------------------------------''',
1067 ] # end of log list
1069 for log in logs:
1070 log_chain = parse_log_output([line+"\n" for line in log.split("\n")])
1073 def check_merge_results(log_chain, expected_merges):
1074 '''Check LOG_CHAIN to see if the log information contains 'Merged via'
1075 information indicated by EXPECTED_MERGES. EXPECTED_MERGES is a dictionary
1076 whose key is the merged revision, and whose value is the merging revision.'''
1078 # Check to see if the number and values of the revisions is correct
1079 for log in log_chain:
1080 if log['revision'] not in expected_merges:
1081 raise SVNUnexpectedLogs("Found unexpected revision %d" %
1082 log['revision'], log_chain)
1084 # Check to see that each rev in expected_merges contains the correct data
1085 for rev in expected_merges:
1086 try:
1087 log = [x for x in log_chain if x['revision'] == rev][0]
1088 if 'merges' in log.keys():
1089 actual = log['merges']
1090 else:
1091 actual = []
1092 expected = expected_merges[rev]
1094 if actual != expected:
1095 raise SVNUnexpectedLogs(("Merging revisions in rev %d not correct; " +
1096 "expecting %s, found %s") %
1097 (rev, str(expected), str(actual)), log_chain)
1098 except IndexError:
1099 raise SVNUnexpectedLogs("Merged revision '%d' missing" % rev, log_chain)
1102 def merge_sensitive_log_single_revision(sbox):
1103 "test sensitive log on a single revision"
1105 merge_history_repos(sbox)
1107 # Paths we care about
1108 wc_dir = sbox.wc_dir
1109 TRUNK_path = os.path.join(wc_dir, "trunk")
1110 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1112 # Run the merge sensitive log, and compare results
1113 saved_cwd = os.getcwd()
1115 os.chdir(TRUNK_path)
1116 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1117 'log', '-g',
1118 '-r14')
1121 log_chain = parse_log_output(output)
1122 expected_merges = {
1123 14: [], 13 : [14], 12 : [14], 11 : [14, 12],
1125 check_merge_results(log_chain, expected_merges)
1127 os.chdir(saved_cwd)
1129 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1130 'log', '-g',
1131 '-r12',
1132 BRANCH_B_path)
1133 log_chain = parse_log_output(output)
1134 expected_merges = {
1135 12: [], 11 : [12],
1137 check_merge_results(log_chain, expected_merges)
1140 def merge_sensitive_log_branching_revision(sbox):
1141 "test 'svn log -g' on a branching revision"
1143 merge_history_repos(sbox)
1145 # Paths we care about
1146 wc_dir = sbox.wc_dir
1147 BRANCH_B_path = os.path.join(wc_dir, "branches", "b")
1149 # Run log on a copying revision
1150 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1151 'log', '-g',
1152 '-r10',
1153 BRANCH_B_path)
1155 # Parse and check output. There should be no extra revisions.
1156 log_chain = parse_log_output(output)
1157 expected_merges = {
1158 10: [],
1160 check_merge_results(log_chain, expected_merges)
1163 def merge_sensitive_log_non_branching_revision(sbox):
1164 "test 'svn log -g' on a non-branching revision"
1166 merge_history_repos(sbox)
1168 TRUNK_path = os.path.join(sbox.wc_dir, "trunk")
1170 # Run log on a non-copying revision that adds mergeinfo
1171 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1172 'log', '-g',
1173 '-r6',
1174 TRUNK_path)
1176 # Parse and check output. There should be one extra revision.
1177 log_chain = parse_log_output(output)
1178 expected_merges = {
1179 6: [], 4 : [6], 3: [6],
1181 check_merge_results(log_chain, expected_merges)
1184 def merge_sensitive_log_added_path(sbox):
1185 "test 'svn log -g' a path added before merge"
1187 merge_history_repos(sbox)
1189 XI_path = os.path.join(sbox.wc_dir, "trunk", "A", "xi")
1191 # Run log on a non-copying revision that adds mergeinfo
1192 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1193 'log', '-g',
1194 XI_path)
1196 # Parse and check output. There should be one extra revision.
1197 log_chain = parse_log_output(output)
1198 expected_merges = {
1199 14: [], 12 : [], 11 : [],
1201 check_merge_results(log_chain, expected_merges)
1203 revprops = [{'svn:author': 'jconstant', 'svn:date': '',
1204 'svn:log': 'Merged branches/b to trunk.'},
1205 {'svn:author': 'jconstant', 'svn:date': '',
1206 'svn:log': 'Merged branches/a to branches/b.'},
1207 {'svn:author': 'jrandom', 'svn:date': '',
1208 'svn:log': "Added 'xi' to branches/a,"
1209 ' made a few other changes.'}]
1210 svntest.actions.run_and_verify_log_xml(expected_revprops=revprops,
1211 args=['-g', XI_path])
1214 def log_single_change(sbox):
1215 "test log -c for a single change"
1217 guarantee_repos_and_wc(sbox)
1218 repo_url = sbox.repo_url
1220 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1221 'log', '-c',
1222 4, repo_url)
1223 log_chain = parse_log_output(output)
1224 check_log_chain(log_chain, [4])
1226 def log_changes_range(sbox):
1227 "test log -c on range of changes"
1229 guarantee_repos_and_wc(sbox)
1230 repo_url = sbox.repo_url
1232 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1233 'log', '-c',
1234 '2:5', repo_url)
1236 log_chain = parse_log_output(output)
1237 check_log_chain(log_chain, [2, 3, 4, 5])
1239 def log_changes_list(sbox):
1240 "test log -c on comma-separated list of changes"
1242 guarantee_repos_and_wc(sbox)
1243 repo_url = sbox.repo_url
1245 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [],
1246 'log', '-c',
1247 '2,5,7',
1248 repo_url)
1250 log_chain = parse_log_output(output)
1251 check_log_chain(log_chain, [2, 5, 7])
1253 #----------------------------------------------------------------------
1254 def only_one_wc_path(sbox):
1255 "svn log of two wc paths is disallowed"
1257 sbox.build(read_only = True)
1258 os.chdir(sbox.wc_dir)
1260 svntest.actions.run_and_verify_log_xml(
1261 expected_stderr=('.*When specifying working copy paths,'
1262 ' only one target may be given'),
1263 args=['A/mu', 'iota'])
1265 #----------------------------------------------------------------------
1266 def retrieve_revprops(sbox):
1267 "test revprop retrieval"
1269 sbox.build()
1270 svntest.actions.enable_revprop_changes(sbox.repo_dir)
1272 # test properties
1273 author = 'jrandom'
1274 msg1 = 'Log message for revision 1.'
1275 msg2 = 'Log message for revision 2.'
1276 custom_name = 'retrieve_revprops'
1277 custom_value = 'foo bar'
1279 # Commit a change.
1280 wc_dir = sbox.wc_dir
1281 cwd = os.getcwd()
1282 os.chdir(wc_dir)
1283 svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")
1284 os.chdir(cwd)
1285 omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
1286 expected_output = svntest.wc.State(wc_dir, {
1287 'A/D/H/omega' : Item(verb='Sending'),
1289 expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1290 expected_status.tweak('A/D/H/omega', wc_rev=2, status=' ')
1291 svntest.actions.run_and_verify_commit(wc_dir,
1292 expected_output,
1293 expected_status,
1294 None,
1295 '-m', msg2,
1296 omega_path)
1298 os.chdir(wc_dir)
1300 # Set custom property on r1 and r2.
1301 svntest.actions.run_and_verify_svn(
1302 None, None, [], # message, expected_stdout, expected_stderr
1303 'ps', '--revprop', '-r1', custom_name, custom_value, sbox.repo_url)
1304 svntest.actions.run_and_verify_svn(
1305 None, None, [], # message, expected_stdout, expected_stderr
1306 'ps', '--revprop', '-r2', custom_name, custom_value, sbox.repo_url)
1308 # Can't set revprops with log.
1309 svntest.actions.run_and_verify_log_xml(
1310 expected_stderr=(".*cannot assign with 'with-revprop' option"
1311 " \(drop the '='\)"),
1312 args=['--with-revprop=foo=bar'])
1314 # basic test without revprop options
1315 svntest.actions.run_and_verify_log_xml(
1316 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1}],
1317 args=['-r1'])
1319 # basic test without revprop options, with multiple revisions
1320 svntest.actions.run_and_verify_log_xml(
1321 expected_revprops=[{'svn:author': author, 'svn:date': '', 'svn:log': msg1},
1322 {'svn:author': author, 'svn:date': '', 'svn:log': msg2}])
1324 # -q with no revprop options must suppress svn:log only.
1325 svntest.actions.run_and_verify_log_xml(
1326 expected_revprops=[{'svn:author': author, 'svn:date': ''}],
1327 args=['-q', '-r1'])
1329 # Request svn:date, svn:log, and a non-existent property.
1330 svntest.actions.run_and_verify_log_xml(
1331 expected_revprops=[{'svn:date': '', 'svn:log': msg1}],
1332 args=['-r1', '--with-revprop=svn:date', '--with-revprop', 'svn:log',
1333 '--with-revprop', 'nosuchprop'])
1335 # Get all revprops.
1336 svntest.actions.run_and_verify_log_xml(
1337 expected_revprops=[{'svn:author': author, 'svn:date': '',
1338 'svn:log': msg1, custom_name: custom_value}],
1339 args=['-r1', '--with-all-revprops'])
1341 # Get all revprops, with multiple revisions.
1342 svntest.actions.run_and_verify_log_xml(
1343 expected_revprops=[{'svn:author': author, 'svn:date': '',
1344 'svn:log': msg1, custom_name: custom_value},
1345 {'svn:author': author, 'svn:date': '',
1346 'svn:log': msg2, custom_name: custom_value}],
1347 args=['--with-all-revprops'])
1349 # Get only the custom property.
1350 svntest.actions.run_and_verify_log_xml(
1351 expected_revprops=[{custom_name: custom_value}],
1352 args=['-r1', '--with-revprop', custom_name])
1355 def log_xml_with_bad_data(sbox):
1356 "log --xml escapes non-utf8 data (issue #2866)"
1357 svntest.actions.load_repo(sbox, os.path.join(os.path.dirname(sys.argv[0]),
1358 'log_tests_data',
1359 'xml-invalid-chars.dump'))
1360 r0_props = {
1361 'svn:date' : '',
1362 'svn:log' : 'After the colon are a space, 3 bad chars, '
1363 + '2 good chars, and a period: '
1364 + '?\\021?\\022?\\017\t\n.' }
1365 svntest.actions.run_and_verify_log_xml(
1366 expected_revprops=(r0_props,), args=[sbox.repo_url])
1368 def merge_sensitive_log_target_with_bogus_mergeinfo(sbox):
1369 "'svn log -g target_with_bogus_mergeinfo'"
1370 #Refer issue 3172 for details.
1371 #Create greek tree
1372 #svn ps 'svn:mergeinfo' '/A/B:0' A/D
1373 #svn ci -m 'setting bogus mergeinfo'
1374 #svn log -g -r2
1375 sbox.build()
1376 wc_path = sbox.wc_dir
1377 D_path = os.path.join(wc_path, 'A', 'D')
1378 svntest.main.run_svn(None, 'ps', SVN_PROP_MERGEINFO, '/A/B:0', D_path)
1379 #commit at r2
1380 svntest.main.run_svn(None, 'ci', '-m', 'setting bogus mergeinfo', D_path)
1381 exit_code, output, err = svntest.actions.run_and_verify_svn(None, None,
1382 [], 'log',
1383 '-g', D_path)
1384 if len(err):
1385 raise svntest.Failure("svn log -g target_with_bogus_mergeinfo fails")
1387 ########################################################################
1388 # Run the tests
1391 # list all tests here, starting with None:
1392 test_list = [ None,
1393 plain_log,
1394 log_with_empty_repos,
1395 log_where_nothing_changed,
1396 log_to_revision_zero,
1397 dynamic_revision,
1398 log_with_path_args,
1399 log_wc_with_peg_revision,
1400 url_missing_in_head,
1401 log_through_copyfrom_history,
1402 escape_control_chars,
1403 log_xml_empty_date,
1404 log_limit,
1405 log_base_peg,
1406 log_verbose,
1407 log_parser,
1408 SkipUnless(merge_sensitive_log_single_revision,
1409 server_has_mergeinfo),
1410 SkipUnless(merge_sensitive_log_branching_revision,
1411 server_has_mergeinfo),
1412 SkipUnless(merge_sensitive_log_non_branching_revision,
1413 server_has_mergeinfo),
1414 SkipUnless(merge_sensitive_log_added_path,
1415 server_has_mergeinfo),
1416 log_single_change,
1417 XFail(log_changes_range),
1418 XFail(log_changes_list),
1419 only_one_wc_path,
1420 retrieve_revprops,
1421 log_xml_with_bad_data,
1422 SkipUnless(merge_sensitive_log_target_with_bogus_mergeinfo,
1423 server_has_mergeinfo),
1426 if __name__ == '__main__':
1427 svntest.main.run_tests(test_list)
1428 # NOTREACHED
1431 ### End of file.