Exemplo n.º 1
0
    def test_get_latest_commit_git_error(self, sh):
        # Current directory not a git repo
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"",
                                                 err)

        with self.assertRaisesRegexp(GitContextError,
                                     "fake/path is not a git repository."):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1',
                                           '--pretty=%B',
                                           _tty_out=False,
                                           _cwd="fake/path")

        sh.git.log.reset_mock()
        err = b"fatal: Random git error"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"",
                                                 err)

        expected_msg = "An error occurred while executing 'git log -1 --pretty=%B': {0}".format(
            err)
        with self.assertRaisesRegexp(GitContextError, expected_msg):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1',
                                           '--pretty=%B',
                                           _tty_out=False,
                                           _cwd="fake/path")
Exemplo n.º 2
0
    def test_git_no_commits_error(self, sh):
        # No commits: returned by 'git log'
        err = b"fatal: your current branch 'master' does not have any commits yet"

        sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)

        expected_msg = "Current branch has no commits. Gitlint requires at least one commit to function."
        with self.assertRaisesMessage(GitContextError, expected_msg):
            GitContext.from_local_repository("fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
        sh.git.reset_mock()

        # Unknown reference 'HEAD' commits: returned by 'git rev-parse'
        err = (b"HEAD"
               b"fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree."
               b"Use '--' to separate paths from revisions, like this:"
               b"'git <command> [<revision>...] -- [<file>...]'")

        sh.git.side_effect = [
            "#\n",  # git config --get core.commentchar
            ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err)
        ]

        with self.assertRaisesMessage(GitContextError, expected_msg):
            context = GitContext.from_commit_msg("test")
            context.current_branch

        # assert that commit message was read using git command
        sh.git.assert_called_with("rev-parse", "--abbrev-ref", "HEAD", _tty_out=False, _cwd=None)
Exemplo n.º 3
0
    def test_get_latest_commit_git_error(self, sh):
        # Current directory not a git repo
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.side_effect = ErrorReturnCode("git rev-list --max-count=1 HEAD",
                                             b"", err)

        with self.assertRaisesRegex(GitContextError,
                                    u"fåke/path is not a git repository."):
            GitContext.from_local_repository(u"fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("rev-list",
                                       "--max-count=1",
                                       "HEAD",
                                       _tty_out=False,
                                       _cwd=u"fåke/path")
        sh.git.reset_mock()
        err = b"fatal: Random git error"
        sh.git.side_effect = ErrorReturnCode("git rev-list --max-count=1 HEAD",
                                             b"", err)

        expected_msg = u"An error occurred while executing 'git rev-list --max-count=1 HEAD': {0}".format(
            err)
        with self.assertRaisesRegex(GitContextError, expected_msg):
            GitContext.from_local_repository(u"fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("rev-list",
                                       "--max-count=1",
                                       "HEAD",
                                       _tty_out=False,
                                       _cwd=u"fåke/path")
Exemplo n.º 4
0
    def test_get_latest_commit_git_error(self, sh):
        # Current directory not a git repo
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"",
                                             err)

        with self.assertRaisesRegex(GitContextError,
                                    u"fåke/path is not a git repository."):
            GitContext.from_local_repository(u"fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("log", "-1", "--pretty=%H",
                                       **self.expected_sh_special_args)
        sh.git.reset_mock()
        err = b"fatal: Random git error"
        sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"",
                                             err)

        expected_msg = u"An error occurred while executing 'git log -1 --pretty=%H': {0}".format(
            err)
        with self.assertRaisesRegex(GitContextError, expected_msg):
            GitContext.from_local_repository(u"fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("log", "-1", "--pretty=%H",
                                       **self.expected_sh_special_args)
Exemplo n.º 5
0
    def test_get_latest_commit_command_not_found(self, sh):
        sh.git.log.side_effect = CommandNotFound("git")
        expected_msg = "'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."
        with self.assertRaisesRegexp(GitContextError, expected_msg):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1', '--pretty=%B', _tty_out=False, _cwd="fake/path")
Exemplo n.º 6
0
    def test_get_latest_commit_git_error(self, sh):
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"", err)

        with self.assertRaisesRegexp(GitContextError, "fake/path is not a git repository."):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1', '--pretty=%B', _tty_out=False, _cwd="fake/path")
