rbtools.hooks.git
1
from collections import defaultdict
2
from copy import deepcopy
3
4
from rbtools.hooks.common import execute, get_review_request_id
5
6
7
def get_branch_name(ref_name):
8
    """Returns the branch name corresponding to the specified ref name."""
9
    branch_ref_prefix = 'refs/heads/'
10
11
    if ref_name.startswith(branch_ref_prefix):
12
        return ref_name[len(branch_ref_prefix):]
13
14
def get_commit_hashes(old_rev, new_rev):
15
    """Returns a list of abbreviated commit hashes from old_rev to new_rev."""
16
    git_command = ('git rev-list --abbrev-commit --reverse %s..%s'
17
                   % (old_rev, new_rev))
18
    return execute(git_command).split('\n')
19
20
def get_unique_commit_hashes(ref_name):
21
    """Returns a list of abbreviated commit hashes unique to ref_name."""
22
    excluded_branches = get_excluded_branches(ref_name)
23
    git_command = ('git rev-list %s --abbrev-commit --reverse --not %s'
24
                   % (ref_name, excluded_branches))
25
    return execute(git_command).strip().split('\n')
26
27
def get_excluded_branches(ref_name):
28
    """Returns a string with all branches, excluding the specified branch."""
29
    git_command = ('git for-each-ref refs/heads/ --format="%%(refname)" '
30
                   '| grep -v %s' % ref_name)
31
    return execute(git_command).replace('\n', ' ')
32
33
def get_branches_containing_commit(commit_hash):
34
    """Returns a list of all branches containing the specified commit."""
35
    git_command = ('git branch --contains %s' % commit_hash)
36
    branches = execute(git_command).replace('*', '').split('\n')
37
    return [branch.strip() for branch in branches]
38
39
def get_commit_message(commit):
40
    """Returns the specified commit's commit message."""
41
    git_command = ('git show -s --pretty=format:"%%B" %s' % commit)
42
    return execute(git_command).strip()
43
44
def get_review_id_to_commits_map(lines, regex):
45
    """Returns a dictionary, mapping a review request ID to a list of commits.
46
47
    The commits must be in the form: oldrev newrev refname (separated by
48
    newlines), as given by a Git pre-receive or post-receive hook.
49
50
    If a commit's commit message does not contain a review request ID, we append
51
    the commit to the key '0'.
52
    """
53
    review_id_to_commits_map = defaultdict(list)
54
55
    # Store a list of new branches (which have an all-zero old_rev value)
56
    # created in this push to handle them specially.
57
    new_branches = []
58
    null_sha1 = '0000000000000000000000000000000000000000'
59
60
    for line in lines:
61
        old_rev, new_rev, ref_name = line.split()
62
        branch_name = get_branch_name(ref_name)
63
64
        if not branch_name or new_rev == null_sha1:
65
            continue
66
67
        if old_rev == null_sha1:
68
            new_branches.append(branch_name)
69
            commit_hashes = get_unique_commit_hashes(ref_name)
70
        else:
71
            commit_hashes = get_commit_hashes(old_rev, new_rev)
72
73
        for commit_hash in commit_hashes:
74
75
            if commit_hash:
76
                commit_message = get_commit_message(commit_hash)
77
                review_request_id = get_review_request_id(regex, commit_message)
78
79
                if not review_request_id:
80
                    review_request_id = '0'
81
82
                commit = '%s (%s)' % (branch_name, commit_hash)
83
                review_id_to_commits_map[review_request_id].append(commit)
84
85
    # If there are new branches, check every commit in the dictionary
86
    # (corresponding to only old branches) to see if the new branches also
87
    # contain that commit.
88
    if new_branches:
89
        review_id_to_commits_map_copy = deepcopy(review_id_to_commits_map)
90
91
        for review_id, commit_list in review_id_to_commits_map_copy.iteritems():
92
93
            for commit in commit_list:
94
                commit_branch = commit[:commit.find('(') - 1]
95
96
                if commit_branch in new_branches:
97
                    continue
98
99
                commit_hash = commit[commit.find('(') + 1:-1]
100
                commit_branches = get_branches_containing_commit(commit_hash)
101
102
                for branch in set(new_branches).intersection(commit_branches):
103
                    new_commit = '%s (%s)' % (branch, commit_hash)
104
                    review_id_to_commits_map[review_id].append(new_commit)
105
106
    return review_id_to_commits_map
Loading...