async def test_new_upstream_commits_to_pull_down(mock_input_a):
    """
    Test case for "origin/review-branch has new commits that I must pull down"
    (no new local commits that origin doesn't have yet)
    """
    with temp_repo() as upstream:
        upstream_default_branch = upstream.active_branch
        upstream_feature_branch = upstream.create_head("feature-123")
        # upstream_feature_branch.checkout()
        with temp_repo_clone(upstream, ['feature-123']) as downstream:
            assert ", ".join(sorted_repo_branch_names(
                downstream)) == f"feature-123, {upstream_default_branch.name}"
            upstream_feature_branch.checkout()
            create_git_history(upstream, [
                (('file_0.txt', 'content for file 0'), 'second commit'),
                (('file_1.txt', 'content for file 1'), 'third commit'),
                (('file_2.txt', 'content for file 2'), 'fourth commit'),
            ])
            upstream_default_branch.checkout()
            assert upstream.active_branch.name in ['main', 'master']
            downstream.heads['feature-123'].checkout()
            opts = ValidateOptions(
                ValidateCLIOptions(verbose=False,
                                   cwd=downstream.working_dir,
                                   auto_fetch=True))
            assert opts.is_verbose() == False
            with fake_cliux(log_level=INFO) as (cli, get_lines):
                assert cli.log_level == INFO
                await do_validate(cli=cli, opts=opts)
                assert strip_ansi(
                    "".join(get_lines())
                ) == """determined that local branch feature-123 tracks upstream branch feature-123 on remote origin
Example #2
0
def determine_whether_to_auto_fetch(
        cli: CLIUX, opts: ValidateOptions,
        active_branch_tracked_ref: RemoteReference):
    user_response = ""
    if opts.is_auto_fetch_enabled() == True:
        cli.debug(
            'User provided --auto-fetch CLI argument. Proceeding as instructed'
        )
        user_response = 'y'
    elif opts.is_auto_fetch_enabled() == False:
        cli.debug(
            'User provided --no-auto-fetch CLI argument. Halting as instructed'
        )
        raise UserBypassException(
            f"""user decided not to download new commits from {
                active_branch_tracked_ref.name}""")

    while user_response not in ['y', 'Y']:
        user_response = input(
            "Would you like to download these new commits? [y/n]")
        if user_response in ['N', 'n']:
            cli.debug(
                f"When asked whether we can download new commits, user response was {user_response}"
            )
            raise UserBypassException(
                f"""user decided not to download new commits from {
                active_branch_tracked_ref.name}""")
        if user_response not in ['y', 'Y']:
            cli.warning(
                f"Invalid response detected: '{user_response}'. Please answer 'Y' or 'N'."
            )
async def validate(verbose: bool, cwd: str, enabled: bool, current_branch: str,
                   color: bool, tty: bool, auto_fetch: bool,
                   commit_count_soft_fail_threshold: bool,
                   commit_count_hard_fail_threshold: bool,
                   commit_count_auto_bypass_soft_fail: bool):
    """Examine the current Git workspace and perform some sanity-checking"""
    cliOptions = ValidateCLIOptions(
        verbose=verbose,
        cwd=cwd,
        current_branch=current_branch,
        color=color,
        tty=tty,
        auto_fetch=auto_fetch,
        commit_count_soft_fail_threshold=commit_count_soft_fail_threshold,
        commit_count_hard_fail_threshold=commit_count_hard_fail_threshold,
        commit_count_auto_bypass_soft_fail=commit_count_auto_bypass_soft_fail)
    opts = ValidateOptions(cliOptions)
    log_level = DEBUG if opts.is_verbose() else INFO
    cli = CLIUX(log_level=log_level,
                supports_color=opts.is_terminal_color_supported(),
                supports_tty=opts.is_terminal_tty_supported())
    if (enabled == False):
        print(
            "skipping validation, due to '--no-enabled' CLI arg, or GIT_GUARDRAILS_ENABLED=False env variable"
        )
        return
    await do_validate(cli, opts)
async def test_branch_name_infer():
    with temp_repo() as repo:
        repo.index.commit("first commit")
        repo.create_head("example_branch")
        repo.git.checkout("example_branch")
        o = ValidateOptions(ValidateCLIOptions())
        assert await o.get_current_branch_name(repo) == "example_branch", "absence of --current-branch results in inference"
async def test_branch_name_passthrough():
    with temp_repo() as repo:
        repo.index.commit("first commit")
        repo.create_head("special_branch")
        repo.git.checkout("special_branch")
        o = ValidateOptions(ValidateCLIOptions(verbose=True, current_branch="fizz"))
        assert await o.get_current_branch_name(repo) == "fizz", "--current-branch supercedes git repo state"
async def test_review_branch_not_yet_pushed(mock_input):
    """
    Test case for a new review branch that does not yet track any remote branch
    (presumably, it hasn't yet been pushed)
    """
    with temp_repo() as upstream:
        merge_base = upstream.active_branch.commit
        assert merge_base is not None
        with temp_repo_clone(upstream) as downstream:
            new_branch = downstream.create_head("feature-123")
            new_branch.checkout()
            assert downstream.active_branch.name == "feature-123"
            create_git_history(
                downstream,
                [(('my-file.txt', 'sample content'), 'second commit')])
            assert downstream.heads['feature-123'] is not None
            downstream_default_branch = git_default_branch(downstream)
            assert downstream_default_branch == upstream.active_branch.name
            with fake_cliux(log_level=INFO) as (cli, get_lines):
                opts = ValidateOptions(
                    ValidateCLIOptions(verbose=True,
                                       cwd=downstream.working_dir))
                await do_validate(cli=cli, opts=opts)
                assert strip_ansi(
                    "".join(get_lines())
                ) == """git_guardrails has completed without taking any action.
async def test_to_dict():
    with temp_repo() as repo:
        repo.index.commit("first commit")
        o = ValidateOptions(ValidateCLIOptions(verbose=True, cwd="foo", current_branch="fizz"))
        d = await o.to_dict(repo)
        assert d["verbose"] == True
        assert d["cwd"] == "foo"
        assert d["current_branch"] == "fizz"
async def test_do_validate_no_remote(mock_input):
    """
    Test case for repo w/o any git remotes
    """
    with temp_repo() as upstream:
        with fake_cliux() as (cli, get_lines):
            opts = ValidateOptions(
                ValidateCLIOptions(verbose=True, cwd=upstream.working_dir))
            await do_validate(cli=cli, opts=opts)
            assert strip_ansi("".join(get_lines(
            ))) == """git_guardrails has completed without taking any action.
async def test_need_to_fetch_only_upstream_commits(mock_input):
    with setup_need_to_fetch_scenario() as (upstream, downstream):
        assert upstream.is_dirty() == False
        downstream.heads['review-999'].checkout()
        with fake_cliux() as (cli, get_lines):
            opts = ValidateOptions(
                ValidateCLIOptions(verbose=True,
                                   auto_fetch=True,
                                   cwd=downstream.working_dir))
            await do_validate(cli=cli, opts=opts)
            assert "[WARNING]: New commits on origin/review-999 were detected" in strip_ansi(
                "".join(get_lines()))
async def test_need_to_fetch_only_upstream_commits_user_refuses(mock_input):
    with setup_need_to_fetch_scenario() as (upstream, downstream):
        assert upstream.is_dirty() == False
        downstream.heads['review-999'].checkout()
        with fake_cliux() as (cli, get_lines):
            opts = ValidateOptions(
                ValidateCLIOptions(verbose=True,
                                   auto_fetch=False,
                                   cwd=downstream.working_dir))
            await do_validate(cli=cli, opts=opts)
            assert strip_ansi(
                "".join(get_lines())
            ) == """determined that local branch review-999 tracks upstream branch review-999 on remote origin
async def test_push_from_default_branch(mock_input):
    """
    Test case for "push while on the default branch instead of a review branch"
    """
    with temp_repo() as upstream:
        with temp_repo_clone(upstream) as downstream:
            with fake_cliux(log_level=INFO) as (cli, get_lines):
                opts = ValidateOptions(
                    ValidateCLIOptions(verbose=False,
                                       cwd=downstream.working_dir))
                await do_validate(cli=cli, opts=opts)
                assert strip_ansi(
                    "".join(get_lines())
                ) == """git_guardrails has completed without taking any action.
Example #12
0
def test_tty_arg_infer():
    """
    absence of --tty argument results in inference
    """
    o = ValidateOptions(ValidateCLIOptions())

    callback_invocation_count = 0

    def fake_tty_detector() -> bool:
        nonlocal callback_invocation_count
        callback_invocation_count += 1
        return True

    def fake_no_tty_detector() -> bool:
        nonlocal callback_invocation_count
        callback_invocation_count += 1
        return False

    assert callback_invocation_count == 0
    assert o.is_terminal_tty_supported(fake_tty_detector) == True
    assert callback_invocation_count == 1
    assert o.is_terminal_tty_supported(fake_no_tty_detector) == False
    assert callback_invocation_count == 2
async def test_no_connect_to_remote(mock_input):
    """
    Test case for "can't connect to git remote"
    """
    with pytest.raises(GitRemoteConnectivityException):
        with temp_repo() as upstream:
            with temp_repo_clone(upstream) as downstream:
                downstream_origin: Remote = downstream.remotes['origin']
                assert downstream_origin is not None
                downstream_origin.set_url(new_url='https://example.com')
                with fake_cliux(log_level=INFO) as (cli, get_lines):
                    opts = ValidateOptions(
                        ValidateCLIOptions(verbose=False,
                                           cwd=downstream.working_dir))
                    await do_validate(cli=cli, opts=opts)
                    assert strip_ansi("".join(get_lines())) == ""
async def test_review_branch_commit_count_hard_fail():
    try:
        with temp_repo() as upstream:
            upstream_default_branch = upstream.active_branch
            upstream.create_head('mnorth-review-111')
            create_git_history(upstream, [
                (("demo_0.txt", "content for demo_0"), "demo 0 commit"),
                (("demo_1.txt", "content for demo_1"), "demo 1 commit"),
                (("demo_2.txt", "content for demo_2"), "demo 2 commit"),
                (("demo_3.txt", "content for demo_3"), "demo 3 commit"),
            ])
            upstream_default_branch.checkout()
            with temp_repo_clone(upstream,
                                 ['mnorth-review-111']) as downstream:
                # downstream_default_branch = downstream.active_branch
                downstream_review_branch = downstream.heads[
                    'mnorth-review-111']
                assert downstream_review_branch is not None
                downstream_review_branch.checkout()
                create_git_history(downstream, [
                    (("demo_4.txt", "content for demo_4"), "demo 4 commit"),
                    (("demo_5.txt", "content for demo_5"), "demo 5 commit"),
                    (("demo_6.txt", "content for demo_6"), "demo 6 commit"),
                    (("demo_7.txt", "content for demo_7"), "demo 7 commit"),
                    (("demo_8.txt", "content for demo_8"), "demo 8 commit"),
                    (("demo_9.txt", "content for demo_9"), "demo 9 commit"),
                    (("demo_10.txt", "content for demo_10"), "demo 10 commit"),
                    (("demo_11.txt", "content for demo_11"), "demo 11 commit"),
                    (("demo_12.txt", "content for demo_12"), "demo 12 commit"),
                    (("demo_13.txt", "content for demo_13"), "demo 13 commit"),
                    (("demo_14.txt", "content for demo_14"), "demo 14 commit"),
                    (("demo_15.txt", "content for demo_15"), "demo 15 commit"),
                ])
                with fake_cliux(log_level=INFO) as (cli, get_lines):
                    opts = ValidateOptions(
                        ValidateCLIOptions(verbose=True,
                                           commit_count_soft_fail_threshold=5,
                                           commit_count_hard_fail_threshold=10,
                                           cwd=downstream.working_dir))
                    await do_validate(cli=cli, opts=opts)
                    assert False, 'Error should have already been thrown by this point'
    except LikelyUserErrorException as ex:
        assert ex is not None
        assert strip_ansi(
            str(ex)) == """DANGER: VERY LARGE NUMBER OF REVIEW BRANCH COMMITS
async def test_review_branch_commit_count_soft_fail():
    with temp_repo() as upstream:
        upstream_default_branch = upstream.active_branch
        upstream.create_head('mnorth-review-111')
        create_git_history(upstream, [
            (("demo_0.txt", "content for demo_0"), "demo 0 commit"),
            (("demo_1.txt", "content for demo_1"), "demo 1 commit"),
            (("demo_2.txt", "content for demo_2"), "demo 2 commit"),
            (("demo_3.txt", "content for demo_3"), "demo 3 commit"),
        ])
        upstream_default_branch.checkout()
        with temp_repo_clone(upstream, ['mnorth-review-111']) as downstream:
            # downstream_default_branch = downstream.active_branch
            downstream_review_branch = downstream.heads['mnorth-review-111']
            assert downstream_review_branch is not None
            downstream_review_branch.checkout()
            create_git_history(downstream, [
                (("demo_4.txt", "content for demo_4"), "demo 4 commit"),
                (("demo_5.txt", "content for demo_5"), "demo 5 commit"),
                (("demo_6.txt", "content for demo_6"), "demo 6 commit"),
                (("demo_7.txt", "content for demo_7"), "demo 7 commit"),
                (("demo_8.txt", "content for demo_8"), "demo 8 commit"),
                (("demo_9.txt", "content for demo_9"), "demo 9 commit"),
                (("demo_10.txt", "content for demo_10"), "demo 10 commit"),
                (("demo_11.txt", "content for demo_11"), "demo 11 commit"),
                (("demo_12.txt", "content for demo_12"), "demo 12 commit"),
                (("demo_13.txt", "content for demo_13"), "demo 13 commit"),
                (("demo_14.txt", "content for demo_14"), "demo 14 commit"),
                (("demo_15.txt", "content for demo_15"), "demo 15 commit"),
            ])
            with fake_cliux(log_level=INFO) as (cli, get_lines):
                opts = ValidateOptions(
                    ValidateCLIOptions(verbose=True,
                                       commit_count_soft_fail_threshold=10,
                                       commit_count_auto_bypass_soft_fail=True,
                                       cwd=downstream.working_dir))
                await do_validate(cli=cli, opts=opts)
                assert strip_ansi(
                    "".join(get_lines())
                ) == """determined that local branch mnorth-review-111 tracks upstream branch mnorth-review-111 on remote origin
async def test_need_to_fetch_upstream_and_downstream_commits(mock_input):
    with setup_need_to_fetch_scenario() as (upstream, downstream):
        assert upstream.is_dirty() == False
        review_branch: Head = downstream.heads['review-999']
        review_branch.checkout()
        tracked_branch: Head = review_branch.tracking_branch()
        head_before_fetch = tracked_branch.commit
        assert head_before_fetch.hexsha == tracked_branch.commit.hexsha
        with fake_cliux(log_level=INFO) as (cli, get_lines):
            opts = ValidateOptions(
                ValidateCLIOptions(verbose=False,
                                   auto_fetch=True,
                                   cwd=downstream.working_dir))
            await do_validate(cli=cli, opts=opts)
            assert strip_ansi(
                "".join(get_lines())
            ) == """determined that local branch review-999 tracks upstream branch review-999 on remote origin
[WARNING]: New commits on origin/review-999 were detected, which have not yet been pulled down to review-999
Fetching new commits for branch origin/review-999
Fetch from origin complete
Comparing review-999 against origin/review-999
"""
            assert head_before_fetch.hexsha != tracked_branch.commit.hexsha, 'New commits have been pulled down'
Example #17
0
def test_tty_arg_passthrough():
    o1 = ValidateOptions(ValidateCLIOptions(tty=True))
    assert o1.is_terminal_tty_supported() == True, "--tty passes through correctly in ValidateOptions (True)"
    o2 = ValidateOptions(ValidateCLIOptions(tty=False))
    assert o2.is_terminal_tty_supported() == False, "--tty passes through correctly in ValidateOptions (False)"
Example #18
0
def test_cwd_passthrough():
    o = ValidateOptions(ValidateCLIOptions(cwd="/fizz"))
    assert o.get_cwd() == "/fizz"
Example #19
0
def test_cwd_infer():
    o = ValidateOptions(ValidateCLIOptions())
    assert o.get_cwd() == getcwd(), "absence of --cwd results in inference"
Example #20
0
def test_verbosity_passthrough():
    o = ValidateOptions(ValidateCLIOptions(verbose=True))
    assert o.is_verbose() is True, "--verbose results in isVerbose() returning True"

    o2 = ValidateOptions(ValidateCLIOptions())
    assert o2.is_verbose() is False, "--verbose results in isVerbose() returning False"
Example #21
0
def test_to_string():
    o = ValidateOptions(ValidateCLIOptions(verbose=True))
    assert str(o) == "ValidateOptions(cliOpts=ValidateCLIOptions(cwd=None, verbose=True, current_branch=None))"
Example #22
0
async def do_validate(cli: CLIUX, opts: ValidateOptions):
    try:
        cwd = opts.get_cwd()  # working directory
        repo = Repo(cwd)  # git repo

        validate_remotes(repo=repo)
        (active_branch,
         default_branch) = await validate_branches_and_merge_bases(cli=cli,
                                                                   repo=repo,
                                                                   opts=opts)
        (latest_remote_sha,
         active_branch_tracked_ref) = analyze_review_branch_tracking_situation(
             cli, repo, active_branch)
        has_latest_commits_from_upstream = git_does_commit_exist_locally(
            repo=repo, sha=latest_remote_sha)

        if (has_latest_commits_from_upstream == False):
            offer_to_fetch_from_upstream(
                cli=cli,
                repo=repo,
                opts=opts,
                active_branch=active_branch,
                active_branch_tracked_ref=active_branch_tracked_ref)
        cli.info(
            f"Comparing {active_branch.name} against {active_branch_tracked_ref.name}"
        )
        merge_base = validate_merge_bases_with_default_branch(
            cli, repo, repo.active_branch.name, active_branch_tracked_ref.name)
        cli.debug(f"Identified common commit {merge_base.hexsha[0:8]}")
        cli.debug(f"Local sha: {active_branch.commit.hexsha}")
        cli.debug(f"Upstream sha: {active_branch_tracked_ref.commit.hexsha}")
        new_local_commits = get_truncated_log(repo, active_branch.commit,
                                              merge_base.hexsha)
        new_upstream_commits = get_truncated_log(
            repo, active_branch_tracked_ref.commit, merge_base.hexsha)
        cli.debug(f"new local commits: {new_local_commits}")
        cli.debug(f"new upstream commits: {new_upstream_commits}")

        if (len(new_local_commits) >
                opts.get_commit_count_hard_fail_threshold()):
            raise LikelyUserErrorException(
                "Very large number of review branch commits",
                f"""An very large {len(new_local_commits)} number of commits were detected on review branch {
                    active_branch.name
                    }, which were not found on tracked branch {active_branch_tracked_ref.name
                    }.

{format_highlight("This may be an indication of an improper rebase!")}

This warning is presented whenever more than {format_integer(opts.get_commit_count_hard_fail_threshold())
} new commits that have not yet been pushed are found on a review branch.

Please take a close look at your review branch, and ensure you don't see any duplicate commits that are already on {
default_branch.name}""")
        elif (len(new_local_commits) >
              opts.get_commit_count_soft_fail_threshold()):
            raise UserBypassableWarning(
                "Large number of review branch commits",
                f"""An unusually large {format_integer(len(new_local_commits))} number of commits were detected on review branch {
                    active_branch.name
                    }, which were not found on tracked branch {active_branch_tracked_ref.name
                    }.