Exemplo n.º 7
0
    def test_get_latest_commit_command_not_found(self, sh):
        sh.git.side_effect = CommandNotFound("git")
        expected_msg = "'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."
        with self.assertRaisesMessage(GitNotInstalledError, expected_msg):
            GitContext.from_local_repository("fåke/path")

        # assert that commit message was read using git command
        sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
Exemplo n.º 8
0
    def test_get_latest_commit_command_not_found(self, sh):
        sh.git.log.side_effect = CommandNotFound("git")
        expected_msg = "'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."
        with self.assertRaisesRegexp(GitContextError, expected_msg):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1',
                                           '--pretty=%B',
                                           _tty_out=False,
                                           _cwd="fake/path")
Exemplo n.º 9
0
    def test_get_latest_commit_git_error(self, sh):
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"",
                                                 err)

        with self.assertRaisesRegexp(GitContextError,
                                     "fake/path is not a git repository."):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1',
                                           '--pretty=%B',
                                           _tty_out=False,
                                           _cwd="fake/path")
Exemplo n.º 10
0
    def test_get_latest_commit_merge_commit(self, sh):
        sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9"

        sh.git.side_effect = [sample_sha,
                              u"test åuthor\x00test-emå[email protected]\x002016-12-03 15:28:15 01:00\x00åbc def\n"
                              u"Merge \"foo bår commit\"",
                              u"file1.txt\npåth/to/file2.txt\n"]

        context = GitContext.from_local_repository(u"fåke/path")
        # assert that commit info was read using git command
        expected_calls = [
            call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
            call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args),
            call('diff-tree', '--no-commit-id', '--name-only', '-r', sample_sha, **self.expected_sh_special_args)
        ]

        self.assertEqual(sh.git.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title, u"Merge \"foo bår commit\"")
        self.assertEqual(last_commit.message.body, [])
        self.assertEqual(last_commit.author_name, u"test åuthor")
        self.assertEqual(last_commit.author_email, u"test-emå[email protected]")
        self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
                                                             tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
        self.assertListEqual(last_commit.parents, [u"åbc", "def"])
        self.assertTrue(last_commit.is_merge_commit)
        self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
Exemplo n.º 11
0
    def test_from_local_repository_specific_ref(self, sh):
        sample_sha = "myspecialref"

        sh.git.side_effect = [sample_sha,
                              u"test åuthor\x00test-emå[email protected]\x002016-12-03 15:28:15 01:00\x00åbc\n"
                              u"cömmit-title\n\ncömmit-body",
                              u"file1.txt\npåth/to/file2.txt\n"]

        context = GitContext.from_local_repository(u"fåke/path", sample_sha)
        # assert that commit info was read using git command
        expected_calls = [
            call("rev-list", sample_sha, **self.expected_sh_special_args),
            call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args),
            call('diff-tree', '--no-commit-id', '--name-only', '-r', sample_sha, **self.expected_sh_special_args)
        ]
        self.assertListEqual(sh.git.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title, u"cömmit-title")
        self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
        self.assertEqual(last_commit.author_name, u"test åuthor")
        self.assertEqual(last_commit.author_email, u"test-emå[email protected]")
        self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
                                                             tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
        self.assertListEqual(last_commit.parents, [u"åbc"])
        self.assertFalse(last_commit.is_merge_commit)
        self.assertFalse(last_commit.is_fixup_commit)
        self.assertFalse(last_commit.is_squash_commit)
        self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
