Bug 1048322 - Include commit messages in Try submission emails. r=catlee
authorBirunthan Mohanathas <birunthan@mohanathas.com>
Thu, 28 Aug 2014 10:16:23 -0700
changeset 3821 f57b59955738
parent 3820 b0e0b15b24c4
child 3824 57d3152f9e1e
child 3825 0e1829955f1a
push id3135
push userbirunthan@mohanathas.com
push dateThu, 28 Aug 2014 17:19:05 +0000
reviewerscatlee
bugs1048322
Bug 1048322 - Include commit messages in Try submission emails. r=catlee
changes/hgpoller.py
status/generators.py
test/test_hgpoller.py
test/test_status.py
--- a/changes/hgpoller.py
+++ b/changes/hgpoller.py
@@ -267,16 +267,18 @@ class BaseHgPoller(BasePoller):
             return
 
         # We want to add at most self.maxChanges changes per push. If
         # mergePushChanges is True, then we'll get up to maxChanges pushes,
         # each with up to maxChanges changes.
         # Go through the list of pushes backwards, since we want to keep the
         # latest ones and possibly discard earlier ones.
         change_list = []
+        commit_titles = []
+        commit_titles_total_length = 0
         too_many = False
         for push in reversed(pushes):
             # Used for merging push changes
             c = dict(
                 user=push['user'],
                 date=push['date'],
                 files=[],
                 desc="",
@@ -302,16 +304,31 @@ class BaseHgPoller(BasePoller):
                     c['files'].extend(change['files'])
                     # Keep the comments and revision of the last change of this push.
                     # We're going through the changes in reverse order, so we
                     # should use the comments and revision of the first change
                     # in this loop
                     if c['node'] is None:
                         c['desc'] = change['desc']
                         c['node'] = change['node']
+
+                    title = change['desc'].split('\n', 1)[0];
+                    if len(title) > 100:
+                        trim_pos = title.rfind(' ', 0, 100)
+                        if trim_pos == -1:
+                            trim_pos = 100
+                        title = title[:trim_pos]
+                    # The commit titles are stored in a Change property, which
+                    # are limited to 1024 chars in the database (see
+                    # change_properties in buildbot/db/scheme/tables.sql). In
+                    # order to avoid insert/update failures, we enforce a cap
+                    # on the total length with enough room for JSON overhead.
+                    if commit_titles_total_length + len(title) <= 800:
+                        commit_titles_total_length += len(title)
+                        commit_titles.append(title)
                 else:
                     c = dict(
                         user=push['user'],
                         date=push['date'],
                         files=change['files'],
                         desc=change['desc'],
                         node=change['node'],
                         branch=change['branch'],
@@ -357,16 +374,19 @@ class BaseHgPoller(BasePoller):
                 link = "%s/rev/%s" % (self.baseURL, change["node"])
                 c = changes.Change(who=change["user"],
                                    files=change["files"],
                                    revision=change["node"],
                                    comments=change["desc"],
                                    revlink=link,
                                    when=change["date"],
                                    branch=self.branch)
+                if self.mergePushChanges:
+                    c.properties.setProperty('commit_titles', commit_titles,
+                                             'BaseHgPoller')
                 self.changeHook(c)
                 self.parent.addChange(c)
 
         # The repository isn't empty any more!
         self.emptyRepo = False
         # Use the last change found by the poller, regardless of if it's on our
         # branch or not. This is so we don't have to constantly ignore it in
         # future polls.
--- a/status/generators.py
+++ b/status/generators.py
@@ -24,9 +24,43 @@ https://tbpl.mozilla.org/?tree=%(tree)s&
 
 Alternatively, view them on Treeherder (experimental):
 https://treeherder.mozilla.org/ui/#/jobs?repo=%(treeherderTree)s&revision=%(revision)s
 
 Once completed, builds and logs will be available at:
 %(packageDir)s
 """ % locals()
 
+    commitTitles = change.properties.getProperty('commit_titles')
+    if commitTitles:
+        title = getSensibleCommitTitle(commitTitles)
+        allTitles = '\n  * '.join(commitTitles)
+
+        msgdict['subject'] += ': %(title)s' % locals()
+        msgdict['body'] += """\
+
+Summary:
+  * %(allTitles)s
+""" % locals()
+
     return msgdict
+
+def getSensibleCommitTitle(titles):
+    """
+    Returns the first non-trychooser title with unnecessary cruft removed.
+    """
+    for title in titles:
+        # Remove trychooser syntax.
+        title = re.sub(r'\btry: .*', '', title)
+
+        # Remove MQ cruft.
+        title = re.sub(r'^(imported patch|\[mq\]:) ', '', title);
+
+        # Remove review, feedback, etc. annotations.
+        title = re.sub(r'\b(r|sr|f|a)[=\?].*', '', title)
+
+        # Remove trailing punctuation and whitespace.
+        title = re.sub(r'[;,\-\. ]+$', '', title).strip();
+
+        if title:
+            return title
+
+    return titles[0]
--- a/test/test_hgpoller.py
+++ b/test/test_hgpoller.py
@@ -330,16 +330,23 @@ class RepoBranchHandling(unittest.TestCa
         self.doTest('default')
 
         # mergePushChanges is on by default, so we end up with a single change
         # here
         self.assertEquals(len(self.changes), 1)
         self.assertEquals(self.changes[0].revision,
                           '33be08836cb164f9e546231fc59e9e4cf98ed991')
 
+        titles = self.changes[0].properties.getProperty('commit_titles')
+        self.assertEquals(len(titles), 2)
+        self.assertEquals(titles[0],
+                          'Bug 563088 - Re-enable image discarding.r=joe,a=blocker')
+        self.assertEquals(titles[1],
+                          'Backout of changesets c866e73f3209 and baff7b7b32bc because of sicking\'s push-and-run bustage.')
+
     def testRelbranch(self):
         self.doTest('GECKO20b5pre_20100820_RELBRANCH')
 
         self.assertEquals(len(self.changes), 1)
         self.assertEquals(self.changes[0].revision,
                           '4c23e51a484f077ea27af3ea4a4ee13da5aeb5e6')
 
 
new file mode 100644
--- /dev/null
+++ b/test/test_status.py
@@ -0,0 +1,26 @@
+from twisted.trial import unittest
+from buildbot.changes import changes
+
+from buildbotcustom.status.generators import getSensibleCommitTitle
+
+SENSIBLE_TITLE_TESTCASES = [
+    ['Bug 1', ['Bug 1']],
+    ['Bug 1', ['Bug 1', 'Bug 2']],
+
+    ['Bug 1', ['try: -b d -p all', 'Bug 1']],
+    ['Bug 1', ['try: -b d -p all', 'try: -b d -p none', 'Bug 1']],
+
+    ['test.patch', ['[mq]: test.patch']],
+    ['test.patch', ['imported patch test.patch']],
+    ['test imported patch test.patch', ['test imported patch test.patch']],
+
+    ['Bug 1 - Test', ['Bug 1 - Test; r=me']],
+    ['Bug 1 - Test', ['Bug 1 - Test. r?me f=you']],
+
+    ['Bug 1', [' Bug 1;,.- ']],
+]
+
+class TestGenerator(unittest.TestCase):
+    def testGetSensibleCommitTitle(self):
+        for case in SENSIBLE_TITLE_TESTCASES:
+            self.assertEquals(getSensibleCommitTitle(case[1]), case[0])