#!/usr/bin/env python
import sys, codecs
import pygit2 as git
import re
from distutils.version import StrictVersion

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

patterns = [
        r'reverts commit (?P<reverts>[0-9a-f]{8,40})',
        r'^fixes: (?P<fixes>[0-9a-f]{8,40}) \("(?P<fixes_subject>.*)"\)',
        r'[Cc]ommit (?P<upstream>[0-9a-fA-F]{40})',
        r'^Cc: (?P<stable>.*stable@(?:vger.kernel.org|kernel.org)[ >#\[\(v]*(?P<stable_first>[0-9]+\.[0-9]+(?:\.[0-9]+)?)?.*)$',
        r'(?P<CVE>CVE-[0-9]+-[0-9]+)',
]

matcher = re.compile('|'.join(patterns), flags = re.IGNORECASE | re.MULTILINE)

repo = git.Repository(".git")
upstream_head = repo.revparse_single('upstream/master')
branch_head = repo[repo.head.target]
branch_base = repo[repo.merge_base(branch_head.id, upstream_head.id)]
branch_time = branch_base.commit_time
branch_name = repo.describe(branch_base, abbreviated_size=0).lstrip('v')
branch_version = StrictVersion(branch_name)

# upsteam commits backported into branch
backported_commits = set()
walker = repo.walk(branch_head.id)
walker.hide(branch_base.id)
for commit in walker:
    for match in matcher.finditer(commit.message):
        upstream_id = match.group('upstream')
        if upstream_id is not None:
            upstream_id = repo.expand_id(upstream_id)
            backported_commits.add(upstream_id.raw)

print 'Backported', len(backported_commits)
print

# unreachable upstream commits commited before branching
unreachable_commits = set()
walker = repo.walk(upstream_head.id)
walker.hide(branch_base.id)
for commit in walker:
    if commit.commit_time < branch_time:
        unreachable_commits.add(commit.id.raw)

print 'Unreachable', len(unreachable_commits)
print

prev_commit=None

def report(commit, message, related_id=None, related_subject=None):
    if commit.id.raw in backported_commits:
        return # already backported
    relation = ''
    related_commit = None
    if related_id is not None:
        try:
            related_commit = repo[repo.expand_id(related_id)]
            if related_commit.type == git.GIT_OBJ_COMMIT:
                related_id = related_commit.id
                related_subject = related_commit.message.split('\n', 2)[0]
            else:
                related_commit = None
        except KeyError:
            pass
        if related_commit is None:
            relation = 'unknown   '
        elif related_commit.id.raw in backported_commits:
            relation = 'backported'
        elif related_commit.commit_time > branch_time:
            return # commit after braching
        elif related_commit.id.raw in unreachable_commits:
            return # unreachable commit
        else:
            relation = 'downstream'

    global prev_commit
    if prev_commit != commit.id:
        if prev_commit is not None:
            print
        print 'Upstream commit', '\t', commit.id, commit.message.split('\n', 2)[0]
        prev_commit = commit.id

    print message, relation, '\t',
    if related_id is not None:
        print related_id, related_subject,
    print

walker = repo.walk(upstream_head.id)
walker.hide(branch_base.id)
for commit in walker:
    if len(commit.parent_ids) > 1:
        continue # merge commit
    for match in matcher.finditer(commit.message):
        fixes = match.group('fixes')
        fixes_subject = match.group('fixes_subject')
        if fixes is not None:
            report(commit, 'Fixes', fixes, fixes_subject)
        reverts = match.group('reverts')
        if reverts is not None:
            report(commit, 'Reverts', reverts)
        cve = match.group('CVE')
        if cve is not None:
            report(commit, cve)
        stable = match.group('stable')
        if stable is not None:
            stable_first = match.group('stable_first')
            if stable_first is None:
                report(commit, 'Stable ' + stable)
            elif StrictVersion(stable_first) <= branch_version:
                report(commit, 'Stable ' + stable)
            else:
                report(commit, 'Later stable ' + stable)