Exemplo n.º 12
0
    def test_get_latest_commit(self, sh):
        def git_log_side_effect(*args, **kwargs):
            return_values = {'--pretty=%B': "commit-title\n\ncommit-body", '--pretty=%aN': "test author",
                             '--pretty=%aE': "*****@*****.**", '--pretty=%aD': "Mon Feb 29 22:19:39 2016 +0100"}
            return return_values[args[1]]

        sh.git.log.side_effect = git_log_side_effect
        sh.git.return_value = "file1.txt\npath/to/file2.txt\n"

        context = GitContext.from_local_repository("fake/path")
        expected_sh_special_args = {
            '_tty_out': False,
            '_cwd': "fake/path"
        }
        # assert that commit info was read using git command
        expected_calls = [call('-1', '--pretty=%B', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%aN', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%aE', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%aD', _cwd='fake/path', _tty_out=False)]

        self.assertListEqual(sh.git.log.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title, "commit-title")
        self.assertEqual(last_commit.message.body, ["", "commit-body"])
        self.assertEqual(last_commit.author_name, "test author")
        self.assertEqual(last_commit.author_email, "*****@*****.**")

        # assert that changed files are read using git command
        sh.git.assert_called_once_with('diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD',
                                       **expected_sh_special_args)
        self.assertListEqual(last_commit.changed_files, ["file1.txt", "path/to/file2.txt"])
Exemplo n.º 13
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj[0]
    try:
        if sys.stdin.isatty():
            # If target has not been set explicitly before, fallback to the current directory
            gitcontext = GitContext.from_local_repository(lint_config.target)
        else:
            gitcontext = GitContext.from_commit_msg(sys.stdin.read())
    except GitContextError as e:
        click.echo(str(e))
        ctx.exit(GIT_CONTEXT_ERROR_CODE)

    config_builder = ctx.obj[1]
    last_commit = gitcontext.commits[-1]
    # Apply an additional config that is specified in the last commit message
    config_builder.set_config_from_commit(last_commit)
    lint_config = config_builder.build(lint_config)

    # Let's get linting!
    linter = GitLinter(lint_config)
    violations = linter.lint(last_commit)
    linter.print_violations(violations)
    exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations))
    ctx.exit(exit_code)
Exemplo n.º 14
0
    def test_get_latest_commit_fixup_squash_commit(self, sh):
        commit_types = ["fixup", "squash"]
        for commit_type in commit_types:
            sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9"

            sh.git.side_effect = [
                sample_sha,
                "test åuthor\x00test-emå[email protected]\x002016-12-03 15:28:15 +0100\x00åbc\n"
                f"{commit_type}! \"foo bår commit\"",
                "#",  # git config --get core.commentchar
                "file1.txt\npåth/to/file2.txt\n",
                "foöbar\n* hürdur\n"
            ]

            context = GitContext.from_local_repository("fåke/path")
            # assert that commit info was read using git command
            expected_calls = [
                call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
                call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args),
                call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args),
                call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha,
                     **self.expected_sh_special_args),
                call('branch', '--contains', sample_sha, **self.expected_sh_special_args)
            ]

            # Only first 'git log' call should've happened at this point
            self.assertEqual(sh.git.mock_calls, expected_calls[:-4])

            last_commit = context.commits[-1]
            self.assertIsInstance(last_commit, LocalGitCommit)
            self.assertEqual(last_commit.sha, sample_sha)
            self.assertEqual(last_commit.message.title, f"{commit_type}! \"foo bår commit\"")
            self.assertEqual(last_commit.message.body, [])
            self.assertEqual(last_commit.author_name, "test åuthor")
            self.assertEqual(last_commit.author_email, "test-emå[email protected]")
            self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
                                                                 tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
            self.assertListEqual(last_commit.parents, ["åbc"])

            # First 2 'git log' calls should've happened at this point
            self.assertEqual(sh.git.mock_calls, expected_calls[:3])

            # Asserting that squash and fixup are correct
            for type in commit_types:
                attr = "is_" + type + "_commit"
                self.assertEqual(getattr(last_commit, attr), commit_type == type)

            self.assertFalse(last_commit.is_merge_commit)
            self.assertFalse(last_commit.is_revert_commit)
            self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])

            self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
            # 'git diff-tree' should have happened at this point
            self.assertListEqual(sh.git.mock_calls, expected_calls[:4])

            self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
            # All expected calls should've happened at this point
            self.assertListEqual(sh.git.mock_calls, expected_calls)

            sh.git.reset_mock()
Exemplo n.º 15
0
def build_git_context(lint_config, msg_filename, refspec):
    """ Builds a git context based on passed parameters and order of precedence """

    # Determine which GitContext method to use if a custom message is passed
    from_commit_msg = GitContext.from_commit_msg
    if lint_config.staged:
        LOG.debug("Fetching additional meta-data from staged commit")
        from_commit_msg = lambda message: GitContext.from_staged_commit(
            message, lint_config.target)  # noqa

    # Order of precedence:
    # 1. Any data specified via --msg-filename
    if msg_filename:
        LOG.debug("Using --msg-filename.")
        return from_commit_msg(str(msg_filename.read()))

    # 2. Any data sent to stdin (unless stdin is being ignored)
    if not lint_config.ignore_stdin:
        stdin_input = get_stdin_data()
        if stdin_input:
            LOG.debug("Stdin data: '%s'", stdin_input)
            LOG.debug("Stdin detected and not ignored. Using as input.")
            return from_commit_msg(stdin_input)

    if lint_config.staged:
        raise GitLintUsageError(
            "The 'staged' option (--staged) can only be used when using '--msg-filename' or "
            "when piping data to gitlint via stdin.")

    # 3. Fallback to reading from local repository
    LOG.debug(
        "No --msg-filename flag, no or empty data passed to stdin. Using the local repo."
    )
    return GitContext.from_local_repository(lint_config.target, refspec)
