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
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.
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'
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)"
def test_cwd_passthrough(): o = ValidateOptions(ValidateCLIOptions(cwd="/fizz")) assert o.get_cwd() == "/fizz"
def test_cwd_infer(): o = ValidateOptions(ValidateCLIOptions()) assert o.get_cwd() == getcwd(), "absence of --cwd results in inference"
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"
def test_to_string(): o = ValidateOptions(ValidateCLIOptions(verbose=True)) assert str(o) == "ValidateOptions(cliOpts=ValidateCLIOptions(cwd=None, verbose=True, current_branch=None))"
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""")
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
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)"
def test_creation(): o = ValidateOptions(ValidateCLIOptions()) assert o is not None, "Creation of Options instance is successful"