summaryrefslogtreecommitdiffstats
path: root/roles/git/hooks/files/post-receive-fedmsg
blob: 3545be2e3bc3b4e3cd785e99d78351fda6a81ae9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env python

import getpass
import os
import subprocess as sp
import sys

from collections import defaultdict

import pygit2

import fedmsg
import fedmsg.config

# Use $GIT_DIR to determine where this repo is.
abspath = os.path.abspath(os.environ['GIT_DIR'])
# This assumes git root dir is named "repo_name.git"
repo_name = '.'.join(abspath.split(os.path.sep)[-1].split('.')[:-1])
namespace = abspath.split(os.path.sep)[-2]

username = getpass.getuser()

repo = pygit2.Repository(abspath)

print "Emitting a message to the fedmsg bus."
config = fedmsg.config.load_config([], None)
config['active'] = True
config['endpoints']['relay_inbound'] = config['relay_inbound']
fedmsg.init(name='relay_inbound', cert_prefix='scm', **config)


def revs_between(head, base):
    """ Yield revisions between HEAD and BASE. """

    # pygit2 can't do a rev-list yet, so we have to shell out.. silly.
    cmd = '/usr/bin/git rev-list %s...%s' % (head.id, base.id)
    proc = sp.Popen(cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE, cwd=abspath)
    stdout, stderr = proc.communicate()
    if proc.returncode != 0:
        raise IOError('git rev-list failed: %r, err: %r' % (stdout, stderr))

    for line in stdout.strip().split('\n'):
        yield line.strip()


def build_stats(commit):
    files = defaultdict(lambda: defaultdict(int))

    # Calculate diffs against all parent commits
    diffs = [repo.diff(parent, commit) for parent in commit.parents]
    # Unless this is the first commit, with no parents.
    diffs = diffs or [commit.tree.diff_to_tree(swap=True)]

    for diff in diffs:
        for patch in diff:
            if hasattr(patch, 'new_file_path'):
                path = patch.new_file_path
            else:
                path = patch.delta.new_file.path

            if hasattr(patch, 'additions'):
                files[path]['additions'] += patch.additions
                files[path]['deletions'] += patch.deletions
                files[path]['lines'] += patch.additions + patch.deletions
            else:
                files[path]['additions'] += patch.line_stats[1]
                files[path]['deletions'] += patch.line_stats[2]
                files[path]['lines'] += patch.line_stats[1] \
                                        + patch.line_stats[2]

    total = defaultdict(int)
    for name, stats in files.items():
        total['additions'] += stats['additions']
        total['deletions'] += stats['deletions']
        total['lines'] += stats['lines']
        total['files'] += 1

    return files, total


seen = []

# Read in all the rev information git-receive-pack hands us.
lines = [line.split() for line in sys.stdin.readlines()]
for line in lines:
    base, head, branch = line
    branch = '/'.join(branch.split('/')[2:])

    try:
        head = repo.revparse_single(head)
    except KeyError:
        # This means they are deleting this branch.. and we don't have a fedmsg
        # for that (yet?).  It is disallowed by dist-git in Fedora anyways.
        continue

    try:
        base = repo.revparse_single(base)
        revs = revs_between(head, base)
    except KeyError:
        revs = [head.id]

    def _build_commit(rev):
        commit = repo.revparse_single(unicode(rev))

        # Tags are a little funny, and vary between versions of pygit2, so we'll
        # just ignore them as far as fedmsg is concerned.
        if isinstance(commit, pygit2.Tag):
            return None

        files, total = build_stats(commit)

        return dict(
            name=commit.author.name,
            email=commit.author.email,
            username=username,
            summary=commit.message.split('\n')[0],
            message=commit.message,
            stats=dict(
                files=files,
                total=total,
            ),
            rev=unicode(rev),
            path=abspath,
            repo=repo_name,
            namespace=namespace,
            branch=branch,
            agent=os.getlogin(),
        )

    commits = map(_build_commit, revs)

    print "* Publishing information for %i commits" % len(commits)
    for commit in reversed(commits):
        if commit is None:
            continue

        # Keep track of whether or not we have already published this commit on
        # another branch or not.  It is conceivable that someone could make a
        # commit to a number of branches, and push them all at the same time.
        # Make a note in the fedmsg payload so we can try to reduce spam at a
        # later stage.
        if commit['rev'] in seen:
            commit['seen'] = True
        else:
            commit['seen'] = False
            seen.append(commit['rev'])

        fedmsg.publish(
            topic="receive",
            msg=dict(commit=commit),
            modname="git",
        )