<pre style='margin:0'>
Rainer Müller (raimue) pushed a commit to branch master
in repository trac.macports.org.

</pre>
<p><a href="https://github.com/macports/trac.macports.org/commit/b87ae642ad751eeb87169682e8bd162a0da6ca56">https://github.com/macports/trac.macports.org/commit/b87ae642ad751eeb87169682e8bd162a0da6ca56</a></p>
<pre style="white-space: pre; background: #F8F8F8"><span style='display:block; white-space:pre;color:#808000;'>commit b87ae642ad751eeb87169682e8bd162a0da6ca56
</span>Author: Rainer Müller <raimue@macports.org>
AuthorDate: Wed Nov 9 00:31:00 2016 +0100

<span style='display:block; white-space:pre;color:#404040;'>    trac-github-update: get user data from Trac
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    If GitHub supplied us with a username, try to find corresponding
</span><span style='display:block; white-space:pre;color:#404040;'>    metadata in Trac. If no data exists in Trac, query GitHub to expand the
</span><span style='display:block; white-space:pre;color:#404040;'>    real name.
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    Closes: https://trac.macports.org/ticket/52826
</span>---
 plugins/hooks/trac-github-update.config |   1 -
 plugins/hooks/trac-github-update.py     | 172 ++++++++++++++++++++++++++++----
 2 files changed, 152 insertions(+), 21 deletions(-)

