xref: /openbmc/u-boot/scripts/mailmapper (revision 3eceff64)
194b13bbaSMasahiro Yamada#!/usr/bin/env python2
2*6bfa0367SHeinrich Schuchardt# SPDX-License-Identifier: GPL-2.0+
345765eedSMasahiro Yamada#
445765eedSMasahiro Yamada# Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com>
545765eedSMasahiro Yamada
645765eedSMasahiro Yamada'''
745765eedSMasahiro YamadaA tool to create/update the mailmap file
845765eedSMasahiro Yamada
945765eedSMasahiro YamadaThe command 'git shortlog' summarizes git log output in a format suitable
1045765eedSMasahiro Yamadafor inclusion in release announcements. Each commit will be grouped by
1145765eedSMasahiro Yamadaauthor and title.
1245765eedSMasahiro Yamada
1345765eedSMasahiro YamadaOne problem is that the authors' name and/or email address is sometimes
1445765eedSMasahiro Yamadaspelled differently. The .mailmap feature can be used to coalesce together
1545765eedSMasahiro Yamadacommits by the same persion.
1645765eedSMasahiro Yamada(See 'man git-shortlog' for furthur information of this feature.)
1745765eedSMasahiro Yamada
1845765eedSMasahiro YamadaThis tool helps to create/update the mailmap file.
1945765eedSMasahiro Yamada
2045765eedSMasahiro YamadaIt runs 'git shortlog' internally and searches differently spelled author
2145765eedSMasahiro Yamadanames which share the same email address. The author name with the most
2245765eedSMasahiro Yamadacommits is asuumed to be a canonical real name. If the number of commits
2345765eedSMasahiro Yamadafrom the cananonical name is equal to or greater than 'MIN_COMMITS',
2445765eedSMasahiro Yamadathe entry for the cananical name will be output. ('MIN_COMMITS' is used
2545765eedSMasahiro Yamadahere because we do not want to create a fat mailmap by adding every author
2645765eedSMasahiro Yamadawith only a few commits.)
2745765eedSMasahiro Yamada
2845765eedSMasahiro YamadaIf there exists a mailmap file specified by the mailmap.file configuration
2945765eedSMasahiro Yamadaoptions or '.mailmap' at the toplevel of the repository, it is used as
3045765eedSMasahiro Yamadaa base file. (The mailmap.file configuration takes precedence over the
3145765eedSMasahiro Yamada'.mailmap' file if both exist.)
3245765eedSMasahiro Yamada
3345765eedSMasahiro YamadaThe base file and the newly added entries are merged together and sorted
3445765eedSMasahiro Yamadaalphabetically (but the comment block is kept untouched), and then printed
3545765eedSMasahiro Yamadato standard output.
3645765eedSMasahiro Yamada
3745765eedSMasahiro YamadaUsage
3845765eedSMasahiro Yamada-----
3945765eedSMasahiro Yamada
4045765eedSMasahiro Yamada  scripts/mailmapper
4145765eedSMasahiro Yamada
4245765eedSMasahiro Yamadaprints the mailmapping to standard output.
4345765eedSMasahiro Yamada
4445765eedSMasahiro Yamada  scripts/mailmapper > tmp; mv tmp .mailmap
4545765eedSMasahiro Yamada
4645765eedSMasahiro Yamadawill be useful for updating '.mailmap' file.
4745765eedSMasahiro Yamada'''
4845765eedSMasahiro Yamada
4945765eedSMasahiro Yamadaimport sys
5045765eedSMasahiro Yamadaimport os
5145765eedSMasahiro Yamadaimport subprocess
5245765eedSMasahiro Yamada
5345765eedSMasahiro Yamada# The entries only for the canonical names with MIN_COMMITS or more commits.
5445765eedSMasahiro Yamada# This limitation is used so as not to create a too big mailmap file.
5545765eedSMasahiro YamadaMIN_COMMITS = 50
5645765eedSMasahiro Yamada
5745765eedSMasahiro Yamadatry:
5845765eedSMasahiro Yamada    toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'])
5945765eedSMasahiro Yamadaexcept subprocess.CalledProcessError:
6031e2141dSMasahiro Yamada    sys.exit('Please run in a git repository.')
6145765eedSMasahiro Yamada
6245765eedSMasahiro Yamada# strip '\n'
6345765eedSMasahiro Yamadatoplevel = toplevel.rstrip()
6445765eedSMasahiro Yamada
6545765eedSMasahiro Yamada# Change the current working directory to the toplevel of the respository
6645765eedSMasahiro Yamada# for our easier life.
6745765eedSMasahiro Yamadaos.chdir(toplevel)
6845765eedSMasahiro Yamada
6945765eedSMasahiro Yamada# First, create 'auther name' vs 'number of commits' database.
7045765eedSMasahiro Yamada# We assume the name with the most commits as the canonical real name.
7145765eedSMasahiro Yamadashortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n'])
7245765eedSMasahiro Yamada
7345765eedSMasahiro Yamadacommits_per_name = {}
7445765eedSMasahiro Yamada
7545765eedSMasahiro Yamadafor line in shortlog.splitlines():
7645765eedSMasahiro Yamada    try:
7745765eedSMasahiro Yamada        commits, name = line.split(None, 1)
7845765eedSMasahiro Yamada    except ValueError:
7945765eedSMasahiro Yamada        # ignore lines with an empty author name
8045765eedSMasahiro Yamada        pass
8145765eedSMasahiro Yamada    commits_per_name[name] = int(commits)
8245765eedSMasahiro Yamada
8345765eedSMasahiro Yamada# Next, coalesce the auther names with the same email address
8445765eedSMasahiro Yamadashortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n', '-e'])
8545765eedSMasahiro Yamada
8645765eedSMasahiro Yamadamail_vs_name = {}
8745765eedSMasahiro Yamadaoutput = {}
8845765eedSMasahiro Yamada
8945765eedSMasahiro Yamadafor line in shortlog.splitlines():
9045765eedSMasahiro Yamada    # tmp, mail = line.rsplit(None, 1) is not safe
9145765eedSMasahiro Yamada    # because weird email addresses might include whitespaces
9245765eedSMasahiro Yamada    tmp, mail = line.split('<')
9345765eedSMasahiro Yamada    mail = '<' + mail.rstrip()
9445765eedSMasahiro Yamada    try:
9545765eedSMasahiro Yamada        _, name = tmp.rstrip().split(None, 1)
9645765eedSMasahiro Yamada    except ValueError:
9745765eedSMasahiro Yamada        # author name is empty
9845765eedSMasahiro Yamada        name = ''
9945765eedSMasahiro Yamada    if mail in mail_vs_name:
10045765eedSMasahiro Yamada        # another name for the same email address
10145765eedSMasahiro Yamada        prev_name = mail_vs_name[mail]
10245765eedSMasahiro Yamada        # Take the name with more commits
10345765eedSMasahiro Yamada        major_name = sorted([prev_name, name],
10445765eedSMasahiro Yamada                            key=lambda x: commits_per_name[x] if x else 0)[1]
10545765eedSMasahiro Yamada        mail_vs_name[mail] = major_name
10645765eedSMasahiro Yamada        if commits_per_name[major_name] > MIN_COMMITS:
10745765eedSMasahiro Yamada            output[mail] = major_name
10845765eedSMasahiro Yamada    else:
10945765eedSMasahiro Yamada        mail_vs_name[mail] = name
11045765eedSMasahiro Yamada
11145765eedSMasahiro Yamada# [1] If there exists a mailmap file at the location pointed to
11245765eedSMasahiro Yamada#     by the mailmap.file configuration option, update it.
11345765eedSMasahiro Yamada# [2] If the file .mailmap exists at the toplevel of the repository, update it.
11445765eedSMasahiro Yamada# [3] Otherwise, create a new mailmap file.
11545765eedSMasahiro Yamadamailmap_files = []
11645765eedSMasahiro Yamada
11745765eedSMasahiro Yamadatry:
11845765eedSMasahiro Yamada    config_mailmap = subprocess.check_output(['git', 'config', 'mailmap.file'])
11945765eedSMasahiro Yamadaexcept subprocess.CalledProcessError:
12045765eedSMasahiro Yamada    config_mailmap = ''
12145765eedSMasahiro Yamada
12245765eedSMasahiro Yamadaconfig_mailmap = config_mailmap.rstrip()
12345765eedSMasahiro Yamadaif config_mailmap:
12445765eedSMasahiro Yamada    mailmap_files.append(config_mailmap)
12545765eedSMasahiro Yamada
12645765eedSMasahiro Yamadamailmap_files.append('.mailmap')
12745765eedSMasahiro Yamada
12845765eedSMasahiro Yamadainfile = None
12945765eedSMasahiro Yamada
13045765eedSMasahiro Yamadafor map_file in mailmap_files:
13145765eedSMasahiro Yamada    try:
13245765eedSMasahiro Yamada        infile = open(map_file)
13345765eedSMasahiro Yamada    except:
13445765eedSMasahiro Yamada        # Failed to open. Try next.
13545765eedSMasahiro Yamada        continue
13645765eedSMasahiro Yamada    break
13745765eedSMasahiro Yamada
13845765eedSMasahiro Yamadacomment_block = []
13945765eedSMasahiro Yamadaoutput_lines = []
14045765eedSMasahiro Yamada
14145765eedSMasahiro Yamadaif infile:
14245765eedSMasahiro Yamada    for line in infile:
14345765eedSMasahiro Yamada        if line[0] == '#' or line[0] == '\n':
14445765eedSMasahiro Yamada            comment_block.append(line)
14545765eedSMasahiro Yamada        else:
14645765eedSMasahiro Yamada            output_lines.append(line)
14745765eedSMasahiro Yamada            break
14845765eedSMasahiro Yamada    for line in infile:
14945765eedSMasahiro Yamada        output_lines.append(line)
15045765eedSMasahiro Yamada    infile.close()
15145765eedSMasahiro Yamada
15245765eedSMasahiro Yamadafor mail, name in output.items():
15345765eedSMasahiro Yamada    output_lines.append(name + ' ' + mail + '\n')
15445765eedSMasahiro Yamada
15545765eedSMasahiro Yamadaoutput_lines.sort()
15645765eedSMasahiro Yamada
15745765eedSMasahiro Yamadasys.stdout.write(''.join(comment_block + output_lines))
158