| 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 | rows = [] |
| | 419 | for line in props[name].splitlines(): |
| | 420 | try: |
| | 421 | path, revs = line.split(':', 1) |
| | 422 | spath = path.strip('/') |
| | 423 | revs = revs.strip() |
| | 424 | if 'LOG_VIEW' in context.perm('source', spath): |
| | 425 | row = [tag.a(path, title=_('View dir'), |
| | 426 | href=context.href.browser(spath, |
| | 427 | rev=context.resource.version))] |
| | 428 | if revs: |
| | 429 | label = (_('merged'), _('blocked'))[ |
| | 430 | name.endswith('blocked')] |
| | 431 | row.append(self._get_revs_link(label, context, spath, |
| | 432 | revs)) |
| | 433 | else: |
| | 434 | row.append(no_revisions) |
| | 435 | if has_eligible: |
| | 436 | repos = self.env.get_repository() |
| | 437 | node = repos.get_node(spath, context.resource.version) |
| | 438 | eligible = set() |
| | 439 | for (p, rev, chg) in node.get_history(): |
| | 440 | if p != spath: |
| | 441 | break |
| | 442 | eligible.add(rev) |
| | 443 | eligible -= set(Ranges(revs)) |
| | 444 | blocked = self._get_blocked_revs(props, name, spath) |
| | 445 | eligible -= set(Ranges(blocked)) |
| | 446 | if eligible: |
| | 447 | eligible = to_ranges(eligible) |
| | 448 | row.append(tag.a(_('eligible'), |
| | 449 | title=eligible.replace(',', ', '), |
| | 450 | href=context.href.log(spath, |
| | 451 | revs=eligible))) |
| | 452 | else: |
| | 453 | row.append(no_eligible) |
| | 454 | rows.append(tag.td(each) for each in row) |
| | 455 | else: |
| | 456 | revs = revs.replace(',', u',\u200b') |
| | 457 | rows.append(tag.td(path), |
| | 458 | tag.td(revs, |
| | 459 | colspan=has_eligible and 2 or None)) |
| | 460 | except Exception, e: |
| | 461 | self.log.warning('Rendering of property %s failed: %s', name, |
| | 462 | exception_to_unicode(e)) |
| | 463 | rows.append(tag.td(line, colspan=has_eligible and 3 or 2)) |
| | 464 | return tag.div(tag.table(tag.tbody(tag.tr(each) for each in rows))) |
| | 465 | |
| | 470 | def _get_revs_link(self, label, context, spath, revs): |
| | 471 | if ',' in revs or '-' in revs: |
| | 472 | revs_href = context.href.log(spath, revs=revs) |
| | 473 | else: |
| | 474 | revs_href = context.href.changeset(revs, spath) |
| | 475 | return tag.a(label, title=revs.replace(',', ', '), href=revs_href) |
| | 477 | |
| | 478 | # IPropertyDiffRenderer methods |
| | 479 | |
| | 480 | def match_property_diff(self, name): |
| | 481 | return (name == 'svn:mergeinfo' or name.startswith('svnmerge-')) and \ |
| | 482 | 4 or 0 |
| | 483 | |
| | 484 | def render_property_diff(self, name, old_context, old_props, |
| | 485 | new_context, new_props, options): |
| | 486 | # build 3 columns table showing modifications on merge sources |
| | 487 | # || source (added|modified|removed) || added revs || removed revs || |
| | 488 | nothing_added = tag.span('-', title=_('nothing added')) |
| | 489 | nothing_removed = tag.span('-', title=_('nothing removed')) |
| | 490 | def parse_sources(props): |
| | 491 | sources = {} |
| | 492 | for line in props[name].splitlines(): |
| | 493 | path, revs = line.split(':', 1) |
| | 494 | spath = path.strip('/') |
| | 495 | revs = revs.strip() |
| | 496 | sources[spath] = set(Ranges(revs)) |
| | 497 | return sources |
| | 498 | old_sources = parse_sources(old_props) |
| | 499 | new_sources = parse_sources(new_props) |
| | 500 | # go through new sources, detect modified ones or added ones |
| | 501 | modified_and_added_sources = [] |
| | 502 | for spath, new_revs in new_sources.iteritems(): |
| | 503 | if spath in old_sources: |
| | 504 | old_revs = old_sources.pop(spath) |
| | 505 | status = '' |
| | 506 | else: |
| | 507 | old_revs = set() |
| | 508 | status = _(' (added)') |
| | 509 | def revs_link(revs, context): |
| | 510 | if revs: |
| | 511 | revs = to_ranges(revs) |
| | 512 | return self._get_revs_link(revs.replace(',', u',\u200b'), |
| | 513 | context, spath, revs) |
| | 514 | source_href = new_context.href.browser(spath, |
| | 515 | rev=new_context.resource.version) |
| | 516 | modified_and_added_sources.append([ |
| | 517 | tag.a(spath, status, title=_('View dir'), href=source_href), |
| | 518 | revs_link(new_revs - old_revs, new_context) or nothing_added, |
| | 519 | revs_link(old_revs - new_revs, old_context) or nothing_removed, |
| | 520 | ]) |
| | 521 | # go through remaining old sources, those were deleted |
| | 522 | removed_sources = [] |
| | 523 | for spath, old_revs in old_sources.iteritems(): |
| | 524 | removed_sources.append( |
| | 525 | [tag.a(spath, _(' (removed)'), |
| | 526 | title=_('View dir'), |
| | 527 | href=old_context.href.browser(spath, |
| | 528 | rev=old_context.resource.version)), |
| | 529 | nothing_added, |
| | 530 | nothing_removed]) |
| | 531 | return tag.div( |
| | 532 | tag.table( |
| | 533 | tag.thead(tag.th(c) for c in [name, _("added"), _("removed")]), |
| | 534 | tag.tbody([ |
| | 535 | tag.tr([tag.td(col) for col in row]) |
| | 536 | for row in modified_and_added_sources + removed_sources]))) |
| | 537 | |
| | 538 | |