--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com>
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
+
+'''
+A tool to create/update the mailmap file
+
+The command 'git shortlog' summarizes git log output in a format suitable
+for inclusion in release announcements. Each commit will be grouped by
+author and title.
+
+One problem is that the authors' name and/or email address is sometimes
+spelled differently. The .mailmap feature can be used to coalesce together
+commits by the same persion.
+(See 'man git-shortlog' for furthur information of this feature.)
+
+This tool helps to create/update the mailmap file.
+
+It runs 'git shortlog' internally and searches differently spelled author
+names which share the same email address. The author name with the most
+commits is asuumed to be a canonical real name. If the number of commits
+from the cananonical name is equal to or greater than 'MIN_COMMITS',
+the entry for the cananical name will be output. ('MIN_COMMITS' is used
+here because we do not want to create a fat mailmap by adding every author
+with only a few commits.)
+
+If there exists a mailmap file specified by the mailmap.file configuration
+options or '.mailmap' at the toplevel of the repository, it is used as
+a base file. (The mailmap.file configuration takes precedence over the
+'.mailmap' file if both exist.)
+
+The base file and the newly added entries are merged together and sorted
+alphabetically (but the comment block is kept untouched), and then printed
+to standard output.
+
+Usage
+-----
+
+ scripts/mailmapper
+
+prints the mailmapping to standard output.
+
+ scripts/mailmapper > tmp; mv tmp .mailmap
+
+will be useful for updating '.mailmap' file.
+'''
+
+import sys
+import os
+import subprocess
+
+# The entries only for the canonical names with MIN_COMMITS or more commits.
+# This limitation is used so as not to create a too big mailmap file.
+MIN_COMMITS = 50
+
+try:
+ toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'])
+except subprocess.CalledProcessError:
+ print >> sys.stderr, 'Please run in a git repository.'
+ sys.exit(1)
+
+# strip '\n'
+toplevel = toplevel.rstrip()
+
+# Change the current working directory to the toplevel of the respository
+# for our easier life.
+os.chdir(toplevel)
+
+# First, create 'auther name' vs 'number of commits' database.
+# We assume the name with the most commits as the canonical real name.
+shortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n'])
+
+commits_per_name = {}
+
+for line in shortlog.splitlines():
+ try:
+ commits, name = line.split(None, 1)
+ except ValueError:
+ # ignore lines with an empty author name
+ pass
+ commits_per_name[name] = int(commits)
+
+# Next, coalesce the auther names with the same email address
+shortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n', '-e'])
+
+mail_vs_name = {}
+output = {}
+
+for line in shortlog.splitlines():
+ # tmp, mail = line.rsplit(None, 1) is not safe
+ # because weird email addresses might include whitespaces
+ tmp, mail = line.split('<')
+ mail = '<' + mail.rstrip()
+ try:
+ _, name = tmp.rstrip().split(None, 1)
+ except ValueError:
+ # author name is empty
+ name = ''
+ if mail in mail_vs_name:
+ # another name for the same email address
+ prev_name = mail_vs_name[mail]
+ # Take the name with more commits
+ major_name = sorted([prev_name, name],
+ key=lambda x: commits_per_name[x] if x else 0)[1]
+ mail_vs_name[mail] = major_name
+ if commits_per_name[major_name] > MIN_COMMITS:
+ output[mail] = major_name
+ else:
+ mail_vs_name[mail] = name
+
+# [1] If there exists a mailmap file at the location pointed to
+# by the mailmap.file configuration option, update it.
+# [2] If the file .mailmap exists at the toplevel of the repository, update it.
+# [3] Otherwise, create a new mailmap file.
+mailmap_files = []
+
+try:
+ config_mailmap = subprocess.check_output(['git', 'config', 'mailmap.file'])
+except subprocess.CalledProcessError:
+ config_mailmap = ''
+
+config_mailmap = config_mailmap.rstrip()
+if config_mailmap:
+ mailmap_files.append(config_mailmap)
+
+mailmap_files.append('.mailmap')
+
+infile = None
+
+for map_file in mailmap_files:
+ try:
+ infile = open(map_file)
+ except:
+ # Failed to open. Try next.
+ continue
+ break
+
+comment_block = []
+output_lines = []
+
+if infile:
+ for line in infile:
+ if line[0] == '#' or line[0] == '\n':
+ comment_block.append(line)
+ else:
+ output_lines.append(line)
+ break
+ for line in infile:
+ output_lines.append(line)
+ infile.close()
+
+for mail, name in output.items():
+ output_lines.append(name + ' ' + mail + '\n')
+
+output_lines.sort()
+
+sys.stdout.write(''.join(comment_block + output_lines))