Add pushhead() revset; fix bug in push head storage
authorGregory Szorc <gps@mozilla.com>
Fri, 08 Nov 2013 01:35:33 -0800
changeset 68 1a0274f1ff870879696d163639630a08f68718b3
parent 67 40cd3c48f7817119e1f65f5cad8566bf78039079
child 69 1af548d52f11c1da60f242b7c44571b186e1f4cb
push id49
push usergszorc@mozilla.com
push dateFri, 08 Nov 2013 09:35:35 +0000
Add pushhead() revset; fix bug in push head storage
__init__.py
mozautomation/changetracker.py
--- a/__init__.py
+++ b/__init__.py
@@ -126,16 +126,19 @@ me()
 
    Currently, this only retrieves changesets you authored (via ui.username).
    In the future, this extension will index review syntax in commit messages
    and return changesets that you reviewed.
 
 nobug()
    Retrieve changesets that don't reference a bug in the commit message.
 
+pushhead(TREE)
+   Retrieve changesets that are push heads for a given tree.
+
 firstpushtree(TREE)
    Retrieve changesets that initially landed on the specified tree.
 
 tree(TREE)
    Retrieve changesets that are currently in the specified tree.
 
    Trees are specified with a known alias. e.g. ``tree(central)``.
 
@@ -776,16 +779,36 @@ def revset_firstpushtree(repo, subset, x
 
         if not pushes:
             continue
 
         if pushes[0][0] == tree:
             yield rev
 
 
+def revset_pushhead(repo, subset, x):
+    """``pushhead(TREE)``
+    Changesets that are push heads.
+
+    A push head is a changeset that was a head when it was pushed to a
+    repository. In other words, the automation infrastructure likely
+    kicked off a build using this changeset.
+    """
+    tree = revset.getstring(x, _('pushhead() requires a string argument.'))
+    tree, uri = resolve_trees_to_uris([tree])[0]
+
+    if not uri:
+        raise util.Abort(_("Don't know about tree: %s") % tree)
+
+    heads = set(repo[r[4]].rev() for r in
+        repo.changetracker.tree_push_heads(tree))
+
+    return [r for r in subset if r in heads]
+
+
 def template_bug(repo, ctx, **args):
     """:bug: String. The bug this changeset is most associated with."""
     bugs = parse_bugs(ctx.description())
     return bugs[0] if bugs else None
 
 
 def template_bugs(repo, ctx, **args):
     """:bugs: List of ints. The bugs associated with this changeset."""
@@ -960,16 +983,17 @@ def extsetup(ui):
         pass
 
     extensions.wrapcommand(commands.table, 'pull', pullexpand)
 
     revset.symbols['bug'] = revset_bug
     revset.symbols['dontbuild'] = revset_dontbuild
     revset.symbols['me'] = revset_me
     revset.symbols['nobug'] = revset_nobug
+    revset.symbols['pushhead'] = revset_pushhead
     revset.symbols['tree'] = revset_tree
     revset.symbols['firstpushtree'] = revset_firstpushtree
 
     templatekw.keywords['bug'] = template_bug
     templatekw.keywords['bugs'] = template_bugs
     templatekw.keywords['firstrelease'] = template_firstrelease
     templatekw.keywords['firstbeta'] = template_firstbeta
     templatekw.keywords['firstaurora'] = template_firstaurora
--- a/mozautomation/changetracker.py
+++ b/mozautomation/changetracker.py
@@ -101,33 +101,45 @@ class ChangeTracker(object):
         last_push_id = last_push_id[0] if last_push_id else -1
 
         with self._db:
             for push_id, push in repo.push_info(start_id=last_push_id + 1):
                 self._db.execute('INSERT INTO pushes (push_id, tree_id, time, '
                 'user) VALUES (?, ?, ?, ?)', [push_id, tree_id, push['date'],
                     push['user']])
 
-                head = buffer(binascii.unhexlify(push['changesets'][0]))
+                head = buffer(binascii.unhexlify(push['changesets'][-1]))
 
                 params = [(buffer(binascii.unhexlify(c)), head, push_id,
                     tree_id) for c in push['changesets']]
                 self._db.executemany('INSERT INTO changeset_pushes VALUES '
                     '(?, ?, ?, ?)', params)
 
     def pushes_for_changeset(self, changeset):
         for row in self._db.execute('SELECT trees.name, pushes.push_id, '
             'pushes.time, pushes.user, changeset_pushes.head_changeset '
             'FROM trees, pushes, changeset_pushes '
             'WHERE pushes.push_id = changeset_pushes.push_id AND '
             'pushes.tree_id = changeset_pushes.tree_id AND '
             'trees.id = pushes.tree_id AND changeset_pushes.changeset=? '
             'ORDER BY pushes.time ASC', [buffer(changeset)]):
             yield row
 
+    def tree_push_heads(self, tree):
+        """Obtain all pushes on a given tree."""
+        for name, pid, t, user, head in self._db.execute(
+            'SELECT trees.name, pushes.push_id, '
+            'pushes.time, pushes.user, changeset_pushes.head_changeset '
+            'FROM trees, pushes, changeset_pushes '
+            'WHERE pushes.push_id = changeset_pushes.push_id AND '
+            'pushes.tree_id = changeset_pushes.tree_id AND '
+            'trees.id = pushes.tree_id AND trees.name=? '
+            'ORDER BY pushes.time ASC', [tree]):
+            yield name, pid, t, user, str(head)
+
     def associate_bugs_with_changeset(self, bugs, changeset):
         """Associate a numeric bug number with a changeset.
 
         This facilitates rapidly looking up changesets associated with
         bugs.
         """
         if len(changeset) != 20:
             raise ValueError('Expected binary changesets, not hex.')