Exemplo n.º 16
0
    def test_get_latest_commit_fixup_squash_commit(self, sh):
        commit_types = ["fixup", "squash"]
        for commit_type in commit_types:
            sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9"

            sh.git.side_effect = self.GIT_CONFIG_SIDE_EFFECTS + [
                sample_sha,
                u"test åuthor\x00test-emå[email protected]\x002016-12-03 15:28:15 01:00\x00åbc\n"
                u"{0}! \"foo bår commit\"".format(commit_type),
                u"file1.txt\npåth/to/file2.txt\n"
            ]

            context = GitContext.from_local_repository(u"fåke/path")
            # assert that commit info was read using git command
            expected_calls = self.expected_git_config_calls + [
                call("log", "-1", "--pretty=%H", **
                     self.expected_sh_special_args),
                call("log", sample_sha, "-1",
                     "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **
                     self.expected_sh_special_args),
                call('diff-tree', '--no-commit-id', '--name-only', '-r',
                     sample_sha, **self.expected_sh_special_args)
            ]

            self.assertEqual(sh.git.mock_calls, expected_calls)

            last_commit = context.commits[-1]
            self.assertEqual(last_commit.message.title,
                             u"{0}! \"foo bår commit\"".format(commit_type))
            self.assertEqual(last_commit.message.body, [])
            self.assertEqual(last_commit.author_name, u"test åuthor")
            self.assertEqual(last_commit.author_email, u"test-emå[email protected]")
            self.assertEqual(
                last_commit.date,
                datetime.datetime(2016,
                                  12,
                                  3,
                                  15,
                                  28,
                                  15,
                                  tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
            self.assertListEqual(last_commit.parents, [u"åbc"])

            # Asserting that squash and fixup are correct
            for type in commit_types:
                attr = "is_" + type + "_commit"
                self.assertEqual(getattr(last_commit, attr),
                                 commit_type == type)

            self.assertFalse(last_commit.is_merge_commit)
            self.assertFalse(last_commit.is_revert_commit)
            self.assertListEqual(last_commit.changed_files,
                                 ["file1.txt", u"påth/to/file2.txt"])

            sh.git.reset_mock()
Exemplo n.º 17
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj[0]
    try:
        if sys.stdin.isatty():
            # If target has not been set explicitly before, fallback to the current directory
            gitcontext = GitContext.from_local_repository(lint_config.target, ctx.obj[2])
        else:
            stdin_str = ustr(sys.stdin.read())
            gitcontext = GitContext.from_commit_msg(stdin_str)
    except GitContextError as e:
        click.echo(ustr(e))
        ctx.exit(GIT_CONTEXT_ERROR_CODE)

    number_of_commits = len(gitcontext.commits)
    # Exit if we don't have commits in the specified range. Use a 0 exit code, since a popular use-case is one
    # where users are using --commits in a check job to check the commit messages inside a CI job. By returning 0, we
    # ensure that these jobs don't fail if for whatever reason the specified commit range is empty.
    if number_of_commits == 0:
        click.echo(u'No commits in range "{0}".'.format(ctx.obj[2]))
        ctx.exit(0)

    general_config_builder = ctx.obj[1]
    last_commit = gitcontext.commits[-1]

    # Let's get linting!
    first_violation = True
    exit_code = 0
    for commit in gitcontext.commits:
        # Build a config_builder and linter taking into account the commit specific config (if any)
        config_builder = general_config_builder.clone()
        config_builder.set_config_from_commit(commit)
        lint_config = config_builder.build(lint_config)
        linter = GitLinter(lint_config)

        # Actually do the linting
        violations = linter.lint(commit)
        # exit code equals the total number of violations in all commits
        exit_code += len(violations)
        if violations:
            # Display the commit hash & new lines intelligently
            if number_of_commits > 1 and commit.sha:
                linter.display.e(u"{0}Commit {1}:".format(
                    "\n" if not first_violation or commit is last_commit else "",
                    commit.sha[:10]
                ))
            linter.print_violations(violations)
            first_violation = False

    # cap actual max exit code because bash doesn't like exit codes larger than 255:
    # http://tldp.org/LDP/abs/html/exitcodes.html
    exit_code = min(MAX_VIOLATION_ERROR_CODE, exit_code)
    LOG.debug("Exit Code = %s", exit_code)
    ctx.exit(exit_code)
Exemplo n.º 18
0
    def test_get_latest_commit_git_error(self, sh):
        # Current directory not a git repo
        err = b"fatal: Not a git repository (or any of the parent directories): .git"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"", err)

        with self.assertRaisesRegex(GitContextError, "fake/path is not a git repository."):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1', '--pretty=%B', _tty_out=False, _cwd="fake/path")

        sh.git.log.reset_mock()
        err = b"fatal: Random git error"
        sh.git.log.side_effect = ErrorReturnCode("git log -1 --pretty=%B", b"", err)

        expected_msg = "An error occurred while executing 'git log -1 --pretty=%B': {0}".format(err)
        with self.assertRaisesRegex(GitContextError, expected_msg):
            GitContext.from_local_repository("fake/path")

        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1', '--pretty=%B', _tty_out=False, _cwd="fake/path")
Exemplo n.º 19
0
    def test_from_local_repository_specific_ref(self, sh):
        sample_sha = "myspecialref"

        sh.git.side_effect = [
            sample_sha,
            "test åuthor\x00test-emå[email protected]\x002016-12-03 15:28:15 +0100\x00åbc\n"
            "cömmit-title\n\ncömmit-body",
            "#",  # git config --get core.commentchar
            "file1.txt\npåth/to/file2.txt\n",
            "foöbar\n* hürdur\n"
        ]

        context = GitContext.from_local_repository("fåke/path", sample_sha)
        # assert that commit info was read using git command
        expected_calls = [
            call("rev-list", sample_sha, **self.expected_sh_special_args),
            call("log", sample_sha, "-1", "--pretty=%aN%x00%aE%x00%ai%x00%P%n%B", **self.expected_sh_special_args),
            call('config', '--get', 'core.commentchar', _ok_code=[0, 1], **self.expected_sh_special_args),
            call('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', sample_sha,
                 **self.expected_sh_special_args),
            call('branch', '--contains', sample_sha, **self.expected_sh_special_args)
        ]

        # Only first 'git log' call should've happened at this point
        self.assertEqual(sh.git.mock_calls, expected_calls[:1])

        last_commit = context.commits[-1]
        self.assertIsInstance(last_commit, LocalGitCommit)
        self.assertEqual(last_commit.sha, sample_sha)
        self.assertEqual(last_commit.message.title, "cömmit-title")
        self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
        self.assertEqual(last_commit.author_name, "test åuthor")
        self.assertEqual(last_commit.author_email, "test-emå[email protected]")
        self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
                                                             tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
        self.assertListEqual(last_commit.parents, ["åbc"])
        self.assertFalse(last_commit.is_merge_commit)
        self.assertFalse(last_commit.is_fixup_commit)
        self.assertFalse(last_commit.is_squash_commit)
        self.assertFalse(last_commit.is_revert_commit)

        # First 2 'git log' calls should've happened at this point
        self.assertListEqual(sh.git.mock_calls, expected_calls[:3])

        self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
        # 'git diff-tree' should have happened at this point
        self.assertListEqual(sh.git.mock_calls, expected_calls[:4])

        self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
        # All expected calls should've happened at this point
        self.assertListEqual(sh.git.mock_calls, expected_calls)
Exemplo n.º 20
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj[0]
    try:
        if sys.stdin.isatty():
            # If target has not been set explicitly before, fallback to the current directory
            gitcontext = GitContext.from_local_repository(
                lint_config.target, ctx.obj[2])
        else:
            stdin_str = ustr(sys.stdin.read())
            gitcontext = GitContext.from_commit_msg(stdin_str)
    except GitContextError as e:
        click.echo(ustr(e))
        ctx.exit(GIT_CONTEXT_ERROR_CODE)

    number_of_commits = len(gitcontext.commits)

    # Exit if we don't have commits in the specified range. Use a 0 exit code, since a popular use-case is one
    # where users are using --commits in a check job to check the commit messages inside a CI job. By returning 0, we
    # ensure that these jobs don't fail if for whatever reason the specified commit range is empty.

    if number_of_commits == 0:
        click.echo(u'No commits in range "{0}".'.format(ctx.obj[2]))
        ctx.exit(0)

    config_builder = ctx.obj[1]
    last_commit = gitcontext.commits[-1]
    # Apply an additional config that is specified in the last commit message
    config_builder.set_config_from_commit(last_commit)
    lint_config = config_builder.build(lint_config)

    # Let's get linting!
    linter = GitLinter(lint_config)
    first_violation = True

    for commit in gitcontext.commits:
        violations = linter.lint(commit)
        if violations:
            # Display the commit hash & new lines intelligently
            if number_of_commits > 1 and commit.sha:
                click.echo(u"{0}Commit {1}:".format(
                    "\n" if not first_violation or commit is last_commit else
                    "", commit.sha[:10]))
            linter.print_violations(violations)
            first_violation = False

    exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations))
    ctx.exit(exit_code)