<span style='display:block; white-space:pre;color:#808080;'>diff --git a/plugins/hooks/trac-github-update.config b/plugins/hooks/trac-github-update.config
</span><span style='display:block; white-space:pre;color:#808080;'>index d0ce957..20461a0 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/plugins/hooks/trac-github-update.config
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/plugins/hooks/trac-github-update.config
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -4,6 +4,5 @@
</span>   administrator = admin@macports.org
        mailingList = macports-changes@lists.macports.org
        replyTo = macports-dev@lists.macports.org
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        from = pusher
</span>   commitBrowseURL = https://github.com/macports/%(repo_shortname)s/commit/%(id)s
        commitEmailFormat = html
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/plugins/hooks/trac-github-update.py b/plugins/hooks/trac-github-update.py
</span><span style='display:block; white-space:pre;color:#808080;'>index f455541..95001a0 100755
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/plugins/hooks/trac-github-update.py
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/plugins/hooks/trac-github-update.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -8,8 +8,9 @@ import json
</span> 
 import git_multimail
 from git_multimail import GenericEnvironment, Config, ConfigurationException, \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                          OutputMailer, ReferenceChange, Push
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                          OutputMailer, ReferenceChange, Revision, Push
</span> from github import Github
<span style='display:block; white-space:pre;background:#e0ffe0;'>+import trac.env
</span> 
 
 ### TEMPLATES ###
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -50,10 +51,64 @@ git_multimail.LINK_HTML_TEMPLATE = """\
</span> 
 ### MAIN ###
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+class TracDB(object):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, tracenv):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.env = tracenv
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.cache = {}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get_user(self, username):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if username in self.cache:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.cache[username]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        row = self.env.db_query("""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            SELECT
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.value,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s2.value
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            FROM
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                session_attribute s1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            LEFT JOIN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                session_attribute s2
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ON
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.sid = s2.sid AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.authenticated = s2.authenticated AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.name = 'name' AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s2.name = 'email'
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            WHERE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.authenticated = 1 AND s2.authenticated = 1 AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.sid = %s AND s2.sid = %s AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s1.name = 'name' AND
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                s2.name = 'email'
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            """, (username, username))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        for name, email in row:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.cache[username] = (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.cache[username]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return (None, None)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class GitHubAPI(object):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, github):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.github = github
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.cache = {}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get_user(self, username):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if username in self.cache:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.cache[username]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        name = self.github.get_user(username).name
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # GitHub only lists the primary email address in the payload. We do not
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # want to expose it to the public. The profile may not have any public
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # address. Use a static sender email instead.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        email = "%s@users.noreply.github.com" % (username,)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if name:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.cache[username] = (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.cache[username]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return (None, None)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> 
 class GitHubWebhookEnvironment(GenericEnvironment):
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    def __init__(self, github, **kw):
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        self._github = github
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, github, tracenv, **kw):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._github = GitHubAPI(github)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._tracdb = TracDB(tracenv)
</span>         self._data = None
         self._pusher = None
         self._pusher_email = None
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -61,35 +116,102 @@ class GitHubWebhookEnvironment(GenericEnvironment):
</span> 
     def load_payload(self, payload):
         self._data = json.loads(payload)
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._commits = {}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        for commit in self._data['commits']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self._commits[commit['id']] = commit
</span>         return self._data
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _get_username(self, username):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        result = None
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Get name from Trac DB
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        name, _ = self._tracdb.get_user(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if name:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            result = "%s (%s)" % (name, username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # If user was not in Trac DB, ask GitHub API
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if not result:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            name, _ = self._github.get_user(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if name:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                result = "%s (%s)" % (name, username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            else:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                result = username
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return result
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _get_username_email(self, username):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        result = None
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Get name and email from Trac DB
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        name, email = self._tracdb.get_user(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if name and email:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            result = "%s <%s>" % (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # If user was not in Trac DB, ask GitHub API
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if not result:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            name, email = self._github.get_user(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if name:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                result = "%s <%s>" % (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            else:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                result = "%s <%s@users.noreply.github.com>" % (username, username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return result
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get_fromaddr(self, change=None):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if change:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if isinstance(change, ReferenceChange):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return self.get_pusher_email()
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            elif isinstance(change, Revision):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                author = None
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                commit = self._commits[change.rev.sha1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if 'username' in commit['author']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    username = commit['author']['username']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    author = self._get_username_email(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if not author:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    author = "%s <%s>" % (commit['author']['name'], commit['author']['email'])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return author.encode('utf-8')
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return super(GitHubWebhookEnvironment, self).get_fromaddr(change)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     def get_pusher(self):
         if self._pusher:
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            return self._pusher
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self._pusher.encode('utf-8')
</span>         if not self._data:
             return super(GitHubWebhookEnvironment, self).get_pusher()
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        login = self._data['pusher']['name']
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        realname = self._github.get_user(login).name
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        if realname:
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            self._pusher = "%s (%s)" % (realname, login)
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        else:
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            self._pusher = login
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        username = self._data['pusher']['name']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._pusher = self._get_username(username)
</span>         return self._pusher.encode('utf-8')
 
     def get_pusher_email(self):
         if self._pusher_email:
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            return self._pusher_email
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self._pusher_email.encode('utf-8')
</span>         if not self._data:
             return super(GitHubWebhookEnvironment, self).get_pusher_email()
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        login = self._data['pusher']['name']
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        name = self._github.get_user(login).name or login
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        # GitHub only lists the primary email address in the payload. We do not
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        # want to expose it to the public, and sending with these addresses
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        # would also violate DMARC. Use a static sender email instead.
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        email = "%s@users.noreply.github.com" % (login,)
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        self._pusher_email = "%s <%s>" % (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        username = self._data['pusher']['name']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._pusher_email = self._get_username_email(username)
</span>         return self._pusher_email.encode('utf-8')
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get_reply_to_refchange(self, refchange):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        reply_to = []
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        reply_to_extra = self.config.get('replyTo')
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if reply_to_extra:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            reply_to.append(reply_to_extra)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return ", ".join(reply_to).encode('utf-8')
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get_reply_to_commit(self, revision):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        reply_to = []
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        reply_to_extra = self.config.get('replyTo')
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        reply_to_committer = None
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        commit = self._commits[revision.rev.sha1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        author = commit['author']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        committer = commit['committer']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if 'username' in author and 'username' in committer:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if author['username'] != committer['username']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                username = commit['committer']['username']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                reply_to_committer = self._get_username_email(username)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        else:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if author['email'] != committer['email']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                name = commit['committer']['name']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                email = commit['committer']['email']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                reply_to_committer = "%s <%s>" % (name, email)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if reply_to_committer:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            reply_to.append(reply_to_committer)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if reply_to_extra:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            reply_to.append(reply_to_extra)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return ", ".join(reply_to).encode('utf-8')
</span> 
 def run_as_github_webhook(environment, mailer):
     payload = environment.load_payload(sys.stdin.read())
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -123,6 +245,7 @@ def main(args):
</span>     # git-multimail:
     config = Config('multimailhook')
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    # GitHub API
</span>     token = os.getenv("GITHUB_ACCESS_TOKEN")
     if not token:
         token = config.get("githubAccessToken")
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -130,12 +253,21 @@ def main(args):
</span>         sys.stderr.write("Set GITHUB_ACCESS_TOKEN in environment or " +
                          "'git config multimailhook.githubAccessToken <token>'!\n")
         sys.exit(1)
<span style='display:block; white-space:pre;background:#ffe0e0;'>-
</span>     github = Github(token)
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    # Trac Environment
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    tracenvpath = os.getenv("TRAC_ENV")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if not tracenvpath:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        tracenvpath = config.get("tracEnv")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if not tracenvpath:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        sys.stderr.write("Set TRAC_ENV in environment or " +
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                         "'git config multimailhook.tracEnv <path>'!\n")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        sys.exit(1)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    tracenv = trac.env.Environment(path=tracenvpath, create=False)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     # Select the type of environment:
     try:
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        environment = GitHubWebhookEnvironment(github, config=config)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        environment = GitHubWebhookEnvironment(github, tracenv, config=config)
</span>     except ConfigurationException:
         sys.stderr.write("%s\n" % sys.exc_info()[1])
         sys.exit(1)
</pre><pre style='margin:0'>

</pre>