|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import pygit2 |
| 4 | +import re |
| 5 | +import argparse |
| 6 | +import sys |
| 7 | + |
| 8 | + |
| 9 | +def find_unpicked(repo, from_commit, to_commit, since_commit, show_all): |
| 10 | + base_id = repo.merge_base(from_commit.id, to_commit.id) |
| 11 | + |
| 12 | + cherrypick_re = re.compile('(cherry picked from commit|with child) ([0-9a-fA-F]+)') |
| 13 | + cherrypicked_commits = set() |
| 14 | + |
| 15 | + for commit in repo.walk(to_commit.id, pygit2.GIT_SORT_TOPOLOGICAL): |
| 16 | + if commit.id == base_id: |
| 17 | + break |
| 18 | + |
| 19 | + for match in cherrypick_re.findall(commit.message): |
| 20 | + cherrypicked_commits.add(match[1]) |
| 21 | + |
| 22 | + user_name = repo.config.get_multivar('user.name').next() |
| 23 | + user_email = repo.config.get_multivar('user.email').next() |
| 24 | + |
| 25 | + for commit in repo.walk(from_commit.id, pygit2.GIT_SORT_TOPOLOGICAL): |
| 26 | + # we walk from newest commits to oldest |
| 27 | + if commit.id == base_id: |
| 28 | + break |
| 29 | + |
| 30 | + if str(commit.id) not in cherrypicked_commits and \ |
| 31 | + (show_all or commit.author.name == user_name or |
| 32 | + commit.author.email == user_email): |
| 33 | + yield(commit) |
| 34 | + |
| 35 | + if since_commit and commit.id == since_commit.id: |
| 36 | + break |
| 37 | + |
| 38 | +parser = argparse.ArgumentParser(description='Show commits, eligible for cherry-picking') |
| 39 | +parser.add_argument('branch', metavar='BRANCH', help='Name for the branch to check against') |
| 40 | +parser.add_argument('target_branch', metavar='TARGET_BRANCH', help='Name for the target branch', |
| 41 | + default='HEAD', nargs='?') |
| 42 | +parser.add_argument('--since', metavar='COMMIT', help='Start checking since specified commit') |
| 43 | +parser.add_argument('--all', action='store_true', help='Show commits from all users') |
| 44 | + |
| 45 | +args = parser.parse_args() |
| 46 | +repo = pygit2.Repository('.') |
| 47 | + |
| 48 | +try: |
| 49 | + from_commit = repo.revparse_single(args.branch) |
| 50 | +except: |
| 51 | + print('Invalid branch %s' % args.branch) |
| 52 | + sys.exit(1) |
| 53 | + |
| 54 | +try: |
| 55 | + to_commit = repo.revparse_single(args.target_branch) |
| 56 | +except: |
| 57 | + print('Invalid target branch %s' % args.target_branch) |
| 58 | + sys.exit(1) |
| 59 | + |
| 60 | +if not repo.merge_base(from_commit.id, to_commit.id): |
| 61 | + print('%s and %s does not have common ancestor' % (args.branch, args.target_branch)) |
| 62 | + sys.exit(1) |
| 63 | + |
| 64 | +since_commit = None |
| 65 | +if args.since: |
| 66 | + try: |
| 67 | + since_commit = repo.revparse_single(args.since) |
| 68 | + except: |
| 69 | + print('Invalid since %s' % args.since) |
| 70 | + sys.exit(1) |
| 71 | + |
| 72 | +author_format_str = ' | %s <%s>' |
| 73 | +commit_format_str = '%s %s%s' |
| 74 | + |
| 75 | +if sys.stdout.isatty(): |
| 76 | + author_format_str = ' \033[31m| %s <%s>\033[0m' |
| 77 | + commit_format_str = '\033[33m%s\033[0m %s%s' |
| 78 | + |
| 79 | +for commit in find_unpicked(repo, from_commit, to_commit, since_commit, args.all): |
| 80 | + author_info = '' |
| 81 | + if args.all: |
| 82 | + author_info = author_format_str % (commit.author.name, commit.author.email) |
| 83 | + |
| 84 | + print(commit_format_str % (str(commit.id), commit.message[:commit.message.index('\n')], |
| 85 | + author_info)) |
0 commit comments