Exemplo n.º 21
0
    def test_get_latest_commit_merge_commit(self, sh):
        sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9"

        def git_log_side_effect(*_args, **_kwargs):
            return (
                u"test åuthor,test-emå[email protected],2016-12-03 15:28:15 01:00,åbc def\n"
                u"Merge \"foo bår commit\"")

        sh.git.side_effect = [sample_sha, u"file1.txt\npåth/to/file2.txt\n"]
        sh.git.log.side_effect = git_log_side_effect

        context = GitContext.from_local_repository(u"fåke/path")
        expected_sh_special_args = {'_tty_out': False, '_cwd': u"fåke/path"}
        # assert that commit info was read using git command
        expected_calls = [
            call("rev-list", "--max-count=1", "HEAD",
                 **expected_sh_special_args),
            call.log(sample_sha,
                     "-1",
                     "--pretty=%aN,%aE,%ai,%P%n%B",
                     _cwd=u"fåke/path",
                     _tty_out=False),
            call('diff-tree', '--no-commit-id', '--name-only', '-r',
                 sample_sha, **expected_sh_special_args)
        ]

        self.assertEqual(sh.git.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title,
                         u"Merge \"foo bår commit\"")
        self.assertEqual(last_commit.message.body, [])
        self.assertEqual(last_commit.author_name, u"test åuthor")
        self.assertEqual(last_commit.author_email, u"test-emå[email protected]")
        self.assertEqual(
            last_commit.date,
            datetime.datetime(2016,
                              12,
                              3,
                              15,
                              28,
                              15,
                              tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
        self.assertListEqual(last_commit.parents, [u"åbc", "def"])
        self.assertTrue(last_commit.is_merge_commit)
        self.assertListEqual(last_commit.changed_files,
                             ["file1.txt", u"påth/to/file2.txt"])
Exemplo n.º 22
0
    def test_get_latest_commit(self, sh):
        sh.git.log.return_value = "commit-title\n\ncommit-body"
        sh.git.return_value = "file1.txt\npath/to/file2.txt\n"

        context = GitContext.from_local_repository("fake/path")
        expected_sh_special_args = {
            '_tty_out': False,
            '_cwd': "fake/path"
        }
        # assert that commit message was read using git command
        sh.git.log.assert_called_once_with('-1', '--pretty=%B', **expected_sh_special_args)
        self.assertEqual(context.commit_msg.title, "commit-title")
        self.assertEqual(context.commit_msg.body, ["", "commit-body"])

        # assert that changed files are read using git command
        sh.git.assert_called_once_with('diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD',
                                       **expected_sh_special_args)
        self.assertListEqual(context.changed_files, ["file1.txt", "path/to/file2.txt"])
Exemplo n.º 23
0
    def test_get_latest_commit(self, sh):
        def git_log_side_effect(*args, **_kwargs):
            return_values = {
                '--pretty=%B': "commit-title\n\ncommit-body",
                '--pretty=%aN': "test author",
                '--pretty=%aE': "*****@*****.**",
                '--pretty=%aD': "Mon Feb 29 22:19:39 2016 +0100",
                '--pretty=%P': "abc"
            }
            return return_values[args[1]]

        sh.git.log.side_effect = git_log_side_effect
        sh.git.return_value = "file1.txt\npath/to/file2.txt\n"

        context = GitContext.from_local_repository("fake/path")
        expected_sh_special_args = {'_tty_out': False, '_cwd': "fake/path"}
        # assert that commit info was read using git command
        expected_calls = [
            call('-1', '--pretty=%B', _cwd='fake/path', _tty_out=False),
            call('-1', '--pretty=%aN', _cwd='fake/path', _tty_out=False),
            call('-1', '--pretty=%aE', _cwd='fake/path', _tty_out=False),
            call('-1', '--pretty=%aD', _cwd='fake/path', _tty_out=False),
            call('-1', '--pretty=%P', _cwd='fake/path', _tty_out=False)
        ]

        self.assertListEqual(sh.git.log.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title, "commit-title")
        self.assertEqual(last_commit.message.body, ["", "commit-body"])
        self.assertEqual(last_commit.author_name, "test author")
        self.assertEqual(last_commit.author_email, "*****@*****.**")
        self.assertListEqual(last_commit.parents, ["abc"])
        self.assertFalse(last_commit.is_merge_commit)

        # assert that changed files are read using git command
        sh.git.assert_called_once_with('diff-tree', '--no-commit-id',
                                       '--name-only', '-r', 'HEAD',
                                       **expected_sh_special_args)
        self.assertListEqual(last_commit.changed_files,
                             ["file1.txt", "path/to/file2.txt"])
Exemplo n.º 24
0
def build_git_context(lint_config, msg_filename, refspec):
    """ Builds a git context based on passed parameters and order of precedence """
    # Order of precedence:
    # 1. Any data specified via --msg-filename
    if msg_filename:
        LOG.debug("Attempting to read from --msg-filename.")
        return GitContext.from_commit_msg(ustr(msg_filename.read()))

    # 2. Any data sent to stdin (unless stdin is being ignored)
    if not lint_config.ignore_stdin:
        stdin_input = get_stdin_data()
        if stdin_input:
            LOG.debug("Stdin data: %r", stdin_input)
            LOG.debug("Stdin detected and not ignored. Will be used as input.")
            return GitContext.from_commit_msg(stdin_input)

    # 3. Fallback to reading from local repository
    LOG.debug(
        "No --msg-filename flag, no or empty data passed to stdin. Attempting to read from the local repo."
    )
    return GitContext.from_local_repository(lint_config.target, refspec)
Exemplo n.º 25
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj
    try:
        if sys.stdin.isatty():
            gitcontext = GitContext.from_local_repository(lint_config.target)
        else:
            gitcontext = GitContext.from_commit_msg(sys.stdin.read())
    except GitContextError as e:
        click.echo(str(e))
        ctx.exit(GIT_CONTEXT_ERROR_CODE)

    last_commit = gitcontext.commits[-1]
    # Apply an additional config that is specified in the last commit message
    lint_config.apply_config_from_commit(last_commit)

    # Let's get linting!
    linter = GitLinter(lint_config)
    violations = linter.lint(last_commit)
    linter.print_violations(violations)
    exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations))
    ctx.exit(exit_code)
