From a828c03140f888f9cbc9467095e3bd667898b754 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Tue, 8 Sep 2026 19:14:09 +0200 Subject: [PATCH] Skip traverse PR/MR creation for branches annotated with push=no. A push=no qualifier already means the branch should not be published, so offering to open a pull/merge request for it is inconsistent and can create a duplicate PR for someone else's branch. --- RELEASE_NOTES.md | 1 + docs/man/git-machete.1 | 3 +- docs/source/cli/traverse.rst | 3 +- git_machete/client/traverse.py | 3 +- git_machete/generated_docs.py | 3 +- tests/test_traverse_github.py | 64 ++++++++++++++++++++++++++++++++++ tests/test_traverse_gitlab.py | 64 ++++++++++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 4 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 736a55665..61c0a6887 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ ## New in git-machete 3.45.1 +- fixed: `git machete traverse --sync-github-prs`/`--sync-gitlab-mrs` no longer offers to create a PR/MR for a branch annotated with `push=no` - fixed: `git machete discover` preserves full annotations on rediscovered branches, rather than only their traversal qualifiers (contributed by @be-student) - fixed: branch layout files are now always read and written as UTF-8 rather than the process locale encoding, so annotations with non-ASCII characters no longer break on Windows diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index 30613a578..ad77e3d3b 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -2349,7 +2349,7 @@ asks the user whether to \fBpull\fP the branch if \fB\-H\fP/\fB\-\-sync\-github\-prs\fP or \fB\-L\fP/\fB\-\-sync\-gitlab\-mrs\fP option is present: .INDENT 2.0 .IP \(bu 2 -asks the user whether to \fBcreate\fP a PR/MR for the given branch if it doesn\(aqt exist yet, +asks the user whether to \fBcreate\fP a PR/MR for the given branch if it doesn\(aqt exist yet (unless the branch is annotated with \fBpush=no\fP), .IP \(bu 2 asks the user whether to \fBretarget\fP the PR/MR if it exists for the given branch, and its base/target branch in GitHub/GitLab is different than the upstream in machete file @@ -2374,6 +2374,7 @@ Unlike with \fBgit rebase\fP or \fBgit cherry\-pick\fP, there is no special \fB\ .sp The rebase, push and slide\-out behaviors of \fBtraverse\fP can also be customized for each branch separately using \fIbranch qualifiers\fP\&. There are \fBpush=no\fP, \fBrebase=no\fP and \fBslide\-out=no\fP qualifiers that can be used to opt out of default behavior (rebasing, pushing and sliding the branch out). +\fBpush=no\fP also skips creating a PR/MR under \fB\-\-sync\-github\-prs\fP/\fB\-\-sync\-gitlab\-mrs\fP\&. The qualifier can appear anywhere in the annotation, but needs to be separated by a whitespace from any other character, as in: \fBsome_annotation_text rebase=no push=no slide\-out=no\fP\&. Qualifiers can only be overwritten by manually editing \fB\&.git/machete\fP file or modifying it with \fBgit machete e[dit]\fP, or by updating annotations with \fBgit machete anno\fP\&. Example machete file with branch qualifiers: diff --git a/docs/source/cli/traverse.rst b/docs/source/cli/traverse.rst index 57005e217..43a340b9f 100644 --- a/docs/source/cli/traverse.rst +++ b/docs/source/cli/traverse.rst @@ -50,7 +50,7 @@ For each branch, the command performs the following actions: * if ``-H``/``--sync-github-prs`` or ``-L``/``--sync-gitlab-mrs`` option is present: - - asks the user whether to **create** a PR/MR for the given branch if it doesn't exist yet, + - asks the user whether to **create** a PR/MR for the given branch if it doesn't exist yet (unless the branch is annotated with ``push=no``), - asks the user whether to **retarget** the PR/MR if it exists for the given branch, and its base/target branch in GitHub/GitLab is different than the upstream in machete file @@ -69,6 +69,7 @@ Unlike with ``git rebase`` or ``git cherry-pick``, there is no special ``--conti The rebase, push and slide-out behaviors of ``traverse`` can also be customized for each branch separately using *branch qualifiers*. There are ``push=no``, ``rebase=no`` and ``slide-out=no`` qualifiers that can be used to opt out of default behavior (rebasing, pushing and sliding the branch out). +``push=no`` also skips creating a PR/MR under ``--sync-github-prs``/``--sync-gitlab-mrs``. The qualifier can appear anywhere in the annotation, but needs to be separated by a whitespace from any other character, as in: ``some_annotation_text rebase=no push=no slide-out=no``. Qualifiers can only be overwritten by manually editing ``.git/machete`` file or modifying it with ``git machete e[dit]``, or by updating annotations with ``git machete anno``. Example machete file with branch qualifiers: diff --git a/git_machete/client/traverse.py b/git_machete/client/traverse.py index 885277fa5..10ff13124 100644 --- a/git_machete/client/traverse.py +++ b/git_machete/client/traverse.py @@ -276,7 +276,8 @@ def traverse( if parent: prs = [_pr for _pr in self._get_all_open_prs() if _pr.head == branch] if not prs: - needs_create_pr = True + if branch_anno is None or branch_anno.qualifiers.push: + needs_create_pr = True use_merge = opt_merge or (branch_anno is not None and branch_anno.qualifiers.update_with_merge) diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index 3d57a076e..c5afe251f 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -1686,7 +1686,7 @@ * if `-H`/`--sync-github-prs` or `-L`/`--sync-gitlab-mrs` option is present: - - asks the user whether to create a PR/MR for the given branch if it doesn't exist yet, + - asks the user whether to create a PR/MR for the given branch if it doesn't exist yet (unless the branch is annotated with `push=no`), - asks the user whether to retarget the PR/MR if it exists for the given branch, and its base/target branch in GitHub/GitLab is different than the upstream in machete file @@ -1705,6 +1705,7 @@ The rebase, push and slide-out behaviors of `traverse` can also be customized for each branch separately using branch qualifiers. There are `push=no`, `rebase=no` and `slide-out=no` qualifiers that can be used to opt out of default behavior (rebasing, pushing and sliding the branch out). + `push=no` also skips creating a PR/MR under `--sync-github-prs`/`--sync-gitlab-mrs`. The qualifier can appear anywhere in the annotation, but needs to be separated by a whitespace from any other character, as in: `some_annotation_text rebase=no push=no slide-out=no`. Qualifiers can only be overwritten by manually editing `.git/machete` file or modifying it with `git machete e[dit]`, or by updating annotations with `git machete anno`. Example machete file with branch qualifiers: diff --git a/tests/test_traverse_github.py b/tests/test_traverse_github.py index 1c2e21cb3..5210a7142 100644 --- a/tests/test_traverse_github.py +++ b/tests/test_traverse_github.py @@ -368,3 +368,67 @@ def test_traverse_sync_create_github_prs(self, mocker: MockerFixture) -> None: Reached branch drop-constraint which has no successor; nothing left to update """ ) + + def test_traverse_sync_create_github_prs_skips_push_no(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + self.patch_symbol(mocker, 'git_machete.github.GitHubToken.for_domain', mock_github_token_for_domain_fake) + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(MockGitHubAPIState.with_prs())) + + create_repo_with_remote() + new_branch("develop") + commit() + push() + new_branch("someone-elses-branch") + commit() + push() + check_out("develop") + new_branch("annotated-ok") + commit() + push() + check_out("develop") + new_branch("my-branch") + commit() + push() + + body: str = \ + """ + develop + someone-elses-branch rebase=no push=no + annotated-ok WIP + my-branch + """ + rewrite_branch_layout_file(body) + check_out("develop") + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("n", "q")) + assert_success( + ["traverse", "--sync-github-prs"], + """ + Checking for open GitHub PRs... OK + Checking out annotated-ok... OK + + develop + | + o-someone-elses-branch rebase=no push=no + | + o-annotated-ok * WIP + | + o-my-branch + + Branch annotated-ok does not have a PR in GitHub. + Create a PR from annotated-ok to develop? (y, d[raft], N, q, yq) + + Checking out my-branch... OK + + develop + | + o-someone-elses-branch rebase=no push=no + | + o-annotated-ok WIP + | + o-my-branch * + + Branch my-branch does not have a PR in GitHub. + Create a PR from my-branch to develop? (y, d[raft], N, q, yq) + """ + ) diff --git a/tests/test_traverse_gitlab.py b/tests/test_traverse_gitlab.py index 480b54d41..b6c812bc1 100644 --- a/tests/test_traverse_gitlab.py +++ b/tests/test_traverse_gitlab.py @@ -217,3 +217,67 @@ def test_traverse_sync_gitlab_mrs(self, mocker: MockerFixture) -> None: Retarget MR !2 to allow-ownership-link? (y, N, q, yq) Switching target branch of MR !2 to allow-ownership-link... OK """) + + def test_traverse_sync_create_gitlab_mrs_skips_push_no(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + self.patch_symbol(mocker, 'git_machete.gitlab.GitLabToken.for_domain', mock_gitlab_token_for_domain_fake) + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(MockGitLabAPIState.with_mrs())) + + create_repo_with_remote() + new_branch("develop") + commit() + push() + new_branch("someone-elses-branch") + commit() + push() + check_out("develop") + new_branch("annotated-ok") + commit() + push() + check_out("develop") + new_branch("my-branch") + commit() + push() + + body: str = \ + """ + develop + someone-elses-branch rebase=no push=no + annotated-ok WIP + my-branch + """ + rewrite_branch_layout_file(body) + check_out("develop") + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("n", "q")) + assert_success( + ["traverse", "--sync-gitlab-mrs"], + """ + Checking for open GitLab MRs... OK + Checking out annotated-ok... OK + + develop + | + o-someone-elses-branch rebase=no push=no + | + o-annotated-ok * WIP + | + o-my-branch + + Branch annotated-ok does not have an MR in GitLab. + Create an MR from annotated-ok to develop? (y, d[raft], N, q, yq) + + Checking out my-branch... OK + + develop + | + o-someone-elses-branch rebase=no push=no + | + o-annotated-ok WIP + | + o-my-branch * + + Branch my-branch does not have an MR in GitLab. + Create an MR from my-branch to develop? (y, d[raft], N, q, yq) + """ + )