Skip to content

Commit

Permalink
feat: detect target branch using standard Git configuration
Browse files Browse the repository at this point in the history
The current code forces the user to choose a single target branch which
prevents stacks from working on multiple target branches. This leverage
the standard git configuration to retrieve the target branch.

Change-Id: Ia1455d68eaa76af503b6e6cf4c24c19efcdd9b7b
  • Loading branch information
jd committed Apr 29, 2024
1 parent f1e8eef commit 5762433
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 31 deletions.
77 changes: 47 additions & 30 deletions mergify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,42 +437,60 @@ async def log_httpx_response(response: httpx.Response) -> None: # noqa: RUF029
)


def get_trunk() -> str:
try:
trunk = subprocess.check_output(
"git config --get mergify-cli.stack-trunk",
shell=True,
text=True,
).strip()
except subprocess.CalledProcessError:
trunk = ""
def git_get_branch_name() -> str:
return subprocess.check_output(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
text=True,
).strip()

if not trunk:
try:
dest_branch = subprocess.check_output(
"git rev-parse --abbrev-ref HEAD",
shell=True,
text=True,
).strip()
except subprocess.CalledProcessError:
return ""

try:
trunk = subprocess.check_output(
f"git for-each-ref --format='%(upstream:short)' refs/heads/{dest_branch}",
shell=True,
text=True,
).strip()
except subprocess.CalledProcessError:
trunk = ""
def git_get_target_branch(branch: str) -> str:
target_branch = subprocess.check_output(
["git", "config", "--get", "branch." + branch + ".merge"],
text=True,
).strip()

return target_branch.removeprefix("refs/heads/")


return trunk
def git_get_remote_for_branch(branch: str) -> str:
return subprocess.check_output(
["git", "config", "--get", "branch." + branch + ".remote"],
text=True,
).strip()


def get_trunk() -> str:
try:
branch_name = git_get_branch_name()
except subprocess.CalledProcessError:
console.print("error: can't get the current branch", style="red")
raise
try:
target_branch = git_get_target_branch(branch_name)
except subprocess.CalledProcessError:
# It's possible this has not been set; ignore
console.print("error: can't get the remote target branch", style="red")
console.print(
f"Please set the target branch with `git branch {branch_name} --set-upstream-to=<remote>/<branch>",
style="red",
)
raise
try:
remote = git_get_remote_for_branch(target_branch)
except subprocess.CalledProcessError:
console.print(
f"error: can't get the remote for branch {target_branch}",
style="red",
)
raise
return f"{remote}/{target_branch}"


def trunk_type(trunk: str) -> tuple[str, str]:
result = trunk.split("/", maxsplit=1)
if len(result) != 2:
msg = "stack-trunk is invalid. It must be origin/branch-name [/]"
msg = "Trunk is invalid. It must be origin/branch-name [/]"
raise argparse.ArgumentTypeError(msg)
return result[0], result[1]

Expand Down Expand Up @@ -809,8 +827,7 @@ def parse_args(args: typing.MutableSequence[str]) -> argparse.Namespace:
"-t",
type=trunk_type,
default=get_trunk(),
help="Change the target branch of the stack. "
"Default fetched from git config if added with `git config --add mergify-cli.stack-trunk origin/branch-name`",
help="Change the target branch of the stack.",
)
stack_parser.add_argument(
"--branch-prefix",
Expand Down
32 changes: 31 additions & 1 deletion mergify_cli/tests/test_mergify_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ def _change_working_directory(
monkeypatch.chdir(tmp_path)


@pytest.fixture()
def _git_repo() -> None:
subprocess.call(["git", "init", "--initial-branch=main"])
subprocess.call(["git", "config", "user.email", "[email protected]"])
subprocess.call(["git", "config", "user.name", "Test User"])
subprocess.call(["git", "commit", "--allow-empty", "-m", "Initial commit"])
subprocess.call(["git", "config", "--add", "branch.main.merge", "refs/heads/main"])
subprocess.call(["git", "config", "--add", "branch.main.remote", "origin"])


@pytest.fixture()
def git_mock(
tmp_path: pathlib.Path,
Expand All @@ -58,6 +68,7 @@ def git_mock(
yield git_mock_object


@pytest.mark.usefixtures("_git_repo")
def test_cli_help(capsys: pytest.CaptureFixture[str]) -> None:
with pytest.raises(SystemExit, match="0"):
mergify_cli.parse_args(["--help"])
Expand All @@ -68,6 +79,26 @@ def test_cli_help(capsys: pytest.CaptureFixture[str]) -> None:
assert "options:" in stdout


@pytest.mark.usefixtures("_git_repo")
def test_get_branch_name() -> None:
assert mergify_cli.git_get_branch_name() == "main"


@pytest.mark.usefixtures("_git_repo")
def test_get_target_branch() -> None:
assert mergify_cli.git_get_target_branch("main") == "main"


@pytest.mark.usefixtures("_git_repo")
def test_get_remote_for_branch() -> None:
assert mergify_cli.git_get_remote_for_branch("main") == "origin"


@pytest.mark.usefixtures("_git_repo")
def test_get_trunk() -> None:
assert mergify_cli.get_trunk() == "origin/main"


@pytest.mark.parametrize(
"valid_branch_name",
[
Expand Down Expand Up @@ -533,7 +564,6 @@ async def test_stack_without_common_commit_raises_an_error(
("default_arg_fct", "config_get_result", "expected_default"),
[
(mergify_cli.get_default_keep_pr_title_body, b"true", True),
(mergify_cli.get_trunk, "dummy-origin/main", "dummy-origin/main"),
(mergify_cli.get_default_branch_prefix, b"dummy-prefix", "dummy-prefix"),
],
)
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ ignore = [
"S404",
# NOTE(charly): `subprocess` call with `shell=True`
"S602",
# NOTE(jd): likely a false positive https://github.com/PyCQA/bandit/issues/333
"S603",
# NOTE(charly): Starting a process with a partial executable path
"S607",
# NOTE(charly): Boolean-typed positional argument in function definition.
Expand Down

0 comments on commit 5762433

Please sign in to comment.