diff --git a/bin/licenser.py b/bin/licenser.py index bb2126f..06324b8 100755 --- a/bin/licenser.py +++ b/bin/licenser.py @@ -1,145 +1,145 @@ #! /usr/bin/env python3 # -*- coding: utf-8 -*- __author__ = "Guillaume Anciaux, and Nicolas Richart" __copyright__ = "Copyright (C) 2015, EPFL (Ecole Polytechnique Fédérale de Lausanne) Laboratory " \ "(LSMS - Laboratoire de Simulation en Mécanique des Solides)" __credits__ = ["Guillaume Anciaux", "Nicolas Richart"] __license__ = "GPL" __version__ = "1.0" __maintainer__ = "Nicolas Richart" __email__ = "nicolas.richart@epfl.ch" import argparse import datetime as dt import sys import pylicenser as pylic def mkdate(datestring): return dt.datetime.strptime(datestring, '%Y-%m-%d').date() if __name__ == "__main__": parser = argparse.ArgumentParser(prog='licenser', add_help=True) parser.add_argument("-i,--input", help="Filename to check", dest="filename", default=None) parser.add_argument("-f,--file_list", help="File containing a list of files", dest="file_list", default=None) parser.add_argument("--repo", help="Repository to consider", dest="repo", default=None) parser.add_argument("-p,--path", help="Folder where to find the files", dest="path", default="") parser.add_argument("-s,--skip-first", help="Skip the first files when using the -f option", dest="skip_first", type=int, default=0) parser.add_argument("-v,--versioning-backend", dest="vc_backend", help="Backend used as versioning system (svn, git, none)") parser.add_argument("configuration_file", help="File containing the configuration, .csv or .db (sqlite)") parser.add_argument("-r,--release-date", help="Date at which the release is prepared", dest='release_date', type=mkdate, default=dt.datetime.now().date()) parser.add_argument("-a,--no-author-check", help="Do not check the author list", dest="no_author_check", action='store_true', default=False) parser.add_argument("-b,--no-brief-check", help="Do not check the brief", dest="no_brief_check", action='store_true', default=False) parser.add_argument("--ignore-threshold", help="Limit of number of line to consider an author from the VC system", dest="ignore_threshold", type=int, default=0) parser.add_argument("--ignore-filled-briefs", help="Do not check the brief if they are not empty", dest="ignore_filled_briefs", action='store_true', default=False) parser.add_argument("--dry-run", help="Do nothing for real", dest='dry_run', action='store_true', default=False) parser.add_argument("-l,--force-license", help="Force a give license", dest="force_license", default=None) parser.add_argument("--force", help="Force to update the header even it is considered up-to-date", dest="force", action='store_true', default=False) parser.add_argument("--yes", help="Answers yes to keep author and brief questions", dest="yes", action='store_true', default=False) args = parser.parse_args() if (args.filename is None) and (args.file_list is None): print("You should at least give a filename or a file_list") parser.print_help() sys.exit(-1) if (args.filename is not None) and (args.file_list is not None): print("You should give only on of the option filename or file_list") parser.print_help() sys.exit(-1) file_list = [] if args.filename is not None: file_list.append(args.filename) if args.file_list is not None: with open(args.file_list, "r") as fh: file_list = [l.strip() for l in fh] db = pylic.LicenserDB(args.configuration_file) c = 0 t = len(file_list) _kwargs = vars(args) _kwargs.pop("filename", None) for f in file_list: c += 1 print("[{0:>3}%]({2:>3}/{3}) {1}".format(int(float(c) / t * 100), - pylic.print_colored(f, attrs=['bold']), c, - t), end="") + pylic.print_colored(f, attrs=['bold']), + c, t), end="") if c <= args.skip_first: print(" ({0})".format(pylic.print_colored("skipped", "red", attrs=['bold']))) continue elif args.force: print(" ({0})".format(pylic.print_colored("forced", "red", attrs=['bold']))) else: print("") if not args.path == "": path = args.path.rstrip("/") + "/" else: path = "" ft = pylic.FileTransformer(path + f, db, **_kwargs) ft.replace_file(args.dry_run) diff --git a/pylicenser/file_info.py b/pylicenser/file_info.py index 1535331..278fcb3 100755 --- a/pylicenser/file_info.py +++ b/pylicenser/file_info.py @@ -1,272 +1,272 @@ # -*- coding: utf-8 -*- __author__ = "Guillaume Anciaux, and Nicolas Richart" __copyright__ = "Copyright (C) 2015, EPFL (Ecole Polytechnique Fédérale de Lausanne) Laboratory " \ "(LSMS - Laboratoire de Simulation en Mécanique des Solides)" __credits__ = ["Guillaume Anciaux", "Nicolas Richart"] __license__ = "GPL" __version__ = "1.0" __maintainer__ = "Nicolas Richart" __email__ = "nicolas.richart@epfl.ch" import os import re from datetime import datetime as dt from . import export UNKNOWN_TYPE = 0 CPP_SOURCES = 1 CMAKE_FILES = 2 SWIG_FILES = 3 PYTHON_SCRIPT = 4 CREATION_MODIFICATION = 0 LAST_MODIFICATION = 1 CREATION = 2 @export class FileInfo: __date_style_conv = {'creation_modification': CREATION_MODIFICATION, 'last_modification': LAST_MODIFICATION, 'creation': CREATION} __supported_ext = [] __supported_types = {CPP_SOURCES: [".cc", ".c", ".hh", ".h"], CMAKE_FILES: [".txt", ".cmake"], SWIG_FILES: [".i"], - PYTHON_SCRIPT: [".py"]} + #PYTHON_SCRIPT: [".py"] + } _file_type = UNKNOWN_TYPE _file_content = "" _warn_list = list() _brief = "" _sections = list() _authors = set() _header = "" def __init__(self, filename, author_db): self._warn_list = list() self._sections = list() self._authors = set() self._author_db = author_db self._filename = os.path.expanduser(filename) garbage, self._ext = os.path.splitext(os.path.basename(filename)) if self._ext == '.in': self._ext = os.path.splitext(garbage)[1] for key, value in self.__supported_types.items(): self.__supported_ext.extend(value) if self._ext not in self.__supported_ext: raise (NotImplementedError("File {0} has been skipped based on its extension".format(self._filename))) for key, value in self.__supported_types.items(): if self._ext in value: self._file_type = key self.__analyse_content() def __analyse_content(self): with open(self._filename) as f: self._file_content = f.read() try: self.__split_header() except Exception as error: raise Exception("While dealing with file {0}:\n{1}".format(self._filename, error)) self.__find_authors() - self.__find_brief() self.__save_sections() if self._warn_list: print("\nWARNING!:") print(" While dealing with file {0}".format(self._filename)) print(" the following shit happened:") [print(" {0}".format(warning)) for warning in self._warn_list] print("{0}:1:".format(self._filename)) def __save_sections(self): reg = re.compile('^(.*@section)', re.MULTILINE) pref = re.search(reg, self._header) pref = pref.group(1) if pref is not None else "" reg = re.compile('^.*@section', re.MULTILINE) licreg = re.compile('LICEN.E') for section in re.split(reg, self._header)[1:]: if not re.search(licreg, section.split('\n')[0]): self._sections.append(pref + section) def __split_header(self): if not re.search("Copyright", self._file_content): self._header = "" self._body = self._file_content print("This file as no header or a really badly formatted one!!") else: if self._file_type in [CPP_SOURCES, SWIG_FILES]: self._header, self._body = re.split("\*/", self._file_content, maxsplit=1) self._header = '\n'.join((line for line in self._header.split('\n') if line.strip())) elif self._file_type == CMAKE_FILES: lines = self._file_content.split('\n') nb_sep = 0 nb_whitelines = 0 header_lines = list() while nb_sep < 2: if lines[0].strip().startswith('#===='): nb_sep += 1 elif not lines[0].strip(): nb_whitelines += 1 header_lines.append(lines.pop(0)) if nb_whitelines > 0: self._warn_list.append("The header has a blank line or is ill-formed") self._header = "\n".join(header_lines) self._body = "\n".join(lines) def __find_authors(self): reg = re.compile( '@author\s+(.*)\s 1: self._warn_list.append("There's multiple @brief") self._brief = "\n".join((line.replace(prefix, "").strip() for line in matches[0].group(1).split('\n') if len(line.strip()) > 1)) def generate_header(self, real_authors=None, copyright_txt="", creation_date=None, last_modification_date=None, date_style=CREATION_MODIFICATION, date_format="%a %b %d %H:%M:%S %Y", brief=None): if type(date_style) == str: # noinspection PyTypeChecker date_style = self.__date_style_conv[date_style] if self._file_type in [CPP_SOURCES, SWIG_FILES]: starter = "/**" prefix = " *" closer = " */" force_closer = True elif self._file_type == CMAKE_FILES: starter = "#===============================================================================" prefix = "#" closer = "#===============================================================================" force_closer = False else: raise IOError("file type not recognized") if real_authors is None: real_authors = self._authors new_lines = list() new_lines.append(starter) # file name new_lines.append(prefix + " @file " + os.path.basename(self._filename)) new_lines.append(prefix) # authors authors = list(real_authors) authors.sort(key=lambda x: x.last_name + ' ' + x.first_name) for author in authors: new_lines.append('{0} @author {1}'.format(prefix, author)) new_lines.append(prefix) # date if date_style == CREATION_MODIFICATION and creation_date is not None and last_modification_date is not None: new_lines.append( "{0} @date creation: {1}".format(prefix, creation_date.strftime(date_format))) if last_modification_date != creation_date: new_lines.append("{0} @date last modification: {1}".format(prefix, last_modification_date.strftime(date_format))) elif date_style == LAST_MODIFICATION and last_modification_date is not None: new_lines.append("{0} @date {1}".format( prefix, last_modification_date.strftime(date_format))) elif date_style == CREATION and creation_date is not None: new_lines.append("{0} @date {1}".format( prefix, creation_date.strftime(date_format))) elif date_style == LAST_MODIFICATION and last_modification_date is not None: new_lines.append("{0} @date {1}".format( prefix, last_modification_date.strftime(date_format))) elif date_style == CREATION and creation_date is not None: new_lines.append("{0} @date {1}".format( prefix, creation_date.strftime(date_format))) new_lines.append(prefix) # brief brief_list = (self._brief if brief is None else brief).split("\n") pref_join = ("\n{0} ".format(prefix)) new_lines.append('{0} @brief {1}'.format(prefix, pref_join.join([b for b in brief_list]))) new_lines.append(prefix) # license new_lines.append(prefix + " @section LICENSE") new_lines.append(prefix) for line in copyright_txt.split("\n"): new_lines.append('{0}{1}{2}'.format(prefix, " " if line else "", line)) new_lines.append(prefix) # sections for section in self._sections: new_lines += section.split('\n') if force_closer or not self._sections: new_lines.append(closer) return '\n'.join(new_lines) def get_brief(self): return self._brief def replace_file(self, new_header=None): if new_header is None: new_header = self._header body = self._body.split("\n") while body and not body[0].strip(): body.pop(0) new_file = (new_header + '\n\n' + '\n'.join(body)) with open(self._filename, "w") as fh: print(new_file, file=fh, end='') @property def authors(self): return self._authors diff --git a/pylicenser/file_transformer.py b/pylicenser/file_transformer.py index 50e583d..22ce6d7 100755 --- a/pylicenser/file_transformer.py +++ b/pylicenser/file_transformer.py @@ -1,306 +1,313 @@ # -*- coding: utf-8 -*- __author__ = "Guillaume Anciaux, and Nicolas Richart" __copyright__ = "Copyright (C) 2015, EPFL (Ecole Polytechnique Fédérale de Lausanne) Laboratory " \ "(LSMS - Laboratoire de Simulation en Mécanique des Solides)" __credits__ = ["Guillaume Anciaux", "Nicolas Richart"] __license__ = "GPL" __version__ = "1.0" __maintainer__ = "Nicolas Richart" __email__ = "nicolas.richart@epfl.ch" import datetime as dt from . import licenser_ask_question from . import author_db as adb from . import copyright_db as cdb from . import export from . import file_info as fi from . import print_colored from . import version_info as vc import os from pygments import highlight from pygments.lexers.diff import DiffLexer from pygments.formatters.terminal256 import Terminal256Formatter from pygments.formatters.terminal import TerminalFormatter @export class FileTransformer(object): """ Class that reformat the headers """ __keep_authors = None _brief = None __ignore = False _new_header = None def __init__(self, filename, db, release_date=dt.datetime.now().date(), no_author_check=False, no_brief_check=False, force=False, **kwargs): self.__filename = filename self.__release_date = release_date self.__db = db self.__no_brief_check = no_brief_check self.__no_author_check = no_author_check # Check the authors self.__author_db = adb.AuthorDB(db) if "vc_backend" in kwargs and kwargs["vc_backend"] is not None: vc_back = kwargs["vc_backend"] else: vc_back = db.versioning_backend if vc_back != "none": self._date_style = self.__db.get_config('date_style') self.__repo = self.__db.get_config('repo') self._vc_info = vc.VersionInfo(self.__repo, self.__filename, self.__db.get_list_of_ignore_emails(), backend=vc_back, rev_to=release_date) self.__name = self._vc_info.name self.__filename = self.__repo + '/' + self.__name self._creation_date = self._vc_info.creation_date self._last_modif = self._vc_info.last_modification_date else: self._creation_date = None self._last_modif = None self._date_style = None self._vc_info = None try: self._file = fi.FileInfo(self.__filename, self.__author_db) except NotImplementedError: print("File {0} ignored due to {1}".format(self._vc_info.name, print_colored('unknown type', 'red', attrs=['bold']))) self.__ignore = True return rev_from = None license_id = None self.__oldest_name = "" if vc_back != "none": self.__oldest_name = self._vc_info.oldest_name file_maj = self.__db.find_file(self.__oldest_name) if file_maj is not None: rev_from, license_id = file_maj if not force and rev_from.date() >= release_date: print("File {0} ignored due to recent modifications ({1})".format(self._vc_info.name, print_colored( rev_from.strftime( "%Y-%m-%d"), 'red', attrs=['bold']))) self.__ignore = True return if not self.__no_author_check: self._vc_authors = self._vc_info.authors self._vc_info.populate(self.__author_db, rev_to=self.__release_date, rev_from=rev_from) self._vc_authors = self._vc_info.authors self._f_authors = self._file.authors self._real_authors = self.__compare_authors(self._f_authors, self._vc_authors, file_is_good=False, **kwargs) if self._real_authors - self._f_authors: print("Added authors:\n{0}".format( "\n".join([" @author {0}".format(author) for author in self._real_authors - self._f_authors]))) if self._f_authors - self._real_authors: print("Removed authors:\n{0}".format( "\n".join([" @author {0}".format(author) for author in self._f_authors - self._real_authors]))) else: self._real_authors = self._file.authors + if len(self._real_authors) == 0: + res = input("This file as no author please provide the username of the authors: ") + while res is not "": + self._real_authors.add(self.__author_db.find_by_user_name(res)) + res = input("> ") + # Check the brief if not self.__no_brief_check: self.__check_brief(**kwargs) else: self._brief = self._file.get_brief() if "force_license" in kwargs and kwargs['force_license'] is not None: license_id = kwargs["force_license"] # Getting the license content self.__copyright_base = cdb.CopyrightDB(db) if license_id is None: copyright_policy = self.__db.get_config('copyright_policy') if copyright_policy == 'creation_date': cdate = self._creation_date.date() elif copyright_policy == 'release_date': cdate = self.__release_date else: cdate = None self._lic = self.__copyright_base.find_by_date(cdate) else: self._lic = self.__copyright_base.find_by_id(license_id) if self._lic is None: raise ("The license with the id {0} is not defined".format(license_id)) date_format = self.__db.get_config('date_format') # Generates the new header file self._new_header = self._file.generate_header( real_authors=self._real_authors, copyright_txt=self._lic.text, date_format=date_format, date_style=self._date_style, last_modification_date=self._last_modif, creation_date=self._creation_date, brief=self._brief) # noinspection PyPep8Naming,PyUnusedLocal def __compare_authors(self, file_known_tmp, vc_known, file_is_good=False, ignore_threshold=0, yes=False, **kwargs): file_known = file_known_tmp if len(file_known) == 0 and len(vc_known) == 1: file_known = vc_known if not file_is_good: last_keep = None if len(self.__oldest_name) != 0: last_keep = self.__db.get_last_keep_authors(self.__oldest_name, self.__author_db) if last_keep is None: last_keep = [] for author in last_keep: print("Keeping @author {0} ({1})".format(author, print_colored("previously validated", 'red', attrs=['bold']))) file_only = file_known - vc_known - set(last_keep) to_remove = set() if file_only: KEEP = 0 REMOVE = 1 for author in file_only: if not yes: answer = licenser_ask_question("Do you want to remove" + " @author {0}".format(author), {'k': KEEP, 'r': REMOVE}, 'k') if answer == REMOVE: to_remove.add(author) else: print("Do you want to remove @author {0}? ({1}/r)".format(author, print_colored('K', 'red', attrs=['bold']))) file_known = file_known - to_remove self.__keep_authors = file_known - to_remove - vc_known vc_only = vc_known - file_known if vc_only: to_add = set() YES = 0 NO = 1 DIFF = 2 for author in vc_only: modifications, stats = self._vc_info.modifications_by_author(author.user_name) number_of_modified_lines = sum([len(e[2]) for e in modifications]) number_of_modifications = len(modifications) if number_of_modified_lines <= ignore_threshold or number_of_modified_lines == number_of_modifications: print('Potential new author' + ' @author {0} '.format(author) + - '({0} modifications, lines count {1}) {2}'.format(number_of_modifications, - number_of_modified_lines, - print_colored("[ignored do to threshold]", - 'red', attrs=['bold']))) + '({0} modifications, lines count {1}) {2}'.format( + number_of_modifications, + number_of_modified_lines, + print_colored("[ignored do to threshold {0}]".format(ignore_threshold), + 'red', attrs=['bold']))) continue answer = DIFF while answer == DIFF: answer = licenser_ask_question( 'Do you want to add' + ' @author {0}'.format(print_colored(author, "blue")) + ' ({0} modifications, lines count {1}) '.format(number_of_modifications, number_of_modified_lines), {'d': DIFF, 'n': NO, 'y': YES}, 'n') if answer == DIFF: for info, modif, nb_lines, diff_stats in modifications: if len(modif) > 2: print(info) formatter = (Terminal256Formatter if '256color' in os.environ.get('TERM', '') else TerminalFormatter) print(highlight(modif, DiffLexer(), formatter())) elif answer == YES: to_add.add(author) file_known = file_known.union(to_add) return file_known # noinspection PyUnusedLocal def __check_brief(self, ignore_filled_briefs=False, yes=False, **kwargs): self._brief = self._file.get_brief() if self._brief and not ignore_filled_briefs: if len(self.__oldest_name) != 0: old_brief = self.__db.get_last_brief(self.__oldest_name) else: old_brief = None if old_brief is not None and old_brief == self._brief: print('Brief: {0} ({1})'.format(self._brief, print_colored("previously validated", 'red', attrs=['bold']))) else: if not yes: keep_brief = licenser_ask_question("\"{0}\"\nDo you want to keep this brief".format(self._brief)) if not keep_brief: self._brief = False else: print("\"{0}\"\nDo you want to keep this brief ? ({1}/n)".format(self._brief, print_colored('Y', 'red', attrs=['bold']))) if not self._brief: res = input("Please type the brief for file: ") brief = [] while res is not "": brief.append(res) res = input("> ") self._brief = "\n".join(brief) def replace_file(self, dry_run=True): if self.__ignore: return if dry_run: print(self._new_header) else: if self._vc_info is not None: self.__db.update_license_file(self.__oldest_name, self._lic.lid, self.__release_date) if self._brief is not None and not self.__no_brief_check: self.__db.update_brief_file(self.__oldest_name, self._brief, self.__release_date) if self.__keep_authors is not None and not self.__no_author_check: self.__db.update_keep_authors_file(self.__oldest_name, self.__keep_authors, self.__release_date) self._file.replace_file(self._new_header) diff --git a/pylicenser/vcs/git.py b/pylicenser/vcs/git.py index edcf166..0e51970 100644 --- a/pylicenser/vcs/git.py +++ b/pylicenser/vcs/git.py @@ -1,139 +1,148 @@ """ Gather info for git based repos """ __author__ = "Guillaume Anciaux, and Nicolas Richart" __copyright__ = "Copyright (C) 2015, EPFL (Ecole Polytechnique Fédérale de Lausanne) Laboratory " \ "(LSMS - Laboratoire de Simulation en Mécanique des Solides)" __credits__ = ["Guillaume Anciaux", "Nicolas Richart"] __license__ = "GPL" __version__ = "1.0" __maintainer__ = "Nicolas Richart" __email__ = "nicolas.richart@epfl.ch" import git import binascii import datetime as dt +import re from .. import export from .. import version_info @export class GITInfo(version_info.VersionInfo): """ git implementation of the version info class """ __repo = None __other_names = [] def __init__(self, repo, filename, ignore_list, rev_to=None, **kwargs): super().__init__(repo, filename, ignore_list, **kwargs) self.__repo = git.Repo(self._repo) self._name = filename.replace(self._repo, '') commits = self._list_commits(rev_to=rev_to, ignore_list=ignore_list) self._modification_dates = [] for c in commits: if c.author.email not in ignore_list: self._modification_dates.append(dt.datetime.fromtimestamp(c.authored_date)) self.__other_names = self._list_names(commits) # noinspection PyIncorrectDocstring def populate(self, author_db, rev_from=None, rev_to=None): """ populate the internal variables """ _commits = self._list_commits(rev_to=rev_to, rev_from=rev_from) for c in _commits: email = c.author.email a = author_db.find_by_email(email) auth = a.user_name if auth not in self._author_list: self._author_list[auth] = [] if not author_db.is_author_in_ignore_list(a): self._authors.add(a) d = dt.datetime.fromtimestamp(c.authored_date) rev = c.hexsha msg = c.message self._author_list[auth].append((d, rev, msg)) def get_modifications(self, revision): hexsha = binascii.unhexlify(revision) commit = git.objects.commit.Commit(self.__repo, hexsha) stats = commit.stats.total patches = [] + subproject_meta = re.compile(r'[-+]Subproject commit [0-9a-z]+$') + header_modif = re.compile(r'[-+] * (@author|@brief|@date|Copyright +\(©\)) ') + + if len(commit.parents) == 2: #merge commit + print('Skipping merge commit') + return '\n'.join(patches), stats + for parent in commit.parents: - diffs = commit.diff(parent, R=True, create_patch=True, ignore_space_change=True, - diff_algorithm='minimal', paths=[self.__other_names]) - patches.extend([d.diff.decode('utf-8', errors="surrogateescape") for d in diffs]) + diffs = commit.diff(parent, create_patch=True, ignore_space_change=True, paths=[self.__other_names]) + patch = [d.diff.decode('utf-8', errors="surrogateescape") for d in diffs] + if len(patch) > 0 and not re.match(subproject_meta, str(patch)) and not re.match(header_modif, str(patch)): + patches.extend(patch) return '\n'.join(patches), stats def _list_commits(self, rev_to=None, rev_from=None, ignore_list=None): if ignore_list is None: ignore_list = [] _args = {'follow': True, 'all': True, 'pretty': 'tformat:%H'} if rev_from is not None: _args['since'] = rev_from.strftime("%Y-%m-%d") if rev_to is not None: _args['until'] = rev_to.strftime("%Y-%m-%d") # git log can follow better renames in case there where badly done (add/rm instead of mv) git_cmd = git.cmd.Git(working_dir=self.__repo.working_dir) str_c = git_cmd.log(self._name, **_args) if len(str_c) != 0: binhashes = (binascii.unhexlify(c) for c in str_c.split('\n')) else: binhashes = [] list_commits = [] for b in binhashes: c = git.objects.commit.Commit(self.__repo, b) if c.author.email not in ignore_list: list_commits.append(c) if len(list_commits) == 0: del _args['follow'] del _args['pretty'] return list(self.__repo.iter_commits(paths=self._name, **_args)) # list_commits = list(self.__repo.iter_commits(paths=self._name, **_args)) return list_commits def _list_names(self, commits): """ Finds all the names of a given file """ names = [self._name] for c in commits: for p in c.parents: diffs = c.diff(p, R=True, M=True) for d in diffs.iter_change_type('R'): if d.renamed and d.rename_to in names: new_path = d.rename_from self._names[new_path] = c.hexsha names.append(new_path) if len(self._names.keys()) == 0: c = commits[-1] self._names[self._name] = c.hexsha names.reverse() return names @property def oldest_name(self): return self.__other_names[0] diff --git a/pylicenser/vcs/svn.py b/pylicenser/vcs/svn.py index 9a5678f..c22dca2 100644 --- a/pylicenser/vcs/svn.py +++ b/pylicenser/vcs/svn.py @@ -1,122 +1,122 @@ """ Gather info for subversion based repos """ __author__ = "Guillaume Anciaux, and Nicolas Richart" __copyright__ = "Copyright (C) 2015, EPFL (Ecole Polytechnique Fédérale de Lausanne) Laboratory " \ "(LSMS - Laboratoire de Simulation en Mécanique des Solides)" __credits__ = ["Guillaume Anciaux", "Nicolas Richart"] __license__ = "GPL" __version__ = "1.0" __maintainer__ = "Nicolas Richart" __email__ = "nicolas.richart@epfl.ch" from .. import export from .. import print_colored from .. import version_info -import pysvn +import svn import re import datetime as dt @export class SVNInfo(version_info.VersionInfo): """ subversion implementation of the version info class """ def __init__(self, repo, filename, ignore_list, rev_to=None, **kwargs): super().__init__(repo, filename, ignore_list, **kwargs) self._client = pysvn.Client() self._root_url = self._client.root_url_from_path(repo) self._infos = self._client.info(self._filename) if rev_to is not None: rev_start = pysvn.Revision(pysvn.opt_revision_kind.date, rev_to.strftime('%s')) else: rev_start = pysvn.Revision(pysvn.opt_revision_kind.head) self._name = self._infos.url.replace(self._root_url, '') self._logs = self._client.log(self._filename, strict_node_history=False, discover_changed_paths=True, revision_start=rev_start) prev_name = self._name self._logs = sorted(self._logs, key=lambda log: log["revision"].number, reverse=True) for entry in self._logs: d = dt.datetime.fromtimestamp(entry["date"]) self._modification_dates.append(d) # checking if the name as changed old_names = [ ch_path for ch_path in entry["changed_paths"] if (re.match("{0}.*".format(ch_path["path"]), prev_name)) ] if len(old_names) > 0 and old_names[0]["copyfrom_path"] is not None: m = re.match("{0}(.*)".format(old_names[0]["path"]), prev_name) tmp_prev_name = prev_name prev_name = "{0}{1}".format(old_names[0]["copyfrom_path"], m.group(1) if m else "") self._names[old_names[0]["copyfrom_revision"].number] = tmp_prev_name self._names[0] = prev_name # print("\n".join(["{0} {1}".format(n, r) for r, n in self._names.items() ])) @property def oldest_name(self): return self._names[0] def populate(self, author_db, rev_from=None, rev_to=None): if rev_from is not None: rev_end = pysvn.Revision(pysvn.opt_revision_kind.date, rev_from.strftime('%s')) else: rev_end = pysvn.Revision(pysvn.opt_revision_kind.number, 0) if rev_to is not None: rev_start = pysvn.Revision(pysvn.opt_revision_kind.date, rev_to.strftime('%s')) else: rev_start = pysvn.Revision(pysvn.opt_revision_kind.head) self._logs = self._client.log(self._filename, strict_node_history=False, revision_start=rev_start, revision_end=rev_end) self._logs = sorted(self._logs, key=lambda log: log["revision"].number) for entry in self._logs: auth = entry["author"] rev = entry["revision"] d = dt.datetime.fromtimestamp(entry["date"]) msg = entry["message"] if "message" in entry else "" self._revisions.append(rev) if auth not in self._author_list: self._author_list[auth] = [] a = author_db.find_by_user_name(auth) if not author_db.is_author_in_ignore_list(a): self._authors.add(a) self._author_list[auth].append((d, rev, msg)) def __find_previous_release(self, r): revs = [ rev.number for rev in self._revisions if rev.number < r] rev = 0 if len(revs) == 0 else max(revs) return pysvn.Revision(pysvn.opt_revision_kind.number, rev) def get_modifications(self, revision): r_pre = pysvn.Revision(pysvn.opt_revision_kind.number, revision.number -1) # self.__find_previous_release(revision.number) relevant_rev = max([ n for n in self._names.keys() if revision.number > n ]) try: # print("{0} r{1}:{2}".format(self._root_url + self._names[relevant_rev], # r_pre.number, # revision.number)) res_diff = self._client.diff("/tmp", self._root_url + self._names[relevant_rev], revision1=r_pre, revision2=revision, diff_options=['-b', '-w']) stats = {'modifications': len(res_diff)} return res_diff,stats except Exception as e: pass return ""