From d1a1e3bf9cba97e83cbab334ad291ed39e2d469c Mon Sep 17 00:00:00 2001 From: eunwoo song Date: Sun, 6 Sep 2026 11:25:22 +0900 Subject: [PATCH] Add advice setting for pull request creation from forks --- RELEASE_NOTES.md | 2 + docs/man/git-machete.1 | 12 ++++ docs/source/cli/github.rst | 3 + docs/source/cli/gitlab.rst | 3 + .../advice.macheteCreateFromFork.rst | 4 ++ git_machete/client/with_code_hosting.py | 2 +- git_machete/config.py | 4 ++ git_machete/generated_docs.py | 12 ++++ tests/test_fork_advice.py | 57 +++++++++++++++++++ 9 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 docs/source/git-config-keys/advice.macheteCreateFromFork.rst create mode 100644 tests/test_fork_advice.py diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1b6f12188..41aafc0ed 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## New in git-machete 3.45.1 +- added: `advice.macheteCreateFromFork` git config key suppresses the fork warning in `github create-pr` and `gitlab create-mr` when set to `false` + ## New in git-machete 3.45.0 - added: `GIT_MACHETE_PUSH_OPTS` environment variable forwards arbitrary extra options to every `git push` invocation (contributed by @HWiese1980) diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index c496770f3..2a30f47f6 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -1215,6 +1215,12 @@ Update PR descriptions for all PRs both upstream and downstream of the PR for th \fBGit config keys\fP .INDENT 0.0 .TP +.B \fBadvice.macheteCreateFromFork\fP (\fBcreate\-pr\fP only) +Controls the warning shown when creating a pull/merge request whose base and head branches live in different repositories. +Set to \fBfalse\fP to suppress the warning about creating stacked pull/merge requests from forks; enabled by default. +This setting affects only the warning, not repository selection or request creation. +For example, run \fBgit config advice.macheteCreateFromFork false\fP in a repository, or add \fB\-\-global\fP to apply it everywhere. +.TP .B \fBmachete.github.{domain,remote,organization,repository,baseRemote,baseOrganization,baseRepository}\fP (all subcommands) .INDENT 7.0 .TP @@ -1518,6 +1524,12 @@ Update MR descriptions for all MRs both upstream and downstream of the MR for th \fBGit config keys\fP .INDENT 0.0 .TP +.B \fBadvice.macheteCreateFromFork\fP (\fBcreate\-mr\fP only) +Controls the warning shown when creating a pull/merge request whose base and head branches live in different repositories. +Set to \fBfalse\fP to suppress the warning about creating stacked pull/merge requests from forks; enabled by default. +This setting affects only the warning, not repository selection or request creation. +For example, run \fBgit config advice.macheteCreateFromFork false\fP in a repository, or add \fB\-\-global\fP to apply it everywhere. +.TP .B \fBmachete.gitlab.{domain,remote,namespace,project,baseRemote,baseNamespace,baseProject}\fP (all subcommands) .INDENT 7.0 .TP diff --git a/docs/source/cli/github.rst b/docs/source/cli/github.rst index 5516833ec..3fa6cf96b 100644 --- a/docs/source/cli/github.rst +++ b/docs/source/cli/github.rst @@ -165,6 +165,9 @@ Create, check out and manage GitHub PRs while keeping them reflected in branch l **Git config keys** +``advice.macheteCreateFromFork`` (``create-pr`` only) + .. include:: git-config-keys/advice.macheteCreateFromFork.rst + ``machete.github.{domain,remote,organization,repository,baseRemote,baseOrganization,baseRepository}`` (all subcommands) .. include:: git-config-keys/github.access.rst diff --git a/docs/source/cli/gitlab.rst b/docs/source/cli/gitlab.rst index 492288e89..45ddcce72 100644 --- a/docs/source/cli/gitlab.rst +++ b/docs/source/cli/gitlab.rst @@ -156,6 +156,9 @@ Create, check out and manage GitLab MRs while keeping them reflected in branch l **Git config keys** +``advice.macheteCreateFromFork`` (``create-mr`` only) + .. include:: git-config-keys/advice.macheteCreateFromFork.rst + ``machete.gitlab.{domain,remote,namespace,project,baseRemote,baseNamespace,baseProject}`` (all subcommands) .. include:: git-config-keys/gitlab.access.rst diff --git a/docs/source/git-config-keys/advice.macheteCreateFromFork.rst b/docs/source/git-config-keys/advice.macheteCreateFromFork.rst new file mode 100644 index 000000000..30ba8a88d --- /dev/null +++ b/docs/source/git-config-keys/advice.macheteCreateFromFork.rst @@ -0,0 +1,4 @@ +Controls the warning shown when creating a pull/merge request whose base and head branches live in different repositories. +Set to ``false`` to suppress the warning about creating stacked pull/merge requests from forks; enabled by default. +This setting affects only the warning, not repository selection or request creation. +For example, run ``git config advice.macheteCreateFromFork false`` in a repository, or add ``--global`` to apply it everywhere. diff --git a/git_machete/client/with_code_hosting.py b/git_machete/client/with_code_hosting.py index 1364d4ad9..177fc6151 100644 --- a/git_machete/client/with_code_hosting.py +++ b/git_machete/client/with_code_hosting.py @@ -258,7 +258,7 @@ def create_pull_request( base_org_repo = base_org_repo_remote.extract_org_and_repo() head_org_repo = head_org_repo_remote.extract_org_and_repo() - if base_org_repo != head_org_repo: + if base_org_repo != head_org_repo and self._config.advice_machete_create_from_fork(): warn(f"{spec.base_branch_name} branch {base} lives in {base_org_repo} {spec.repository_name},\n" f"while {spec.head_branch_name} branch {head} lives in {head_org_repo} {spec.repository_name}.\n" f"git-machete will now attempt to create {spec.pr_short_name_article} {spec.pr_short_name} in {base_org_repo}.\n" diff --git a/git_machete/config.py b/git_machete/config.py index c232bc19a..a84742a5c 100644 --- a/git_machete/config.py +++ b/git_machete/config.py @@ -43,6 +43,7 @@ class PRDescriptionIntroStyle(ParsableEnum): class MacheteConfig: + _ADVICE_MACHETE_CREATE_FROM_FORK = 'advice.macheteCreateFromFork' _ADVICE_MACHETE_EDITOR_SELECTION = 'advice.macheteEditorSelection' _SQUASH_MERGE_DETECTION = 'machete.squashMergeDetection' _STATUS_EXTRA_SPACE_BEFORE_BRANCH_NAME = 'machete.status.extraSpaceBeforeBranchName' @@ -61,6 +62,9 @@ def __init__(self, git: Optional[Git] = None) -> None: def advice_machete_editor_selection(self) -> bool: return self._git.get_config_attr_or_none(self._ADVICE_MACHETE_EDITOR_SELECTION) != 'false' + def advice_machete_create_from_fork(self) -> bool: + return self._git.get_config_attr_or_none(self._ADVICE_MACHETE_CREATE_FROM_FORK) != 'false' + def core_editor(self) -> Optional[str]: return self._git.get_config_attr_or_none("core.editor") diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index 1006e28f4..4b6ef16c4 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -843,6 +843,12 @@ Git config keys + `advice.macheteCreateFromFork` (`create-pr` only) + Controls the warning shown when creating a pull/merge request whose base and head branches live in different repositories. + Set to `false` to suppress the warning about creating stacked pull/merge requests from forks; enabled by default. + This setting affects only the warning, not repository selection or request creation. + For example, run `git config advice.macheteCreateFromFork false` in a repository, or add `--global` to apply it everywhere. + `machete.github.{domain,remote,organization,repository,baseRemote,baseOrganization,baseRepository}` (all subcommands) `machete.github.domain` The domain of the GitHub API server, for use with GitHub Enterprise; otherwise inferred from the remote URL. @@ -1069,6 +1075,12 @@ Git config keys + `advice.macheteCreateFromFork` (`create-mr` only) + Controls the warning shown when creating a pull/merge request whose base and head branches live in different repositories. + Set to `false` to suppress the warning about creating stacked pull/merge requests from forks; enabled by default. + This setting affects only the warning, not repository selection or request creation. + For example, run `git config advice.macheteCreateFromFork false` in a repository, or add `--global` to apply it everywhere. + `machete.gitlab.{domain,remote,namespace,project,baseRemote,baseNamespace,baseProject}` (all subcommands) `machete.gitlab.domain` The domain of the GitLab API server, for use with a GitLab self-managed instance; otherwise inferred from the remote URL. diff --git a/tests/test_fork_advice.py b/tests/test_fork_advice.py new file mode 100644 index 000000000..cf2df4268 --- /dev/null +++ b/tests/test_fork_advice.py @@ -0,0 +1,57 @@ +from typing import Optional + +import pytest +from pytest_mock import MockerFixture + +from tests import mockers_github, mockers_gitlab +from tests.base_test import BaseTest +from tests.cli_runner import assert_success, rewrite_branch_layout_file +from tests.git_repository import add_remote, commit, create_repo, create_repo_with_remote, new_branch, push, set_git_config_key +from tests.mockers_code_hosting import mock_from_url + + +class TestForkAdvice(BaseTest): + + @pytest.mark.parametrize('provider', ['github', 'gitlab']) + @pytest.mark.parametrize('advice', [None, 'true', 'false']) + def test_create_from_fork(self, mocker: MockerFixture, provider: str, advice: Optional[str]) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + if provider == 'github': + self.patch_symbol(mocker, 'git_machete.github.GitHubToken.for_domain', mockers_github.mock_github_token_for_domain_none) + self.patch_symbol(mocker, 'urllib.request.urlopen', mockers_github.mock_urlopen(mockers_github.MockGitHubAPIState.with_prs())) + display, short, base_label, head_label, repository = 'GitHub', 'PR', 'base', 'head', 'repository' + else: + self.patch_symbol(mocker, 'git_machete.gitlab.GitLabToken.for_domain', mockers_gitlab.mock_gitlab_token_for_domain_none) + self.patch_symbol(mocker, 'urllib.request.urlopen', mockers_gitlab.mock_urlopen(mockers_gitlab.MockGitLabAPIState.with_mrs())) + display, short, base_label, head_label, repository = 'GitLab', 'MR', 'target', 'source', 'project' + article = 'a' if provider == 'github' else 'an' + create_repo_with_remote() + fork_path = create_repo('remote-1', bare=True, switch_dir_to_new_repo=False) + add_remote('fork', fork_path) + new_branch('master') + commit() + push(remote='fork', set_upstream=False) + push(remote='origin') + new_branch('feature') + commit() + push(remote='origin') + rewrite_branch_layout_file('master\n\tfeature') + set_git_config_key(f'machete.{provider}.baseRemote', 'fork') + if advice is not None: + set_git_config_key('advice.macheteCreateFromFork', advice) + warning = f""" + Warn: {base_label} branch master lives in example-org/example-repo-1 {repository}, + while {head_label} branch feature lives in example-org/example-repo {repository}. + git-machete will now attempt to create {article} {short} in example-org/example-repo-1. + + Note that due to the limitations of {display}'s {short} model, it is not possible to cleanly create stacked {short}s from forks. + For example, in a hypothetical chain some-other-branch -> feature -> master, {article} {short} from some-other-branch to feature + could not be created in example-org/example-repo-1, since its {head_label} branch feature lives in example-org/example-repo. + Generally, {short}s need to be created in whatever {repository} the {base_label} branch lives. + """ if advice != 'false' else '' + expected = warning + f""" + Checking if {head_label} branch feature exists in origin remote... YES + Checking if {base_label} branch master exists in fork remote... YES + Creating {article} {short} from feature to master... OK, see www.{provider}.com + """ + assert_success([provider, f'create-{short.lower()}'], expected)