From 4fee3e091a8d79a40f70ff9c1f87b29b9340049a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 25 Jan 2021 14:26:11 +0100 Subject: Merging upstream version 0.15.0. Signed-off-by: Daniel Baumann --- gitlint/git.py | 118 +++++++++++++++++++++++---------------------------------- 1 file changed, 48 insertions(+), 70 deletions(-) (limited to 'gitlint/git.py') diff --git a/gitlint/git.py b/gitlint/git.py index 8e00f89..a9609d0 100644 --- a/gitlint/git.py +++ b/gitlint/git.py @@ -8,7 +8,7 @@ from gitlint import shell as sh from gitlint.shell import CommandNotFound, ErrorReturnCode from gitlint.cache import PropertyCache, cache -from gitlint.utils import ustr, sstr +from gitlint.exception import GitlintError # For now, the git date format we use is fixed, but technically this format is determined by `git config log.date` # We should fix this at some point :-) @@ -17,24 +17,23 @@ GIT_TIMEFORMAT = "YYYY-MM-DD HH:mm:ss Z" LOG = logging.getLogger(__name__) -class GitContextError(Exception): +class GitContextError(GitlintError): """ Exception indicating there is an issue with the git context """ pass class GitNotInstalledError(GitContextError): def __init__(self): - super(GitNotInstalledError, self).__init__( - u"'git' command not found. You need to install git to use gitlint on a local repository. " + - u"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.") + super().__init__( + "'git' command not found. You need to install git to use gitlint on a local repository. " + + "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.") class GitExitCodeError(GitContextError): def __init__(self, command, stderr): self.command = command self.stderr = stderr - super(GitExitCodeError, self).__init__( - u"An error occurred while executing '{0}': {1}".format(command, stderr)) + super().__init__(f"An error occurred while executing '{command}': {stderr}") def _git(*command_parts, **kwargs): @@ -42,33 +41,33 @@ def _git(*command_parts, **kwargs): git_kwargs = {'_tty_out': False} git_kwargs.update(kwargs) try: - LOG.debug(sstr(command_parts)) + LOG.debug(command_parts) result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg # If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't # get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting # a non-zero exit code -> just return the entire result if hasattr(result, 'exit_code') and result.exit_code > 0: return result - return ustr(result) - except CommandNotFound: - raise GitNotInstalledError() + return str(result) + except CommandNotFound as e: + raise GitNotInstalledError from e except ErrorReturnCode as e: # Something went wrong while executing the git command error_msg = e.stderr.strip() error_msg_lower = error_msg.lower() if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower: - error_msg = u"{0} is not a git repository.".format(git_kwargs['_cwd']) - raise GitContextError(error_msg) + raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e if (b"does not have any commits yet" in error_msg_lower or b"ambiguous argument 'head': unknown revision" in error_msg_lower): - raise GitContextError(u"Current branch has no commits. Gitlint requires at least one commit to function.") + msg = "Current branch has no commits. Gitlint requires at least one commit to function." + raise GitContextError(msg) from e - raise GitExitCodeError(e.full_cmd, error_msg) + raise GitExitCodeError(e.full_cmd, error_msg) from e def git_version(): """ Determine the git version installed on this host by calling git --version""" - return _git("--version").replace(u"\n", u"") + return _git("--version").replace("\n", "") def git_commentchar(repository_path=None): @@ -77,17 +76,17 @@ def git_commentchar(repository_path=None): # git will return an exit code of 1 if it can't find a config value, in this case we fall-back to # as commentchar if hasattr(commentchar, 'exit_code') and commentchar.exit_code == 1: # pylint: disable=no-member commentchar = "#" - return ustr(commentchar).replace(u"\n", u"") + return commentchar.replace("\n", "") def git_hooks_dir(repository_path): """ Determine hooks directory for a given target dir """ hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path) - hooks_dir = ustr(hooks_dir).replace(u"\n", u"") + hooks_dir = hooks_dir.replace("\n", "") return os.path.realpath(os.path.join(repository_path, hooks_dir)) -class GitCommitMessage(object): +class GitCommitMessage: """ Class representing a git commit message. A commit message consists of the following: - context: The `GitContext` this commit message is part of - original: The actual commit message as returned by `git log` @@ -106,35 +105,26 @@ class GitCommitMessage(object): def from_full_message(context, commit_msg_str): """ Parses a full git commit message by parsing a given string into the different parts of a commit message """ all_lines = commit_msg_str.splitlines() - cutline = u"{0} ------------------------ >8 ------------------------".format(context.commentchar) + cutline = f"{context.commentchar} ------------------------ >8 ------------------------" try: cutline_index = all_lines.index(cutline) except ValueError: cutline_index = None - lines = [ustr(line) for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)] + lines = [line for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)] full = "\n".join(lines) title = lines[0] if lines else "" body = lines[1:] if len(lines) > 1 else [] return GitCommitMessage(context=context, original=commit_msg_str, full=full, title=title, body=body) - def __unicode__(self): - return self.full # pragma: no cover - def __str__(self): - return sstr(self.__unicode__()) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + return self.full def __eq__(self, other): return (isinstance(other, GitCommitMessage) and self.original == other.original and self.full == other.full and self.title == other.title and self.body == other.body) # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - -class GitCommit(object): +class GitCommit: """ Class representing a git commit. A commit consists of: context, message, author name, author email, date, list of parent commit shas, list of changed files, list of branch names. @@ -155,39 +145,33 @@ class GitCommit(object): @property def is_merge_commit(self): - return self.message.title.startswith(u"Merge") + return self.message.title.startswith("Merge") @property def is_fixup_commit(self): - return self.message.title.startswith(u"fixup!") + return self.message.title.startswith("fixup!") @property def is_squash_commit(self): - return self.message.title.startswith(u"squash!") + return self.message.title.startswith("squash!") @property def is_revert_commit(self): - return self.message.title.startswith(u"Revert") - - def __unicode__(self): - format_str = (u"--- Commit Message ----\n%s\n" - u"--- Meta info ---------\n" - u"Author: %s <%s>\nDate: %s\n" - u"is-merge-commit: %s\nis-fixup-commit: %s\n" - u"is-squash-commit: %s\nis-revert-commit: %s\n" - u"Branches: %s\n" - u"Changed Files: %s\n" - u"-----------------------") # pragma: no cover - date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None - return format_str % (ustr(self.message), self.author_name, self.author_email, date_str, - self.is_merge_commit, self.is_fixup_commit, self.is_squash_commit, - self.is_revert_commit, sstr(self.branches), sstr(self.changed_files)) # pragma: no cover + return self.message.title.startswith("Revert") def __str__(self): - return sstr(self.__unicode__()) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None + return (f"--- Commit Message ----\n{self.message}\n" + "--- Meta info ---------\n" + f"Author: {self.author_name} <{self.author_email}>\n" + f"Date: {date_str}\n" + f"is-merge-commit: {self.is_merge_commit}\n" + f"is-fixup-commit: {self.is_fixup_commit}\n" + f"is-squash-commit: {self.is_squash_commit}\n" + f"is-revert-commit: {self.is_revert_commit}\n" + f"Branches: {self.branches}\n" + f"Changed Files: {self.changed_files}\n" + "-----------------------") def __eq__(self, other): # skip checking the context as context refers back to this obj, this will trigger a cyclic dependency @@ -199,9 +183,6 @@ class GitCommit(object): and self.is_squash_commit == other.is_squash_commit and self.is_revert_commit == other.is_revert_commit and self.changed_files == other.changed_files and self.branches == other.branches) # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - class LocalGitCommit(GitCommit, PropertyCache): """ Class representing a git commit that exists in the local git repository. @@ -230,7 +211,7 @@ class LocalGitCommit(GitCommit, PropertyCache): # "YYYY-MM-DD HH:mm:ss Z" -> ISO 8601-like format # Use arrow for datetime parsing, because apparently python is quirky around ISO-8601 dates: # http://stackoverflow.com/a/30696682/381010 - commit_date = arrow.get(ustr(date), GIT_TIMEFORMAT).datetime + commit_date = arrow.get(date, GIT_TIMEFORMAT).datetime # Create Git commit object with the retrieved info commit_msg_obj = GitCommitMessage.from_full_message(self.context, commit_msg) @@ -270,7 +251,7 @@ class LocalGitCommit(GitCommit, PropertyCache): # safely do this since git branches cannot contain '*' anywhere, so if we find an '*' we know it's output # from the git CLI and not part of the branch name. See https://git-scm.com/docs/git-check-ref-format # We also drop the last empty line from the output. - self._cache['branches'] = [ustr(branch.replace("*", "").strip()) for branch in branches[:-1]] + self._cache['branches'] = [branch.replace("*", "").strip() for branch in branches[:-1]] return self._try_cache("branches", cache_branches) @@ -306,17 +287,17 @@ class StagedLocalGitCommit(GitCommit, PropertyCache): @cache def author_name(self): try: - return ustr(_git("config", "--get", "user.name", _cwd=self.context.repository_path)).strip() - except GitExitCodeError: - raise GitContextError("Missing git configuration: please set user.name") + return _git("config", "--get", "user.name", _cwd=self.context.repository_path).strip() + except GitExitCodeError as e: + raise GitContextError("Missing git configuration: please set user.name") from e @property @cache def author_email(self): try: - return ustr(_git("config", "--get", "user.email", _cwd=self.context.repository_path)).strip() - except GitExitCodeError: - raise GitContextError("Missing git configuration: please set user.email") + return _git("config", "--get", "user.email", _cwd=self.context.repository_path).strip() + except GitExitCodeError as e: + raise GitContextError("Missing git configuration: please set user.email") from e @property @cache @@ -356,7 +337,7 @@ class GitContext(PropertyCache): @property @cache def current_branch(self): - current_branch = ustr(_git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path)).strip() + current_branch = _git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path).strip() return current_branch @staticmethod @@ -396,7 +377,7 @@ class GitContext(PropertyCache): # We tried many things here e.g.: defaulting to e.g. HEAD or HEAD^... (incl. dealing with # repos that only have a single commit - HEAD^... doesn't work there), but then we still get into # problems with e.g. merge commits. Easiest solution is just taking the SHA from `git log -1`. - sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace(u"\n", u"")] + sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace("\n", "")] else: sha_list = _git("rev-list", refspec, _cwd=repository_path).split() @@ -410,6 +391,3 @@ class GitContext(PropertyCache): return (isinstance(other, GitContext) and self.commits == other.commits and self.repository_path == other.repository_path and self.commentchar == other.commentchar and self.current_branch == other.current_branch) # noqa - - def __ne__(self, other): - return not self.__eq__(other) # required for py2 -- cgit v1.2.3