acf857ef0abe

Merge upstream
[view raw] [browse files]
author Christophe de Vienne <cdevienne@gmail.com>
date Fri, 12 Dec 2014 11:17:43 +0100
parents af974f8d1eec (current diff) 16e0bcd4f854 (diff)
children bdfacbcf700e
branches/tags (none)
files review/static/styles/style.less review/templates/index.html review/web.py

Changes

--- 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 }}">&rarr;</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)