123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #!/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
- try:
- from StringIO import StringIO
- except ImportError:
- from io import StringIO
- 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',
- '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)$")
|