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