1*45765eedSMasahiro Yamada#!/usr/bin/env python 2*45765eedSMasahiro Yamada# 3*45765eedSMasahiro Yamada# Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com> 4*45765eedSMasahiro Yamada# 5*45765eedSMasahiro Yamada# SPDX-License-Identifier: GPL-2.0+ 6*45765eedSMasahiro Yamada# 7*45765eedSMasahiro Yamada 8*45765eedSMasahiro Yamada''' 9*45765eedSMasahiro YamadaA tool to create/update the mailmap file 10*45765eedSMasahiro Yamada 11*45765eedSMasahiro YamadaThe command 'git shortlog' summarizes git log output in a format suitable 12*45765eedSMasahiro Yamadafor inclusion in release announcements. Each commit will be grouped by 13*45765eedSMasahiro Yamadaauthor and title. 14*45765eedSMasahiro Yamada 15*45765eedSMasahiro YamadaOne problem is that the authors' name and/or email address is sometimes 16*45765eedSMasahiro Yamadaspelled differently. The .mailmap feature can be used to coalesce together 17*45765eedSMasahiro Yamadacommits by the same persion. 18*45765eedSMasahiro Yamada(See 'man git-shortlog' for furthur information of this feature.) 19*45765eedSMasahiro Yamada 20*45765eedSMasahiro YamadaThis tool helps to create/update the mailmap file. 21*45765eedSMasahiro Yamada 22*45765eedSMasahiro YamadaIt runs 'git shortlog' internally and searches differently spelled author 23*45765eedSMasahiro Yamadanames which share the same email address. The author name with the most 24*45765eedSMasahiro Yamadacommits is asuumed to be a canonical real name. If the number of commits 25*45765eedSMasahiro Yamadafrom the cananonical name is equal to or greater than 'MIN_COMMITS', 26*45765eedSMasahiro Yamadathe entry for the cananical name will be output. ('MIN_COMMITS' is used 27*45765eedSMasahiro Yamadahere because we do not want to create a fat mailmap by adding every author 28*45765eedSMasahiro Yamadawith only a few commits.) 29*45765eedSMasahiro Yamada 30*45765eedSMasahiro YamadaIf there exists a mailmap file specified by the mailmap.file configuration 31*45765eedSMasahiro Yamadaoptions or '.mailmap' at the toplevel of the repository, it is used as 32*45765eedSMasahiro Yamadaa base file. (The mailmap.file configuration takes precedence over the 33*45765eedSMasahiro Yamada'.mailmap' file if both exist.) 34*45765eedSMasahiro Yamada 35*45765eedSMasahiro YamadaThe base file and the newly added entries are merged together and sorted 36*45765eedSMasahiro Yamadaalphabetically (but the comment block is kept untouched), and then printed 37*45765eedSMasahiro Yamadato standard output. 38*45765eedSMasahiro Yamada 39*45765eedSMasahiro YamadaUsage 40*45765eedSMasahiro Yamada----- 41*45765eedSMasahiro Yamada 42*45765eedSMasahiro Yamada scripts/mailmapper 43*45765eedSMasahiro Yamada 44*45765eedSMasahiro Yamadaprints the mailmapping to standard output. 45*45765eedSMasahiro Yamada 46*45765eedSMasahiro Yamada scripts/mailmapper > tmp; mv tmp .mailmap 47*45765eedSMasahiro Yamada 48*45765eedSMasahiro Yamadawill be useful for updating '.mailmap' file. 49*45765eedSMasahiro Yamada''' 50*45765eedSMasahiro Yamada 51*45765eedSMasahiro Yamadaimport sys 52*45765eedSMasahiro Yamadaimport os 53*45765eedSMasahiro Yamadaimport subprocess 54*45765eedSMasahiro Yamada 55*45765eedSMasahiro Yamada# The entries only for the canonical names with MIN_COMMITS or more commits. 56*45765eedSMasahiro Yamada# This limitation is used so as not to create a too big mailmap file. 57*45765eedSMasahiro YamadaMIN_COMMITS = 50 58*45765eedSMasahiro Yamada 59*45765eedSMasahiro Yamadatry: 60*45765eedSMasahiro Yamada toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel']) 61*45765eedSMasahiro Yamadaexcept subprocess.CalledProcessError: 62*45765eedSMasahiro Yamada print >> sys.stderr, 'Please run in a git repository.' 63*45765eedSMasahiro Yamada sys.exit(1) 64*45765eedSMasahiro Yamada 65*45765eedSMasahiro Yamada# strip '\n' 66*45765eedSMasahiro Yamadatoplevel = toplevel.rstrip() 67*45765eedSMasahiro Yamada 68*45765eedSMasahiro Yamada# Change the current working directory to the toplevel of the respository 69*45765eedSMasahiro Yamada# for our easier life. 70*45765eedSMasahiro Yamadaos.chdir(toplevel) 71*45765eedSMasahiro Yamada 72*45765eedSMasahiro Yamada# First, create 'auther name' vs 'number of commits' database. 73*45765eedSMasahiro Yamada# We assume the name with the most commits as the canonical real name. 74*45765eedSMasahiro Yamadashortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n']) 75*45765eedSMasahiro Yamada 76*45765eedSMasahiro Yamadacommits_per_name = {} 77*45765eedSMasahiro Yamada 78*45765eedSMasahiro Yamadafor line in shortlog.splitlines(): 79*45765eedSMasahiro Yamada try: 80*45765eedSMasahiro Yamada commits, name = line.split(None, 1) 81*45765eedSMasahiro Yamada except ValueError: 82*45765eedSMasahiro Yamada # ignore lines with an empty author name 83*45765eedSMasahiro Yamada pass 84*45765eedSMasahiro Yamada commits_per_name[name] = int(commits) 85*45765eedSMasahiro Yamada 86*45765eedSMasahiro Yamada# Next, coalesce the auther names with the same email address 87*45765eedSMasahiro Yamadashortlog = subprocess.check_output(['git', 'shortlog', '-s', '-n', '-e']) 88*45765eedSMasahiro Yamada 89*45765eedSMasahiro Yamadamail_vs_name = {} 90*45765eedSMasahiro Yamadaoutput = {} 91*45765eedSMasahiro Yamada 92*45765eedSMasahiro Yamadafor line in shortlog.splitlines(): 93*45765eedSMasahiro Yamada # tmp, mail = line.rsplit(None, 1) is not safe 94*45765eedSMasahiro Yamada # because weird email addresses might include whitespaces 95*45765eedSMasahiro Yamada tmp, mail = line.split('<') 96*45765eedSMasahiro Yamada mail = '<' + mail.rstrip() 97*45765eedSMasahiro Yamada try: 98*45765eedSMasahiro Yamada _, name = tmp.rstrip().split(None, 1) 99*45765eedSMasahiro Yamada except ValueError: 100*45765eedSMasahiro Yamada # author name is empty 101*45765eedSMasahiro Yamada name = '' 102*45765eedSMasahiro Yamada if mail in mail_vs_name: 103*45765eedSMasahiro Yamada # another name for the same email address 104*45765eedSMasahiro Yamada prev_name = mail_vs_name[mail] 105*45765eedSMasahiro Yamada # Take the name with more commits 106*45765eedSMasahiro Yamada major_name = sorted([prev_name, name], 107*45765eedSMasahiro Yamada key=lambda x: commits_per_name[x] if x else 0)[1] 108*45765eedSMasahiro Yamada mail_vs_name[mail] = major_name 109*45765eedSMasahiro Yamada if commits_per_name[major_name] > MIN_COMMITS: 110*45765eedSMasahiro Yamada output[mail] = major_name 111*45765eedSMasahiro Yamada else: 112*45765eedSMasahiro Yamada mail_vs_name[mail] = name 113*45765eedSMasahiro Yamada 114*45765eedSMasahiro Yamada# [1] If there exists a mailmap file at the location pointed to 115*45765eedSMasahiro Yamada# by the mailmap.file configuration option, update it. 116*45765eedSMasahiro Yamada# [2] If the file .mailmap exists at the toplevel of the repository, update it. 117*45765eedSMasahiro Yamada# [3] Otherwise, create a new mailmap file. 118*45765eedSMasahiro Yamadamailmap_files = [] 119*45765eedSMasahiro Yamada 120*45765eedSMasahiro Yamadatry: 121*45765eedSMasahiro Yamada config_mailmap = subprocess.check_output(['git', 'config', 'mailmap.file']) 122*45765eedSMasahiro Yamadaexcept subprocess.CalledProcessError: 123*45765eedSMasahiro Yamada config_mailmap = '' 124*45765eedSMasahiro Yamada 125*45765eedSMasahiro Yamadaconfig_mailmap = config_mailmap.rstrip() 126*45765eedSMasahiro Yamadaif config_mailmap: 127*45765eedSMasahiro Yamada mailmap_files.append(config_mailmap) 128*45765eedSMasahiro Yamada 129*45765eedSMasahiro Yamadamailmap_files.append('.mailmap') 130*45765eedSMasahiro Yamada 131*45765eedSMasahiro Yamadainfile = None 132*45765eedSMasahiro Yamada 133*45765eedSMasahiro Yamadafor map_file in mailmap_files: 134*45765eedSMasahiro Yamada try: 135*45765eedSMasahiro Yamada infile = open(map_file) 136*45765eedSMasahiro Yamada except: 137*45765eedSMasahiro Yamada # Failed to open. Try next. 138*45765eedSMasahiro Yamada continue 139*45765eedSMasahiro Yamada break 140*45765eedSMasahiro Yamada 141*45765eedSMasahiro Yamadacomment_block = [] 142*45765eedSMasahiro Yamadaoutput_lines = [] 143*45765eedSMasahiro Yamada 144*45765eedSMasahiro Yamadaif infile: 145*45765eedSMasahiro Yamada for line in infile: 146*45765eedSMasahiro Yamada if line[0] == '#' or line[0] == '\n': 147*45765eedSMasahiro Yamada comment_block.append(line) 148*45765eedSMasahiro Yamada else: 149*45765eedSMasahiro Yamada output_lines.append(line) 150*45765eedSMasahiro Yamada break 151*45765eedSMasahiro Yamada for line in infile: 152*45765eedSMasahiro Yamada output_lines.append(line) 153*45765eedSMasahiro Yamada infile.close() 154*45765eedSMasahiro Yamada 155*45765eedSMasahiro Yamadafor mail, name in output.items(): 156*45765eedSMasahiro Yamada output_lines.append(name + ' ' + mail + '\n') 157*45765eedSMasahiro Yamada 158*45765eedSMasahiro Yamadaoutput_lines.sort() 159*45765eedSMasahiro Yamada 160*45765eedSMasahiro Yamadasys.stdout.write(''.join(comment_block + output_lines)) 161