--- a/README.markdown Wed Dec 17 10:21:27 2014 +0100
+++ b/README.markdown Fri Dec 12 11:17:43 2014 +0100
@@ -4,7 +4,7 @@
Installing
==========
-`hg-review` requires Mercurial 1.6+ and Python 2.5+. It requires a few other
+`hg-review` requires Mercurial 3.2+ and Python 2.5+. It requires a few other
things too, but they're bundled with the extension so you don't need to worry
about them.
--- a/review/api.py Wed Dec 17 10:21:27 2014 +0100
+++ b/review/api.py Fri Dec 12 11:17:43 2014 +0100
@@ -9,6 +9,7 @@
from mercurial.node import hex
from mercurial import ui as _ui
from mercurial import demandimport
+from mercurial import obsolete
demandimport.ignore.append('json')
try:
@@ -454,25 +455,35 @@
self.target = target
self.ui = ui
self.node = node
-
- if '%s/.exists' % self.node in self.repo['tip']:
- _match = lambda p: lambda fn: fn.startswith(p)
+ self.allnodes = [
+ hex(n) for n in obsolete.allprecursors(
+ target.obsstore, (self.node.decode('hex'),))
+ ]
- relevant = filter(_match(node), self.repo['tip'])
- commentfns = filter(_match('%s/comments' % node), relevant)
- signofffns = filter(_match('%s/signoffs' % node), relevant)
+ self.comments = []
+ self.signoffs = []
+
+ _match = lambda p: lambda fn: fn.startswith(p)
+
+ for node in self.allnodes:
+ if '%s/.exists' % node in self.repo['tip']:
- self.comments = [self._load_comment_file(fn) for fn in commentfns]
- self.comments.sort(key=operator.attrgetter('local_datetime'))
+ relevant = filter(_match(node), self.repo['tip'])
+ commentfns = filter(_match('%s/comments' % node), relevant)
+ signofffns = filter(_match('%s/signoffs' % node), relevant)
- self.signoffs = [self._load_signoff_file(fn) for fn in signofffns]
- self.signoffs.sort(key=operator.attrgetter('local_datetime'))
- else:
- self.comments = []
- self.signoffs = []
+ self.comments.extend(
+ [self._load_comment_file(fn) for fn in commentfns])
+
+ self.signoffs.extend(
+ [self._load_signoff_file(fn) for fn in signofffns])
+
+ self.comments.sort(key=operator.attrgetter('local_datetime'))
+ self.signoffs.sort(key=operator.attrgetter('local_datetime'))
def signoffs_for_user(self, username):
- return filter(lambda s: s.author == username, self.signoffs)
+ return filter(lambda s: s.node == self.node
+ and s.author == username, self.signoffs)
def signoffs_for_current_user(self):
return self.signoffs_for_user(fromlocal(self.ui.username()))
@@ -492,8 +503,7 @@
if existing:
raise SignoffExists
- if not (self.comments or self.signoffs):
- self._create_exists_entry()
+ self._create_exists_entry()
signoff = ReviewSignoff(fromlocal(self.ui.username()), util.makedate(),
self.node, opinion, message, style)
@@ -518,8 +528,7 @@
if filename and not ufilename:
ufilename = fromlocal(filename)
- if not (self.comments or self.signoffs):
- self._create_exists_entry()
+ self._create_exists_entry()
comment = ReviewComment(fromlocal(self.ui.username()), util.makedate(),
self.node, ufilename, filename, map(int, lines), message, style)
--- a/review/static/styles/style.less Wed Dec 17 10:21:27 2014 +0100
+++ b/review/static/styles/style.less Fri Dec 12 11:17:43 2014 +0100
@@ -251,6 +251,12 @@
background-color: lighten(@c-cream, 8%);
}
+ &.draft {
+ .rev {
+ border: 1px solid blue;
+ }
+ }
+
td {
border: none;
line-height: 26px;
@@ -455,6 +461,10 @@
border-bottom: 1px solid #ddd;
position: relative;
min-height: 41px;
+
+ &.obsolete {
+ opacity: 0.6;
+ }
&:first-child {
border-top: none;
@@ -634,6 +644,10 @@
border-top: 1px solid #ddd;
background-color: #f4f4f4;
+ &.obsolete {
+ opacity: 0.6;
+ }
+
.avatar {
float: right;
margin-top: 2px;
--- a/review/templates/index.html Wed Dec 17 10:21:27 2014 +0100
+++ b/review/templates/index.html Fri Dec 12 11:17:43 2014 +0100
@@ -8,10 +8,11 @@
<table>
{% for rcset in rcsets %}
{% set rev = rcset.target[rcset.node] %}
+ {% set phase = rev.phasestr() %}
{% set node_short = utils['node_short'](rev.node()) %}
{% set link = "/changeset/" + node_short + "/" %}
- <tr>
+ <tr class="{{ phase }}">
<td class="node"><span class="rev">{{ rev.rev() }}</span><span class="sep">:</span><span class="hash">{{ node_short }}</span></td>
<td class="user">{{ utils['person'](rev.user()) }}</td>
<td class="desc">
--- a/review/templates/pieces/comment.html Wed Dec 17 10:21:27 2014 +0100
+++ b/review/templates/pieces/comment.html Fri Dec 12 11:17:43 2014 +0100
@@ -1,8 +1,12 @@
{% if comment.style == 'markdown' %}
{% set rendered = utils['markdown'](comment.message) %}
{% endif %}
+{% if comment.node != rev.hex() %}
+ {% set obsolete = True %}
+ {% set obsclass = ' obsolete' %}
+{% endif %}
-<div class="comment group" id="comment-{{ comment.identifier }}">
+<div class="comment group{{ obsclass }}" id="comment-{{ comment.identifier }}">
<a href="#comment-{{ comment.identifier }}" rel="comments" class="expand" id="comment-expand-{{ comment.identifier }}">→</a>
<script type="text/javascript">
$(function() {
@@ -38,6 +42,9 @@
<div class="context">
{% if comment.ufilename %}
<div class="context-head">on {{ comment.ufilename }}</div>
+ {% elif obsolete %}
+ <div class="context-head">on changeset obsolete
+ {{ utils['node_short'](comment.node.decode('hex')) }}</div>
{% else %}
<div class="context-head">on changeset {{ rev.rev() }}:{{ utils['node_short'](rev.node()) }}</div>
{% endif %}
--- a/review/templates/pieces/linecomment.html Wed Dec 17 10:21:27 2014 +0100
+++ b/review/templates/pieces/linecomment.html Fri Dec 12 11:17:43 2014 +0100
@@ -1,9 +1,13 @@
{% if comment.style == 'markdown' %}
{% set rendered = utils['markdown'](comment.message) %}
{% endif %}
+{% if comment.node != rev.hex() %}
+ {% set obsolete = True %}
+ {% set obsclass = ' obsolete' %}
+{% endif %}
<tr class="comment">
- <td class="comment group" colspan="3" id="comment-{{ comment.identifier }}">
+ <td class="comment group {{ obsclass }}" colspan="3" id="comment-{{ comment.identifier }}">
<div class="comment-content">
<span class="identifier disabled">{{ comment.identifier }}</span>
<span class="commentlines disabled">{{ ','.join(utils['map'](utils['str'], comment.lines)) }}</span>
--- a/review/templates/pieces/signoff.html Wed Dec 17 10:21:27 2014 +0100
+++ b/review/templates/pieces/signoff.html Fri Dec 12 11:17:43 2014 +0100
@@ -2,7 +2,12 @@
{% set rendered = utils['markdown'](signoff.message) %}
{% endif %}
-<div class="signoff group {{ signoff.opinion or 'neutral' }}">
+{% if signoff.node != rev.hex() %}
+ {% set obsolete = True %}
+ {% set obsclass = ' obsolete' %}
+{% endif %}
+
+<div class="signoff group {{ signoff.opinion or 'neutral' }}{{ obsclass }}">
<div class="avatar">
<img src="{{ utils['item_gravatar'](signoff, 30) }}" />
</div>
--- a/review/web.py Wed Dec 17 10:21:27 2014 +0100
+++ b/review/web.py Fri Dec 12 11:17:43 2014 +0100
@@ -53,10 +53,21 @@
except (IndexError, KeyError):
return 'con'
+def _last_signoffs(signoffs):
+ last_signoff_by_author = {}
+ for signoff in signoffs:
+ author = signoff.author
+ if (author in last_signoff_by_author and
+ last_signoff_by_author[author].hgdate > signoff.hgdate):
+ continue
+ last_signoff_by_author[author] = signoff
+ return last_signoff_by_author.values()
+
def _categorize_signoffs(signoffs):
- return { 'yes': len(filter(lambda s: s.opinion == 'yes', signoffs)),
- 'no': len(filter(lambda s: s.opinion == 'no', signoffs)),
- 'neutral': len(filter(lambda s: s.opinion == '', signoffs)),}
+ last_signoffs = _last_signoffs(signoffs)
+ return { 'yes': len(filter(lambda s: s.opinion == 'yes', last_signoffs)),
+ 'no': len(filter(lambda s: s.opinion == 'no', last_signoffs)),
+ 'neutral': len(filter(lambda s: s.opinion == '', last_signoffs)),}
def _email(s):
return fromlocal(email(s))
@@ -116,16 +127,20 @@
if rev_max > tip or rev_max < 0:
rev_max = tip
- rev_min = rev_max - LOG_PAGE_LEN if rev_max >= LOG_PAGE_LEN else 0
- if rev_min < 0:
- rev_min = 0
+ revs = g.datastore.target.revs('sort(last(:%s, %s), -rev)' % (
+ rev_max, LOG_PAGE_LEN))
+
+ rev_max, rev_min = revs.first(), revs.last()
+
+ older = g.datastore.target.revs('first(last(:%s, 2))' % rev_min).first()
- older = rev_min - 1 if rev_min > 0 else -1
- newer = rev_max + LOG_PAGE_LEN + 1 if rev_max < tip else -1
- if newer > tip:
- newer = tip
+ if rev_max == tip:
+ newer = -1
+ else:
+ newer = g.datastore.target.revs('last(first(%s:, %s))' % (
+ rev_max, LOG_PAGE_LEN + 1)).first()
- rcsets = [g.datastore[r] for r in xrange(rev_max, rev_min - 1, -1)]
+ rcsets = [g.datastore[r] for r in revs]
return _render('index.html', rcsets=rcsets, newer=newer, older=older)
@@ -187,8 +202,12 @@
cu_signoff = cu_signoffs[0] if cu_signoffs else None
tip = g.datastore.target['tip'].rev()
- newer = rcset.target[rev.rev() + 1] if rev.rev() < tip else None
- older = rcset.target[rev.rev() - 1] if rev.rev() > 0 else None
+ newer = rcset.target[
+ rcset.target.revs('sort(first(%s:, 2), -rev)' % rev.rev()).first()
+ ] if rev.rev() < tip else None
+ older = rcset.target[
+ rcset.target.revs('sort(last(:%s, 2), rev)' % rev.rev()).first()
+ ] if rev.rev() > 0 else None
return _render('changeset.html', rcset=rcset, rev=rev, cu_signoff=cu_signoff,
newer=newer, older=older)