| 392 | | def _render_mergeinfo(self, prop): |
| 393 | | prop = prop.rsplit(':', 1) |
| 394 | | if len(prop) == 2: |
| 395 | | prop[1] = prop[1].replace(',', u',\u200b') |
| 396 | | return ':'.join(prop) |
| | 393 | def _get_blocked_revs(self, props, name, path): |
| | 394 | """Return the revisions blocked from merging for the given property |
| | 395 | name and path. |
| | 396 | """ |
| | 397 | if name == 'svnmerge-integrated': |
| | 398 | prop = props.get('svnmerge-blocked', '') |
| | 399 | else: |
| | 400 | return "" |
| | 401 | for line in prop.splitlines(): |
| | 402 | try: |
| | 403 | p, revs = line.split(':', 1) |
| | 404 | if p.strip('/') == path: |
| | 405 | return revs |
| | 406 | except Exception: |
| | 407 | pass |
| | 408 | return "" |
| | 410 | def _render_mergeinfo(self, name, mode, context, props): |
| | 411 | """Parse svn:mergeinfo and svnmerge-* properties, converting branch |
| | 412 | names to links and providing links to the revision log for merged |
| | 413 | and eligible revisions. |
| | 414 | """ |
| | 415 | has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') |
| | 416 | no_revisions = tag.span(_('revisions'), title=_('No revisions')) |
| | 417 | no_eligible = tag.span(_('eligible'), title=_('No eligible revisions')) |
| | 418 | visible_rows = [] |
| | 419 | hidden_rows = [] |
| | 420 | repos = self.env.get_repository() |
| | 421 | for line in props[name].splitlines(): |
| | 422 | try: |
| | 423 | path, revs = line.split(':', 1) |
| | 424 | spath = path.strip('/') |
| | 425 | revs = revs.strip() |
| | 426 | if 'LOG_VIEW' in context.perm('source', spath): |
| | 427 | row = [tag.a(path, title=_('View dir'), |
| | 428 | href=context.href.browser(spath, |
| | 429 | rev=context.resource.version))] |
| | 430 | if revs: |
| | 431 | label = (_('merged'), _('blocked'))[ |
| | 432 | name.endswith('blocked')] |
| | 433 | row.append(self._get_revs_link(label, context, spath, |
| | 434 | revs)) |
| | 435 | else: |
| | 436 | row.append(no_revisions) |
| | 437 | try: |
| | 438 | node = repos.get_node(spath, |
| | 439 | context.resource.version) |
| | 440 | except NoSuchNode: |
| | 441 | node = None |
| | 442 | if node and has_eligible: |
| | 443 | eligible = set() |
| | 444 | for (p, rev, chg) in node.get_history(): |
| | 445 | if p != spath: |
| | 446 | break |
| | 447 | eligible.add(rev) |
| | 448 | eligible -= set(Ranges(revs)) |
| | 449 | blocked = self._get_blocked_revs(props, name, spath) |
| | 450 | eligible -= set(Ranges(blocked)) |
| | 451 | if eligible: |
| | 452 | eligible = to_ranges(eligible) |
| | 453 | row.append(tag.a(_('eligible'), |
| | 454 | title=eligible.replace(',', ', '), |
| | 455 | href=context.href.log(spath, |
| | 456 | revs=eligible))) |
| | 457 | else: |
| | 458 | row.append(no_eligible) |
| | 459 | if node: |
| | 460 | visible_rows.append(tag.td(each) for each in row) |
| | 461 | else: |
| | 462 | hidden_rows.append(tag.td(e) for e in line.split(':')) |
| | 463 | else: |
| | 464 | revs = revs.replace(',', u',\u200b') |
| | 465 | visible_rows.append(tag.td(path), |
| | 466 | tag.td(revs, |
| | 467 | colspan=has_eligible and 2 or None)) |
| | 468 | except Exception, e: |
| | 469 | self.log.warning('Rendering of property %s failed: %s', name, |
| | 470 | exception_to_unicode(e)) |
| | 471 | rows.append(tag.td(line, colspan=has_eligible and 3 or 2)) |
| | 472 | vtable = tag.table(tag.tbody(tag.tr(each) for each in visible_rows)) |
| | 473 | htable = tag.table(tag.tbody(tag.tr(each) for each in hidden_rows), |
| | 474 | style='display: none', id="oldmerged") |
| | 475 | ttable = tag.table(tag.tbody(tag.tr(tag.td( |
| | 476 | tag.a(_('Toggle deleted branches'), |
| | 477 | href="#toggleoldmerged", id="toggleoldmerged"))))), |
| | 478 | return tag.div(vtable, ttable, htable) |
| | 479 | |
| | 491 | |
| | 492 | # IPropertyDiffRenderer methods |
| | 493 | |
| | 494 | def match_property_diff(self, name): |
| | 495 | return (name == 'svn:mergeinfo' or name.startswith('svnmerge-')) and \ |
| | 496 | 4 or 0 |
| | 497 | |
| | 498 | def render_property_diff(self, name, old_context, old_props, |
| | 499 | new_context, new_props, options): |
| | 500 | # build 3 columns table showing modifications on merge sources |
| | 501 | # || source (added|modified|removed) || added revs || removed revs || |
| | 502 | nothing_added = tag.span('-', title=_('nothing added')) |
| | 503 | nothing_removed = tag.span('-', title=_('nothing removed')) |
| | 504 | def parse_sources(props): |
| | 505 | sources = {} |
| | 506 | for line in props[name].splitlines(): |
| | 507 | path, revs = line.split(':', 1) |
| | 508 | spath = path.strip('/') |
| | 509 | revs = revs.strip() |
| | 510 | sources[spath] = set(Ranges(revs)) |
| | 511 | return sources |
| | 512 | old_sources = parse_sources(old_props) |
| | 513 | new_sources = parse_sources(new_props) |
| | 514 | # go through new sources, detect modified ones or added ones |
| | 515 | modified_and_added_sources = [] |
| | 516 | for spath, new_revs in new_sources.iteritems(): |
| | 517 | if spath in old_sources: |
| | 518 | old_revs = old_sources.pop(spath) |
| | 519 | status = '' |
| | 520 | else: |
| | 521 | old_revs = set() |
| | 522 | status = _(' (added)') |
| | 523 | def revs_link(revs, context): |
| | 524 | if revs: |
| | 525 | revs = to_ranges(revs) |
| | 526 | return self._get_revs_link(revs.replace(',', u',\u200b'), |
| | 527 | context, spath, revs) |
| | 528 | source_href = new_context.href.browser(spath, |
| | 529 | rev=new_context.resource.version) |
| | 530 | modified_and_added_sources.append([ |
| | 531 | tag.a(spath, status, title=_('View dir'), href=source_href), |
| | 532 | revs_link(new_revs - old_revs, new_context) or nothing_added, |
| | 533 | revs_link(old_revs - new_revs, old_context) or nothing_removed, |
| | 534 | ]) |
| | 535 | # go through remaining old sources, those were deleted |
| | 536 | removed_sources = [] |
| | 537 | for spath, old_revs in old_sources.iteritems(): |
| | 538 | removed_sources.append( |
| | 539 | [tag.a(spath, _(' (removed)'), |
| | 540 | title=_('View dir'), |
| | 541 | href=old_context.href.browser(spath, |
| | 542 | rev=old_context.resource.version)), |
| | 543 | nothing_added, |
| | 544 | nothing_removed]) |
| | 545 | return tag.div( |
| | 546 | tag.table( |
| | 547 | tag.thead(tag.th(c) for c in [name, _("added"), _("removed")]), |
| | 548 | tag.tbody([ |
| | 549 | tag.tr([tag.td(col) for col in row]) |
| | 550 | for row in modified_and_added_sources + removed_sources]))) |
| | 551 | |
| | 552 | |