Edgewall Software

Ticket #7715: svn-mergeinfo-7715-2.patch

File svn-mergeinfo-7715-2.patch, 10.0 KB (added by eblot, 3 years ago)

cboos' last patch with deleted branches show/hide feature

  • trac/versioncontrol/svn_fs.py

     
    6060from trac.versioncontrol.cache import CachedRepository 
    6161from trac.versioncontrol.svn_authz import SubversionAuthorizer 
    6262from trac.versioncontrol.web_ui.browser import IPropertyRenderer 
    63 from trac.util import embedded_numbers 
     63from trac.versioncontrol.web_ui.changeset import IPropertyDiffRenderer 
     64from trac.util import Ranges, embedded_numbers, to_ranges 
    6465from trac.util.text import exception_to_unicode, to_unicode 
    6566from trac.util.translation import _ 
    6667from trac.util.datefmt import utc 
     
    308309 
    309310 
    310311class SubversionPropertyRenderer(Component): 
    311     implements(IPropertyRenderer) 
     312    implements(IPropertyRenderer, IPropertyDiffRenderer) 
    312313 
    313314    def __init__(self): 
    314315        self._externals_map = {} 
     
    323324        if name == 'svn:externals': 
    324325            return self._render_externals(props[name]) 
    325326        elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'): 
    326             return self._render_mergeinfo(props[name]) 
     327            return self._render_mergeinfo(name, mode, context, props) 
    327328        elif name == 'svn:needs-lock': 
    328329            return self._render_needslock(context) 
    329330 
     
    389390        return tag.ul([tag.li(tag.a(label, href=href, title=title)) 
    390391                       for label, href, title in externals_data]) 
    391392 
    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 "" 
    397409 
     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     
    398480    def _render_needslock(self, context): 
    399481        return tag.img(src=context.href.chrome('common/lock-locked.png'), 
    400482                       alt="needs lock", title="needs lock") 
    401483 
     484    def _get_revs_link(self, label, context, spath, revs): 
     485        if ',' in revs or '-' in revs: 
     486            revs_href = context.href.log(spath, revs=revs) 
     487        else: 
     488            revs_href = context.href.changeset(revs, spath) 
     489        return tag.a(label, title=revs.replace(',', ', '), href=revs_href) 
    402490 
     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 
    403553class SubversionRepository(Repository): 
    404554    """Repository implementation based on the svn.fs API.""" 
    405555 
  • trac/versioncontrol/templates/browser.html

     
    1414    <script type="text/javascript" src="${chrome.htdocs_location}js/folding.js"></script> 
    1515    <script type="text/javascript"> 
    1616      jQuery(document).ready(function($) { 
     17        $("#toggleoldmerged").click(function() { 
     18                  $("#oldmerged").slideToggle("fast"); 
     19                  return false; 
     20        }); 
    1721        $("#jumploc input").hide(); 
    1822        $("#jumploc select").change(function () { 
    1923          this.parentNode.parentNode.submit();