mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
230 lines
7.2 KiB
Python
Executable File
230 lines
7.2 KiB
Python
Executable File
#!/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)$")
|