Exemplo n.º 26
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj
    try:
        if sys.stdin.isatty():
            gitcontext = GitContext.from_local_repository(lint_config.target)
        else:
            gitcontext = GitContext.from_commit_msg(sys.stdin.read())
    except GitContextError as e:
        click.echo(str(e))
        ctx.exit(GIT_CONTEXT_ERROR_CODE)

    last_commit = gitcontext.commits[-1]
    # Apply an additional config that is specified in the last commit message
    lint_config.apply_config_from_commit(last_commit)

    # Let's get linting!
    linter = GitLinter(lint_config)
    violations = linter.lint(last_commit, gitcontext)
    linter.print_violations(violations)
    exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations))
    ctx.exit(exit_code)
Exemplo n.º 27
0
    def test_get_latest_commit_merge_commit(self, sh):
        def git_log_side_effect(*args, **_kwargs):
            return_values = {'--pretty=%B': "Merge \"foo bar commit\"", '--pretty=%aN': "test author",
                             '--pretty=%aE': "*****@*****.**", '--pretty=%ai': "2016-12-03 15:28:15 01:00",
                             '--pretty=%P': "abc def"}
            return return_values[args[1]]

        sh.git.log.side_effect = git_log_side_effect
        sh.git.return_value = "file1.txt\npath/to/file2.txt\n"

        context = GitContext.from_local_repository("fake/path")
        expected_sh_special_args = {
            '_tty_out': False,
            '_cwd': "fake/path"
        }
        # assert that commit info was read using git command
        expected_calls = [call('-1', '--pretty=%B', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%aN', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%aE', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%ai', _cwd='fake/path', _tty_out=False),
                          call('-1', '--pretty=%P', _cwd='fake/path', _tty_out=False)]

        self.assertListEqual(sh.git.log.mock_calls, expected_calls)

        last_commit = context.commits[-1]
        self.assertEqual(last_commit.message.title, "Merge \"foo bar commit\"")
        self.assertEqual(last_commit.message.body, [])
        self.assertEqual(last_commit.author_name, "test author")
        self.assertEqual(last_commit.author_email, "*****@*****.**")
        self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
                                                             tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
        self.assertListEqual(last_commit.parents, ["abc", "def"])
        self.assertTrue(last_commit.is_merge_commit)

        # assert that changed files are read using git command
        sh.git.assert_called_once_with('diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD',
                                       **expected_sh_special_args)
        self.assertListEqual(last_commit.changed_files, ["file1.txt", "path/to/file2.txt"])
Exemplo n.º 28
0
def lint(ctx):
    """ Lints a git repository [default command] """
    lint_config = ctx.obj[0]
    msg_filename = ctx.obj[3]

    # Let's determine where our input data is coming from:
    # Order of precedence:
    # 1. Any data specified via --msg-filename
    # 2. Any data sent to stdin
    # 3. Fallback to reading from local repository
    stdin_input = get_stdin_data()
    if msg_filename:
        LOG.debug("Attempting to read from --msg-filename.")
        gitcontext = GitContext.from_commit_msg(ustr(msg_filename.read()))
    elif stdin_input:
        LOG.debug("No --msg-filename flag. Attempting to read from stdin.")
        gitcontext = GitContext.from_commit_msg(stdin_input)
    else:
        LOG.debug(
            "No --msg-filename flag, no or empty data passed to stdin. Attempting to read from the local repo."
        )
        gitcontext = GitContext.from_local_repository(lint_config.target,
                                                      ctx.obj[2])

    number_of_commits = len(gitcontext.commits)
    # Exit if we don't have commits in the specified range. Use a 0 exit code, since a popular use-case is one
    # where users are using --commits in a check job to check the commit messages inside a CI job. By returning 0, we
    # ensure that these jobs don't fail if for whatever reason the specified commit range is empty.
    if number_of_commits == 0:
        LOG.debug(u'No commits in range "%s"', ctx.obj[2])
        ctx.exit(0)

    general_config_builder = ctx.obj[1]
    last_commit = gitcontext.commits[-1]

    # Let's get linting!
    first_violation = True
    exit_code = 0
    for commit in gitcontext.commits:
        # Build a config_builder taking into account the commit specific config (if any)
        config_builder = general_config_builder.clone()
        config_builder.set_config_from_commit(commit)

        # Create a deepcopy from the original config, so we have a unique config object per commit
        # This is important for configuration rules to be able to modifying the config on a per commit basis
        commit_config = config_builder.build(copy.deepcopy(lint_config))

        # Actually do the linting
        linter = GitLinter(commit_config)
        violations = linter.lint(commit)
        # exit code equals the total number of violations in all commits
        exit_code += len(violations)
        if violations:
            # Display the commit hash & new lines intelligently
            if number_of_commits > 1 and commit.sha:
                linter.display.e(u"{0}Commit {1}:".format(
                    "\n" if not first_violation or commit is last_commit else
                    "", commit.sha[:10]))
            linter.print_violations(violations)
            first_violation = False

    # cap actual max exit code because bash doesn't like exit codes larger than 255:
    # http://tldp.org/LDP/abs/html/exitcodes.html
    exit_code = min(MAX_VIOLATION_ERROR_CODE, exit_code)
    LOG.debug("Exit Code = %s", exit_code)
    ctx.exit(exit_code)