blob: a8d950df84a5af1d6498dbb470ad94c6c9a5c2eb [file] [log] [blame]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# It is based on the idea of http://0pointer.net/blog/projects/copyright.html
import os
import re
import io
import sys
from git import *
from shutil import move
if sys.version_info[0] >= 3:
# strings are already parsed to unicode
def unicode(s):
return s
# Replace the name by another value, i.e. add affiliation or replace user name by full name
# only use lower case name
authorFullName = {
'staldert': 'Thomas Stalder, Blue Time Concept SA',
'mark giraud': 'Mark Giraud, Fraunhofer IOSB',
'julius pfrommer': 'Julius Pfrommer, Fraunhofer IOSB',
'stefan profanter': 'Stefan Profanter, fortiss GmbH',
}
# Skip commits with the following authors, since they are not valid names
# and come from an invalid git config
skipNames = ['=', 'open62541', 'opcua']
def compactYears(yearList):
current = None
last = None
result = []
for yStr in yearList:
y = int(yStr)
if last is None:
current = y
last = y
continue
if y == last + 1:
last = y
continue
if last == current:
result.append("%i" % last)
else:
result.append("%i-%i" % (current, last))
current = y
last = y
if not last is None:
if last == current:
result.append("%i" % last)
else:
result.append("%i-%i" % (current, last))
return ", ".join(result)
fileAuthorStats = dict()
def insertCopyrightAuthors(file, authorsList):
copyrightEntries = list()
for author in authorsList:
copyrightEntries.append(unicode("Copyright {} (c) {}").format(compactYears(author['years']), author['author']))
copyrightAdded = False
commentPattern = re.compile(r"(.*)\*/$")
tmpName = file + ".new"
tempFile = io.open(tmpName, mode="w", encoding="utf-8")
with io.open(file, mode="r", encoding="utf-8") as f:
for line in f:
if copyrightAdded or not commentPattern.match(line):
tempFile.write(line)
else:
tempFile.write(commentPattern.match(line).group(1) + "\n *\n")
for e in copyrightEntries:
tempFile.write(unicode(" * {}\n").format(e))
tempFile.write(unicode(" */\n"))
copyrightAdded = True
tempFile.close()
os.unlink(file)
move(tmpName, file)
def updateCopyright(repo, file):
print("Checking file {}".format(file))
# Build the info on how many lines every author commited every year
relativeFilePath = file[len(repo.working_dir)+1:].replace("\\","/")
if not relativeFilePath in fileAuthorStats:
print("File not found in list: {}".format(relativeFilePath))
return
stats = fileAuthorStats[relativeFilePath]
# Now create a sorted list and filter out small contributions
authorList = list()
for author in stats:
if author in skipNames:
continue
authorYears = list()
for year in stats[author]['years']:
if stats[author]['years'][year] < 10:
# ignore contributions for this year if less than 10 lines changed
continue
authorYears.append(year)
if len(authorYears) == 0:
continue
authorYears.sort()
if author.lower() in authorFullName:
authorName = authorFullName[author.lower()]
else:
authorName = author
authorList.append({
'author': authorName,
'years': authorYears,
'first_commit': stats[author]['first_commit']
})
# Sort the authors list first by year, and then by name
authorListSorted = sorted(authorList, key=lambda a: a['first_commit'])
insertCopyrightAuthors(file, authorListSorted)
# This is required since some commits use different author names for the same person
assumeSameAuthor = {
'Mark': u'Mark Giraud',
'Infinity95': u'Mark Giraud',
'janitza-thbe': u'Thomas Bender',
'Stasik0': u'Sten Grüner',
'Sten': u'Sten Grüner',
'Frank Meerkoetter': u'Frank Meerkötter',
'ichrispa': u'Chris Iatrou',
'Chris Paul Iatrou': u'Chris Iatrou',
'Torben-D': u'TorbenD',
'FlorianPalm': u'Florian Palm',
'ChristianFimmers': u'Christian Fimmers'
}
def buildFileStats(repo):
fileRenameMap = dict()
renamePattern = re.compile(r"(.*){(.*) => (.*)}(.*)")
cnt = 0
for commit in repo.iter_commits():
cnt += 1
curr = 0
for commit in repo.iter_commits():
curr += 1
print("Checking commit {}/{} -> {}".format(curr, cnt, commit.hexsha))
for objpath, stats in commit.stats.files.items():
match = renamePattern.match(objpath)
if match:
# the file was renamed, store the rename to follow up later
oldFile = (match.group(1) + match.group(2) + match.group(4)).replace("//", "/")
newFile = (match.group(1) + match.group(3) + match.group(4)).replace("//", "/")
while newFile in fileRenameMap:
newFile = fileRenameMap[newFile]
if oldFile != newFile:
fileRenameMap[oldFile] = newFile
else:
newFile = fileRenameMap[objpath] if objpath in fileRenameMap else objpath
if stats['insertions'] > 0:
if not newFile in fileAuthorStats:
fileAuthorStats[newFile] = dict()
authorName = unicode(commit.author.name)
if authorName in assumeSameAuthor:
authorName = assumeSameAuthor[authorName]
if not authorName in fileAuthorStats[newFile]:
fileAuthorStats[newFile][authorName] = {
'years': dict(),
'first_commit': commit.committed_datetime
}
elif commit.committed_datetime < fileAuthorStats[newFile][authorName]['first_commit']:
fileAuthorStats[newFile][authorName]['first_commit'] = commit.committed_datetime
if not commit.committed_datetime.year in fileAuthorStats[newFile][authorName]['years']:
fileAuthorStats[newFile][authorName]['years'][commit.committed_datetime.year] = 0
fileAuthorStats[newFile][authorName]['years'][commit.committed_datetime.year] += stats['insertions']
def walkFiles(repo, folder, pattern):
patternCompiled = re.compile(pattern)
for root, subdirs, files in os.walk(folder):
for f in files:
if patternCompiled.match(f):
fname = os.path.join(root,f)
updateCopyright(repo, fname)
if __name__ == '__main__':
baseDir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
repo = Repo(baseDir)
assert not repo.bare
buildFileStats(repo)
dirs = ['src', 'plugins', 'include']
for dir in dirs:
walkFiles(repo, os.path.join(baseDir, dir), r"(.*\.c|.*\.h)$")