1#!/usr/local/bin/python 2 3# ==================================================================== 4# Licensed to the Apache Software Foundation (ASF) under one 5# or more contributor license agreements. See the NOTICE file 6# distributed with this work for additional information 7# regarding copyright ownership. The ASF licenses this file 8# to you under the Apache License, Version 2.0 (the 9# "License"); you may not use this file except in compliance 10# with the License. You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, 15# software distributed under the License is distributed on an 16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17# KIND, either express or implied. See the License for the 18# specific language governing permissions and limitations 19# under the License. 20# ==================================================================== 21 22# Run this without arguments to run unit tests. 23# Run with a path to a davautocheck ops log to test that it can parse that. 24 25import os 26import re 27import sys 28import tempfile 29try: 30 # Python >=3.0 31 from urllib.parse import quote as urllib_parse_quote 32except ImportError: 33 # Python <3.0 34 from urllib import quote as urllib_parse_quote 35import unittest 36 37import svn.core 38 39import svn_server_log_parse 40 41class TestCase(unittest.TestCase): 42 def setUp(self): 43 # Define a class to stuff everything passed to any handle_ 44 # method into self.result. 45 class cls(svn_server_log_parse.Parser): 46 def __getattr__(cls_self, attr): 47 if attr.startswith('handle_'): 48 return lambda *a: setattr(self, 'result', a) 49 raise AttributeError 50 self.parse = cls().parse 51 52 def test_unknown(self): 53 line = 'unknown log line' 54 self.parse(line) 55 self.assertEqual(self.result, (line,)) 56 57 def test_open(self): 58 self.assertRaises(svn_server_log_parse.Error, self.parse, 'open') 59 self.assertRaises(svn_server_log_parse.Error, self.parse, 'open 2 cap / SVN/1.60. fooclient') 60 self.assertRaises(svn_server_log_parse.Error, self.parse, 'open a cap=() / SVN/1.60. fooclient') 61 self.assertEqual(self.parse('open 2 cap=() / SVN fooclient'), '') 62 self.assertEqual(self.result, (2, [], '/', 'SVN', 'fooclient')) 63 # TODO: Teach it about the capabilities, rather than allowing 64 # any words at all. 65 self.assertEqual(self.parse('open 2 cap=(foo) / SVN foo%20client'), '') 66 self.assertEqual(self.result, (2, ['foo'], '/', 'SVN', 'foo client')) 67 68 def test_reparent(self): 69 self.assertRaises(svn_server_log_parse.Error, self.parse, 'reparent') 70 self.assertEqual(self.parse('reparent /'), '') 71 self.assertEqual(self.result, ('/',)) 72 73 def test_get_latest_rev(self): 74 self.assertEqual(self.parse('get-latest-rev'), '') 75 self.assertEqual(self.result, ()) 76 self.assertEqual(self.parse('get-latest-rev r3'), 'r3') 77 self.assertEqual(self.result, ()) 78 79 def test_get_dated_rev(self): 80 self.assertRaises(svn_server_log_parse.Error, self.parse, 81 'get-dated-rev') 82 self.assertEqual(self.parse('get-dated-rev 2008-04-15T20:41:24.000000Z'), '') 83 self.assertEqual(self.result, ('2008-04-15T20:41:24.000000Z',)) 84 85 def test_commit(self): 86 self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit') 87 self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit 3') 88 self.assertEqual(self.parse('commit r3'), '') 89 self.assertEqual(self.result, (3,)) 90 self.assertEqual(self.parse('commit r3 leftover'), ' leftover') 91 self.assertEqual(self.result, (3,)) 92 93 def test_get_dir(self): 94 self.get_dir_or_file('get-dir') 95 96 def test_get_file(self): 97 self.get_dir_or_file('get-file') 98 99 def get_dir_or_file(self, c): 100 self.assertRaises(svn_server_log_parse.Error, self.parse, c) 101 self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo') 102 self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo 3') 103 self.assertEqual(self.parse(c + ' /a/b/c r3 ...'), ' ...') 104 self.assertEqual(self.result, ('/a/b/c', 3, False, False)) 105 self.assertEqual(self.parse(c + ' / r3'), '') 106 self.assertEqual(self.result, ('/', 3, False, False)) 107 # path must be absolute 108 self.assertRaises(svn_server_log_parse.Error, 109 self.parse, c + ' a/b/c r3') 110 self.assertEqual(self.parse(c + ' /k r27 text'), '') 111 self.assertEqual(self.result, ('/k', 27, True, False)) 112 self.assertEqual(self.parse(c + ' /k r27 props'), '') 113 self.assertEqual(self.result, ('/k', 27, False, True)) 114 self.assertEqual(self.parse(c + ' /k r27 text props'), '') 115 self.assertEqual(self.result, ('/k', 27, True, True)) 116 # out of order not accepted 117 self.assertEqual(self.parse(c + ' /k r27 props text'), ' text') 118 self.assertEqual(self.result, ('/k', 27, False, True)) 119 120 def test_lock(self): 121 self.assertRaises(svn_server_log_parse.Error, self.parse, 'lock') 122 self.parse('lock (/foo)') 123 self.assertEqual(self.result, (['/foo'], False)) 124 self.assertEqual(self.parse('lock (/foo) steal ...'), ' ...') 125 self.assertEqual(self.result, (['/foo'], True)) 126 self.assertEqual(self.parse('lock (/foo) stear'), ' stear') 127 128 def test_change_rev_prop(self): 129 self.assertRaises(svn_server_log_parse.Error, 130 self.parse, 'change-rev-prop r3') 131 self.assertRaises(svn_server_log_parse.Error, 132 self.parse, 'change-rev-prop r svn:log') 133 self.assertRaises(svn_server_log_parse.Error, 134 self.parse, 'change-rev-prop rX svn:log') 135 self.assertEqual(self.parse('change-rev-prop r3 svn:log ...'), ' ...') 136 self.assertEqual(self.result, (3, 'svn:log')) 137 138 def test_rev_proplist(self): 139 self.assertRaises(svn_server_log_parse.Error, 140 self.parse, 'rev-proplist') 141 self.assertRaises(svn_server_log_parse.Error, 142 self.parse, 'rev-proplist r') 143 self.assertRaises(svn_server_log_parse.Error, 144 self.parse, 'rev-proplist rX') 145 self.assertEqual(self.parse('rev-proplist r3 ...'), ' ...') 146 self.assertEqual(self.result, (3,)) 147 148 def test_rev_prop(self): 149 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop') 150 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop r') 151 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop rX') 152 self.assertEqual(self.parse('rev-prop r3 foo ...'), ' ...') 153 self.assertEqual(self.result, (3, 'foo')) 154 155 def test_unlock(self): 156 self.assertRaises(svn_server_log_parse.Error, self.parse, 'unlock') 157 self.parse('unlock (/foo)') 158 self.assertEqual(self.result, (['/foo'], False)) 159 self.assertEqual(self.parse('unlock (/foo) break ...'), ' ...') 160 self.assertEqual(self.result, (['/foo'], True)) 161 self.assertEqual(self.parse('unlock (/foo) bear'), ' bear') 162 163 def test_get_lock(self): 164 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-lock') 165 self.parse('get-lock /foo') 166 self.assertEqual(self.result, ('/foo',)) 167 168 def test_get_locks(self): 169 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-locks') 170 self.parse('get-locks /foo') 171 self.assertEqual(self.result, ('/foo',)) 172 173 def test_get_locations(self): 174 self.assertRaises(svn_server_log_parse.Error, self.parse, 175 'get-locations') 176 self.assertRaises(svn_server_log_parse.Error, 177 self.parse, 'get-locations /foo 3') 178 self.assertEqual(self.parse('get-locations /foo (3 4) ...'), ' ...') 179 self.assertEqual(self.result, ('/foo', [3, 4])) 180 self.assertEqual(self.parse('get-locations /foo (3)'), '') 181 self.assertEqual(self.result, ('/foo', [3])) 182 183 def test_get_location_segments(self): 184 self.assertRaises(svn_server_log_parse.Error, self.parse, 185 'get-location-segments') 186 self.assertRaises(svn_server_log_parse.Error, 187 self.parse, 'get-location-segments /foo 3') 188 self.assertEqual(self.parse('get-location-segments /foo@2 r3:4'), '') 189 self.assertEqual(self.result, ('/foo', 2, 3, 4)) 190 191 def test_get_file_revs(self): 192 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs') 193 self.assertRaises(svn_server_log_parse.Error, 194 self.parse, 'get-file-revs /foo 3') 195 self.assertRaises(svn_server_log_parse.Error, 196 self.parse, 'get-file-revs /foo 3:a') 197 self.assertRaises(svn_server_log_parse.Error, 198 self.parse, 'get-file-revs /foo r3:a') 199 self.assertEqual(self.parse('get-file-revs /foo r3:4 ...'), ' ...') 200 self.assertEqual(self.result, ('/foo', 3, 4, False)) 201 self.assertEqual(self.parse('get-file-revs /foo r3:4' 202 ' include-merged-revisions ...'), ' ...') 203 self.assertEqual(self.result, ('/foo', 3, 4, True)) 204 205 def test_get_mergeinfo(self): 206 self.assertRaises(svn_server_log_parse.Error, 207 self.parse, 'get-mergeinfo') 208 self.assertRaises(svn_server_log_parse.Error, 209 self.parse, 'get-mergeinfo /foo') 210 self.assertRaises(svn_server_log_parse.Error, 211 self.parse, 'get-mergeinfo (/foo') 212 self.assertRaises(svn_server_log_parse.Error, 213 self.parse, 'get-mergeinfo (/foo /bar') 214 self.assertRaises(svn_server_log_parse.Error, 215 self.parse, 'get-mergeinfo (/foo)') 216 self.assertRaises(svn_server_log_parse.BadMergeinfoInheritanceError, 217 self.parse, 'get-mergeinfo (/foo) bork') 218 self.assertEqual(self.parse('get-mergeinfo (/foo) explicit'), '') 219 self.assertEqual(self.result, (['/foo'], 220 svn.core.svn_mergeinfo_explicit, False)) 221 self.assertEqual(self.parse('get-mergeinfo (/foo /bar) inherited ...'), 222 ' ...') 223 self.assertEqual(self.result, (['/foo', '/bar'], 224 svn.core.svn_mergeinfo_inherited, False)) 225 self.assertEqual(self.result, (['/foo', '/bar'], 226 svn.core.svn_mergeinfo_inherited, False)) 227 228 def test_log(self): 229 self.assertRaises(svn_server_log_parse.Error, self.parse, 'log') 230 self.assertRaises(svn_server_log_parse.Error, 231 self.parse, 'log /foo') 232 self.assertRaises(svn_server_log_parse.Error, 233 self.parse, 'log (/foo)') 234 self.assertEqual(self.parse('log (/foo) r3:4' 235 ' include-merged-revisions'), '') 236 self.assertEqual(self.result, 237 (['/foo'], 3, 4, 0, False, False, True, [])) 238 self.assertEqual(self.parse('log (/foo /bar) r3:4 revprops=all ...'), 239 ' ...') 240 self.assertEqual(self.result, 241 (['/foo', '/bar'], 3, 4, 0, False, False, False, None)) 242 self.assertEqual(self.parse('log (/foo) r3:4 revprops=(a b) ...'), 243 ' ...') 244 self.assertEqual(self.result, 245 (['/foo'], 3, 4, 0, False, False, False, ['a', 'b'])) 246 self.assertEqual(self.parse('log (/foo) r8:1 limit=3'), '') 247 self.assertEqual(self.result, 248 (['/foo'], 8, 1, 3, False, False, False, [])) 249 250 def test_check_path(self): 251 self.assertRaises(svn_server_log_parse.Error, self.parse, 'check-path') 252 self.assertEqual(self.parse('check-path /foo@9'), '') 253 self.assertEqual(self.result, ('/foo', 9)) 254 255 def test_stat(self): 256 self.assertRaises(svn_server_log_parse.Error, self.parse, 'stat') 257 self.assertEqual(self.parse('stat /foo@9'), '') 258 self.assertEqual(self.result, ('/foo', 9)) 259 260 def test_replay(self): 261 self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay') 262 self.assertRaises(svn_server_log_parse.Error, 263 self.parse, 'replay /foo') 264 self.assertRaises(svn_server_log_parse.Error, 265 self.parse, 'replay (/foo) r9') 266 self.assertRaises(svn_server_log_parse.Error, 267 self.parse, 'replay (/foo) r9:10') 268 self.assertEqual(self.parse('replay /foo r9'), '') 269 self.assertEqual(self.result, ('/foo', 9)) 270 271 def test_checkout_or_export(self): 272 self.assertRaises(svn_server_log_parse.Error, 273 self.parse, 'checkout-or-export') 274 self.assertRaises(svn_server_log_parse.Error, 275 self.parse, 'checkout-or-export /foo') 276 self.assertEqual(self.parse('checkout-or-export /foo r9'), '') 277 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown)) 278 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 279 'checkout-or-export /foo r9 depth=INVALID-DEPTH') 280 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 281 'checkout-or-export /foo r9 depth=bork') 282 self.assertEqual(self.parse('checkout-or-export /foo r9 depth=files .'), 283 ' .') 284 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files)) 285 286 def test_diff_1path(self): 287 self.assertRaises(svn_server_log_parse.Error, 288 self.parse, 'diff') 289 self.assertEqual(self.parse('diff /foo r9:10'), '') 290 self.assertEqual(self.result, ('/foo', 9, 10, 291 svn.core.svn_depth_unknown, False)) 292 self.assertEqual(self.parse('diff /foo r9:10' 293 ' ignore-ancestry ...'), ' ...') 294 self.assertEqual(self.result, ('/foo', 9, 10, 295 svn.core.svn_depth_unknown, True)) 296 self.assertEqual(self.parse('diff /foo r9:10 depth=files'), '') 297 self.assertEqual(self.result, ('/foo', 9, 10, 298 svn.core.svn_depth_files, False)) 299 300 def test_diff_2paths(self): 301 self.assertEqual(self.parse('diff /foo@9 /bar@10'), '') 302 self.assertEqual(self.result, ('/foo', 9, '/bar', 10, 303 svn.core.svn_depth_unknown, False)) 304 self.assertEqual(self.parse('diff /foo@9 /bar@10' 305 ' ignore-ancestry ...'), ' ...') 306 self.assertEqual(self.result, ('/foo', 9, '/bar', 10, 307 svn.core.svn_depth_unknown, True)) 308 self.assertEqual(self.parse('diff /foo@9 /bar@10' 309 ' depth=files ignore-ancestry'), '') 310 self.assertEqual(self.result, ('/foo', 9, '/bar', 10, 311 svn.core.svn_depth_files, True)) 312 313 def test_status(self): 314 self.assertRaises(svn_server_log_parse.Error, 315 self.parse, 'status') 316 self.assertRaises(svn_server_log_parse.Error, 317 self.parse, 'status /foo') 318 self.assertEqual(self.parse('status /foo r9'), '') 319 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown)) 320 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 321 'status /foo r9 depth=INVALID-DEPTH') 322 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 323 'status /foo r9 depth=bork') 324 self.assertEqual(self.parse('status /foo r9 depth=files .'), 325 ' .') 326 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files)) 327 328 def test_switch(self): 329 self.assertEqual(self.parse('switch /foo /bar@10 ...'), ' ...') 330 self.assertEqual(self.result, ('/foo', '/bar', 10, 331 svn.core.svn_depth_unknown)) 332 self.assertEqual(self.parse('switch /foo /bar@10' 333 ' depth=files'), '') 334 self.assertEqual(self.result, ('/foo', '/bar', 10, 335 svn.core.svn_depth_files)) 336 337 def test_update(self): 338 self.assertRaises(svn_server_log_parse.Error, 339 self.parse, 'update') 340 self.assertRaises(svn_server_log_parse.Error, 341 self.parse, 'update /foo') 342 self.assertEqual(self.parse('update /foo r9'), '') 343 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown, 344 False)) 345 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 346 'update /foo r9 depth=INVALID-DEPTH') 347 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 348 'update /foo r9 depth=bork') 349 self.assertEqual(self.parse('update /foo r9 depth=files .'), ' .') 350 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files, 351 False)) 352 self.assertEqual(self.parse('update /foo r9 send-copyfrom-args .'), 353 ' .') 354 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown, 355 True)) 356 357if __name__ == '__main__': 358 if len(sys.argv) == 1: 359 # No arguments so run the unit tests. 360 unittest.main() 361 sys.stderr.write('unittest.main failed to exit\n') 362 sys.exit(2) 363 364 # Use the argument as the path to a log file to test against. 365 366 def uri_encode(s): 367 # urllib.parse.quote encodes :&@ characters, svn does not. 368 return urllib_parse_quote(s, safe='/:&@') 369 370 # Define a class to reconstruct the SVN-ACTION string. 371 class Test(svn_server_log_parse.Parser): 372 def handle_unknown(self, line): 373 sys.stderr.write('unknown log line at %d:\n%s\n' % (self.linenum, 374 line)) 375 sys.exit(2) 376 377 def handle_open(self, protocol, capabilities, path, ra_client, client): 378 capabilities = ' '.join(capabilities) 379 if ra_client is None: 380 ra_client = '-' 381 if client is None: 382 client = '-' 383 path = uri_encode(path) 384 self.action = ('open %d cap=(%s) %s %s %s' 385 % (protocol, capabilities, path, ra_client, client)) 386 387 def handle_reparent(self, path): 388 path = uri_encode(path) 389 self.action = 'reparent ' + path 390 391 def handle_get_latest_rev(self): 392 self.action = 'get-latest-rev' 393 394 def handle_get_dated_rev(self, date): 395 self.action = 'get-dated-rev ' + date 396 397 def handle_commit(self, revision): 398 self.action = 'commit r%d' % (revision,) 399 400 def handle_get_dir(self, path, revision, text, props): 401 path = uri_encode(path) 402 self.action = 'get-dir %s r%d' % (path, revision) 403 if text: 404 self.action += ' text' 405 if props: 406 self.action += ' props' 407 408 def handle_get_file(self, path, revision, text, props): 409 path = uri_encode(path) 410 self.action = 'get-file %s r%d' % (path, revision) 411 if text: 412 self.action += ' text' 413 if props: 414 self.action += ' props' 415 416 def handle_lock(self, paths, steal): 417 paths = [uri_encode(x) for x in paths] 418 self.action = 'lock (%s)' % (' '.join(paths),) 419 if steal: 420 self.action += ' steal' 421 422 def handle_change_rev_prop(self, revision, revprop): 423 revprop = uri_encode(revprop) 424 self.action = 'change-rev-prop r%d %s' % (revision, revprop) 425 426 def handle_rev_prop(self, revision, revprop): 427 revprop = uri_encode(revprop) 428 self.action = 'rev-prop r%d %s' % (revision, revprop) 429 430 def handle_rev_proplist(self, revision): 431 self.action = 'rev-proplist r%d' % (revision,) 432 433 def handle_unlock(self, paths, break_lock): 434 paths = [uri_encode(x) for x in paths] 435 self.action = 'unlock (%s)' % (' '.join(paths),) 436 if break_lock: 437 self.action += ' break' 438 439 def handle_get_lock(self, path): 440 path = uri_encode(path) 441 self.action = 'get-lock ' + path 442 443 def handle_get_locks(self, path): 444 self.action = 'get-locks ' + path 445 path = uri_encode(path) 446 447 def handle_get_locations(self, path, revisions): 448 path = uri_encode(path) 449 self.action = ('get-locations %s (%s)' 450 % (path, ' '.join([str(x) for x in revisions]))) 451 452 def handle_get_location_segments(self, path, peg, left, right): 453 path = uri_encode(path) 454 self.action = 'get-location-segments %s@%d r%d:%d' % (path, peg, 455 left, right) 456 457 def handle_get_file_revs(self, path, left, right, 458 include_merged_revisions): 459 path = uri_encode(path) 460 self.action = 'get-file-revs %s r%d:%d' % (path, left, right) 461 if include_merged_revisions: 462 self.action += ' include-merged-revisions' 463 464 def handle_get_mergeinfo(self, paths, inheritance, include_descendants): 465 paths = [uri_encode(x) for x in paths] 466 self.action = ('get-mergeinfo (%s) %s' 467 % (' '.join(paths), 468 svn.core.svn_inheritance_to_word(inheritance))) 469 if include_descendants: 470 self.action += ' include-descendants' 471 472 def handle_log(self, paths, left, right, limit, discover_changed_paths, 473 strict, include_merged_revisions, revprops): 474 paths = [uri_encode(x) for x in paths] 475 self.action = 'log (%s) r%d:%d' % (' '.join(paths), 476 left, right) 477 if limit != 0: 478 self.action += ' limit=%d' % (limit,) 479 if discover_changed_paths: 480 self.action += ' discover-changed-paths' 481 if strict: 482 self.action += ' strict' 483 if include_merged_revisions: 484 self.action += ' include-merged-revisions' 485 if revprops is None: 486 self.action += ' revprops=all' 487 elif len(revprops) > 0: 488 revprops = [uri_encode(x) for x in revprops] 489 self.action += ' revprops=(%s)' % (' '.join(revprops),) 490 491 def handle_check_path(self, path, revision): 492 path = uri_encode(path) 493 self.action = 'check-path %s@%d' % (path, revision) 494 495 def handle_stat(self, path, revision): 496 path = uri_encode(path) 497 self.action = 'stat %s@%d' % (path, revision) 498 499 def handle_replay(self, path, revision): 500 path = uri_encode(path) 501 self.action = 'replay %s r%d' % (path, revision) 502 503 def maybe_depth(self, depth): 504 if depth != svn.core.svn_depth_unknown: 505 self.action += ' depth=%s' % ( 506 svn.core.svn_depth_to_word(depth),) 507 508 def handle_checkout_or_export(self, path, revision, depth): 509 path = uri_encode(path) 510 self.action = 'checkout-or-export %s r%d' % (path, revision) 511 self.maybe_depth(depth) 512 513 def handle_diff_1path(self, path, left, right, 514 depth, ignore_ancestry): 515 path = uri_encode(path) 516 self.action = 'diff %s r%d:%d' % (path, left, right) 517 self.maybe_depth(depth) 518 if ignore_ancestry: 519 self.action += ' ignore-ancestry' 520 521 def handle_diff_2paths(self, from_path, from_rev, 522 to_path, to_rev, 523 depth, ignore_ancestry): 524 from_path = uri_encode(from_path) 525 to_path = uri_encode(to_path) 526 self.action = ('diff %s@%d %s@%d' 527 % (from_path, from_rev, to_path, to_rev)) 528 self.maybe_depth(depth) 529 if ignore_ancestry: 530 self.action += ' ignore-ancestry' 531 532 def handle_status(self, path, revision, depth): 533 path = uri_encode(path) 534 self.action = 'status %s r%d' % (path, revision) 535 self.maybe_depth(depth) 536 537 def handle_switch(self, from_path, to_path, to_rev, depth): 538 from_path = uri_encode(from_path) 539 to_path = uri_encode(to_path) 540 self.action = ('switch %s %s@%d' 541 % (from_path, to_path, to_rev)) 542 self.maybe_depth(depth) 543 544 def handle_update(self, path, revision, depth, send_copyfrom_args): 545 path = uri_encode(path) 546 self.action = 'update %s r%d' % (path, revision) 547 self.maybe_depth(depth) 548 if send_copyfrom_args: 549 self.action += ' send-copyfrom-args' 550 551 tmp = tempfile.mktemp() 552 try: 553 fp = open(tmp, 'w') 554 parser = Test() 555 parser.linenum = 0 556 log_file = sys.argv[1] 557 log_type = None 558 for line in open(log_file): 559 if log_type is None: 560 # Figure out which log type we have. 561 if re.match(r'\d+ \d\d\d\d-', line): 562 log_type = 'svnserve' 563 elif re.match(r'\[\d\d/', line): 564 log_type = 'mod_dav_svn' 565 else: 566 sys.stderr.write("unknown log format in '%s'" 567 % (log_file,)) 568 sys.exit(3) 569 sys.stderr.write('parsing %s log...\n' % (log_type,)) 570 sys.stderr.flush() 571 572 words = line.split() 573 if log_type == 'svnserve': 574 # Skip over PID, date, client address, username, and repos. 575 if words[5].startswith('ERR'): 576 # Skip error lines. 577 fp.write(line) 578 continue 579 leading = ' '.join(words[:5]) 580 action = ' '.join(words[5:]) 581 else: 582 # Find the SVN-ACTION string from the CustomLog format 583 # davautocheck.sh uses. If that changes, this will need 584 # to as well. Currently it's 585 # %t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e 586 leading = ' '.join(words[:4]) 587 action = ' '.join(words[4:]) 588 589 # Parse the action and write the reconstructed action to 590 # the temporary file. Ignore the returned trailing text, 591 # as we have none in the davautocheck ops log. 592 parser.linenum += 1 593 try: 594 parser.parse(action) 595 except svn_server_log_parse.Error: 596 sys.stderr.write('error at line %d: %s\n' 597 % (parser.linenum, action)) 598 raise 599 fp.write(leading + ' ' + parser.action + '\n') 600 fp.close() 601 # Check differences between original and reconstructed files 602 # (should be identical). 603 result = os.spawnlp(os.P_WAIT, 'diff', 'diff', '-u', log_file, tmp) 604 if result == 0: 605 sys.stderr.write('OK\n') 606 sys.exit(result) 607 finally: 608 try: 609 os.unlink(tmp) 610 except Exception as e: 611 sys.stderr.write('os.unlink(tmp): %s\n' % (e,)) 612