Ticket #7715: 7715-mergeinfo-with-eligible-r8263.patch
| File 7715-mergeinfo-with-eligible-r8263.patch, 6.4 KB (added by rblank, 3 years ago) |
|---|
-
trac/htdocs/css/browser.css
diff --git a/trac/htdocs/css/browser.css b/trac/htdocs/css/browser.css
a b 174 174 margin: 0 0 .4em 1.6em; 175 175 padding: 0; 176 176 } 177 #info .props li { padding:0; overflow: auto; }177 #info .props > li { padding: 2px 0; overflow: auto; } 178 178 179 179 /* Styles for the HTML preview */ 180 180 #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 542 542 else: 543 543 return 0 544 544 545 def 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 545 572 def content_disposition(type, filename=None): 546 573 """Generate a properly escaped Content-Disposition header""" 547 574 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 60 60 from trac.versioncontrol.cache import CachedRepository 61 61 from trac.versioncontrol.svn_authz import SubversionAuthorizer 62 62 from trac.versioncontrol.web_ui.browser import IPropertyRenderer 63 from trac.util import embedded_numbers63 from trac.util import Ranges, embedded_numbers, to_ranges 64 64 from trac.util.text import exception_to_unicode, to_unicode 65 65 from trac.util.translation import _ 66 66 from trac.util.datefmt import utc … … 323 323 if name == 'svn:externals': 324 324 return self._render_externals(props[name]) 325 325 elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'): 326 return self._render_mergeinfo( props[name])326 return self._render_mergeinfo(name, mode, context, props) 327 327 elif name == 'svn:needs-lock': 328 328 return self._render_needslock(context) 329 329 … … 389 389 return tag.ul([tag.li(tag.a(label, href=href, title=title)) 390 390 for label, href, title in externals_data]) 391 391 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 398 465 def _render_needslock(self, context): 399 466 return tag.img(src=context.href.chrome('common/lock-locked.png'), 400 467 alt="needs lock", title="needs lock")
