summaryrefslogtreecommitdiffstats
path: root/gitlint/git.py
diff options
context:
space:
mode:
Diffstat (limited to 'gitlint/git.py')
-rw-r--r--gitlint/git.py118
1 files changed, 48 insertions, 70 deletions
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