Edgewall Software

Ticket #7715: 7715-mergeinfo-with-eligible-r8263.patch

File 7715-mergeinfo-with-eligible-r8263.patch, 6.4 KB (added by rblank, 3 years ago)

mergeinfo display with eligible revisions

  • trac/htdocs/css/browser.css

    diff --git a/trac/htdocs/css/browser.css b/trac/htdocs/css/browser.css
    a b  
    174174 margin: 0 0 .4em 1.6em; 
    175175 padding: 0; 
    176176} 
    177 #info .props li { padding: 0; overflow: auto; } 
     177#info .props > li { padding: 2px 0; overflow: auto; } 
    178178 
    179179/* Styles for the HTML preview */ 
    180180#preview { background: #fff; clear: both; margin: 0 } 
  • trac/util/__init__.py

    diff --git a/trac/util/__init__.py b/trac/util/__init__.py
    a b  
    542542        else: 
    543543            return 0 
    544544 
     545def to_ranges(revs): 
     546    """Converts a list of revisions to a minimal set of ranges. 
     547     
     548    >>> to_ranges([2, 12, 3, 6, 9, 1, 5, 11]) 
     549    '1-3,5-6,9,11-12' 
     550    >>> to_ranges([]) 
     551    '' 
     552    """ 
     553    ranges = [] 
     554    begin = end = None 
     555    def store(): 
     556        if end == begin: 
     557            ranges.append(str(begin)) 
     558        else: 
     559            ranges.append('%d-%d' % (begin, end)) 
     560    for rev in sorted(revs): 
     561        if begin is None: 
     562            begin = end = rev 
     563        elif rev == end + 1: 
     564            end = rev 
     565        else: 
     566            store() 
     567            begin = end = rev 
     568    if begin is not None: 
     569        store() 
     570    return ','.join(ranges) 
     571 
    545572def content_disposition(type, filename=None): 
    546573    """Generate a properly escaped Content-Disposition header""" 
    547574    if filename is not None: 
  • trac/versioncontrol/svn_fs.py

    diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
    a b  
    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.util import Ranges, embedded_numbers, to_ranges 
    6464from trac.util.text import exception_to_unicode, to_unicode 
    6565from trac.util.translation import _ 
    6666from trac.util.datefmt import utc 
     
    323323        if name == 'svn:externals': 
    324324            return self._render_externals(props[name]) 
    325325        elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'): 
    326             return self._render_mergeinfo(props[name]) 
     326            return self._render_mergeinfo(name, mode, context, props) 
    327327        elif name == 'svn:needs-lock': 
    328328            return self._render_needslock(context) 
    329329 
     
    389389        return tag.ul([tag.li(tag.a(label, href=href, title=title)) 
    390390                       for label, href, title in externals_data]) 
    391391 
    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) 
    397  
     392    def _get_blocked_revs(self, props, name, path): 
     393        """Return the revisions blocked from merging for the given property 
     394        name and path. 
     395        """ 
     396        if name == 'svnmerge-integrated': 
     397            prop = props['svnmerge-blocked'] 
     398        else: 
     399            return "" 
     400        for line in prop.splitlines(): 
     401            try: 
     402                p, revs = line.split(':', 1) 
     403                if p.strip('/') == path: 
     404                    return revs 
     405            except Exception: 
     406                pass 
     407        return "" 
     408     
     409    def _render_mergeinfo(self, name, mode, context, props): 
     410        """Parse svn:mergeinfo and svnmerge-* properties, converting branch 
     411        names to links and providing links to the revision log for merged 
     412        and eligible revisions. 
     413        """ 
     414        has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') 
     415        rows = [] 
     416        for line in props[name].splitlines(): 
     417            try: 
     418                path, revs = line.split(':', 1) 
     419                spath = path.strip('/') 
     420                revs = revs.strip() 
     421                if 'LOG_VIEW' in context.perm('source', spath): 
     422                    row = [tag.a(path, title=_('View dir'), 
     423                                 href=context.href.browser(spath, 
     424                                                rev=context.resource.version))] 
     425                    if revs: 
     426                        log_href = context.href.log(spath, revs=revs) 
     427                        row.append(tag.a(_('revisions'), 
     428                                         title=revs.replace(',', ', '), 
     429                                         href=log_href)) 
     430                    else: 
     431                        row.append(tag.span(_('revisions'), 
     432                                            title=_('No revisions'))) 
     433                    if has_eligible: 
     434                        repos = self.env.get_repository() 
     435                        node = repos.get_node(spath, context.resource.version) 
     436                        eligible = set() 
     437                        for (p, rev, chg) in node.get_history(): 
     438                            if p != spath: 
     439                                break 
     440                            eligible.add(rev) 
     441                        eligible -= set(Ranges(revs)) 
     442                        blocked = self._get_blocked_revs(props, name, spath) 
     443                        eligible -= set(Ranges(blocked)) 
     444                        if eligible: 
     445                            eligible = to_ranges(eligible) 
     446                            row.append(tag.a(_('eligible'), 
     447                                       title=eligible.replace(',', ', '), 
     448                                       href=context.href.log(spath, 
     449                                                             revs=eligible))) 
     450                        else: 
     451                            row.append(tag.span(_('eligible'), 
     452                                       title=_('No eligible revisions'))) 
     453                    rows.append(tag.td(each) for each in row) 
     454                else: 
     455                    revs = revs.replace(',', u',\u200b') 
     456                    rows.append(tag.td(path), 
     457                                tag.td(revs, 
     458                                       colspan=has_eligible and 2 or None)) 
     459            except Exception, e: 
     460                self.log.warning('Rendering of property %s failed: %s', name, 
     461                                 exception_to_unicode(e)) 
     462                rows.append(tag.td(line, colspan=has_eligible and 3 or 2)) 
     463        return tag.div(tag.table(tag.tbody(tag.tr(each) for each in rows))) 
     464     
    398465    def _render_needslock(self, context): 
    399466        return tag.img(src=context.href.chrome('common/lock-locked.png'), 
    400467                       alt="needs lock", title="needs lock")