{format_highlight("This may be an indication of an improper rebase!")}

This warning is presented whenever more than {opts.get_commit_count_soft_fail_threshold()
} new commits, that have not yet been pushed, are found on a review branch.

Please take a close look at your review branch, and ensure you don't see any duplicate commits that are already on {
default_branch.name}""")

    except UserBypassException as ex:
        cli.handle_user_bypass_exception(ex)
    except UserBypassableWarning as ex:
        cli.handle_user_bypassable_warning(
            ex,
            bypass_response=("continue" if
                             opts.should_auto_bypass_commit_count_soft_fail()
                             else None))
    except NonApplicableSituationException as ex:
        cli.handle_non_applicable_situation_exception(ex)
    except UnhandledSituationException as ex:
        cli.handle_unhandled_situation_exception(ex)
    except InvalidGitRepositoryError:
        cli.error(
            f"""git_guardrails is only intended for use within a git repository directory
{cwd} does not seem to be a git repository""")
Example #23
0
async def test_in_non_git_directory():
    with temp_dir() as dir:
        with fake_cliux() as (cli, get_lines):
            opts = ValidateOptions(ValidateCLIOptions(verbose=True, auto_fetch=True, cwd=dir))
            await do_validate(cli=cli, opts=opts)
            assert strip_ansi("".join(get_lines())) == f"""[ERROR]: git_guardrails is only intended for use within a git repository directory
Example #24
0
def test_color_arg_passthrough():
    o1 = ValidateOptions(ValidateCLIOptions(color=True))
    assert o1.is_terminal_color_supported() == True, "--color passes through correctly in ValidateOptions (True)"
    o2 = ValidateOptions(ValidateCLIOptions(color=False))
    assert o2.is_terminal_color_supported() == False, "--color passes through correctly in ValidateOptions (False)"
Example #25
0
def test_creation():
    o = ValidateOptions(ValidateCLIOptions())
    assert o is not None, "Creation of Options instance is successful"