From 6a78937abf320f79d9fc37dfea3bf2e95357ad89 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Tue, 24 Sep 2024 15:33:58 +0200 Subject: [PATCH 1/4] Add `-H`/`--sync-github-prs` and `-L`/`--sync-gitlab-mrs` flags to `traverse` --- RELEASE_NOTES.md | 4 +- completion/git-machete.completion.bash | 4 +- completion/git-machete.completion.zsh | 2 + completion/git-machete.fish | 34 +++++++------ docs/man/git-machete.1 | 18 ++++++- docs/source/cli/traverse.rst | 12 +++++ git_machete/__init__.py | 2 +- git_machete/cli.py | 14 ++++-- git_machete/client.py | 54 ++++++++++++++++++++- git_machete/generated_docs.py | 10 ++++ tests/completion_e2e/test_completion_e2e.py | 12 +++-- 11 files changed, 135 insertions(+), 31 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d393e6308..eac08d5fe 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,8 @@ # Release notes -## New in git-machete 3.31.2 +## New in git-machete 3.32.0 + +- added: flags `-H`/`--sync-github-prs` and `-L`/`--sync-gitlab-mrs` to `traverse` to automatically retarget PRs/MRs when traversing (suggested by @chriscz) ## New in git-machete 3.31.1 diff --git a/completion/git-machete.completion.bash b/completion/git-machete.completion.bash index 3f104747f..7a9806a9f 100644 --- a/completion/git-machete.completion.bash +++ b/completion/git-machete.completion.bash @@ -17,7 +17,7 @@ _git_machete() { local common_opts="--debug -h --help -v --verbose" local add_opts="-f --as-first-child -o --onto= -R --as-root -y --yes" local advance_opts="-y --yes" - local anno_opts="-b --branch= -H -L --sync-github-prs --sync-gitlab-mrs" + local anno_opts="-b --branch= -H --sync-github-prs -L --sync-gitlab-mrs" local delete_unmanaged_opts="-y --yes" local diff_opts="-s --stat" local discover_opts="-C --checked-out-since= -l --list-commits -r --roots= -y --yes" @@ -32,7 +32,7 @@ _git_machete() { local slide_out_opts="-d --down-fork-point= --delete -M --merge -n --no-edit-merge --no-interactive-rebase --removed-from-remote" local squash_opts="-f --fork-point=" local status_opts="--color= -L --list-commits-with-hashes -l --list-commits --no-detect-squash-merges" - local traverse_opts="-F --fetch -l --list-commits -M --merge -n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase --no-push --no-push-untracked --push --push-untracked --return-to= --start-from= -w --whole -W -y --yes" + local traverse_opts="-F --fetch -H --sync-github-prs -L --sync-gitlab-mrs -l --list-commits -M --merge -n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase --no-push --no-push-untracked --push --push-untracked --return-to= --start-from= -w --whole -W -y --yes" local update_opts="-f --fork-point= -M --merge -n --no-edit-merge --no-interactive-rebase" cur=${COMP_WORDS[$COMP_CWORD]} diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh index 99b3ceb59..a8eaf7d35 100644 --- a/completion/git-machete.completion.zsh +++ b/completion/git-machete.completion.zsh @@ -140,6 +140,8 @@ _git-machete() { (t|traverse) _arguments \ '(-F --fetch)'{-F,--fetch}'[Fetch the remotes of all managed branches at the beginning of traversal]' \ + '(-H --sync-github-prs)'{-H,--sync-github-prs}'[Retarget GitHub PR when its base branch is different than in machete file]' \ + '(-L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Retarget GitLab MR when its target branch is different than in machete file]' \ '(-l --list-commits)'{-l,--list-commits}'[List the messages of commits introduced on each branch]' \ '(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \ '(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \ diff --git a/completion/git-machete.fish b/completion/git-machete.fish index 7cd803fb2..18f287b05 100644 --- a/completion/git-machete.fish +++ b/completion/git-machete.fish @@ -213,22 +213,24 @@ complete -c git-machete -n "__fish_seen_subcommand_from status s; and not __fish # git machete traverse complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a traverse -d 'Walk through the tree of branch dependencies and rebase, merge, slide out, push and/or pull each branch one by one' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --fetch -F" -f -l fetch -s F -d 'Fetch the remotes of all managed branches at the beginning of traversal (no git pull involved, only git fetch)' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --list-commits -l" -f -l list-commits -s l -d 'When printing the status, additionally list the messages of commits introduced on each branch' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --merge -M" -f -l merge -s M -d 'Update by merge rather than by rebase' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --yes -y" -f -s n -d 'If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-detect-squash-merges" -f -l no-detect-squash-merges -d 'Only consider "strict" (fast-forward or 2-parent) merges, rather than rebase/squash merges, when detecting if a branch is merged into its upstream (parent)' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-edit-merge" -f -l no-edit-merge -d 'If updating by merge, skip opening the editor for merge commit message while doing git merge (i.e. pass --no-edit flag to underlying git merge). Not allowed if updating by rebase' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-interactive-rebase" -f -l no-interactive-rebase -d 'If updating by rebase, run git rebase in non-interactive mode (without -i/--interactive flag). Not allowed if updating by merge' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-push" -f -l no-push -d 'Do not push any (neither tracked nor untracked) branches to remote, re-enable via --push' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-push-untracked" -f -l no-push-untracked -d 'Do not push untracked branches to remote, re-enable via --push-untracked' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --push" -f -l push -d 'Push all (both tracked and untracked) branches to remote - default behavior' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --push-untracked" -f -l push-untracked -d 'Push untracked branches to remote - default behavior' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --return-to" -x -l return-to -a 'stay here nearest-remaining' -d 'Specifies the branch to return after traversal is successfully completed' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --start-from" -x -l start-from -a 'here root first-root' -d 'Specifies the branch to start the traversal from' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --whole -w" -f -l whole -s w -d 'Equivalent to -n --start-from=first-root --return-to=nearest-remaining' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from -W" -f -s W -d 'Equivalent to --fetch --whole; useful for even more automated traversal of all branches' -complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --yes -y" -f -l yes -s y -d 'Do not ask for any interactive input, including confirmation of rebase/push/pull. Implies -n' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --fetch -F" -f -l fetch -s F -d 'Fetch the remotes of all managed branches at the beginning of traversal (no git pull involved, only git fetch)' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --list-commits -l" -f -l list-commits -s l -d 'When printing the status, additionally list the messages of commits introduced on each branch' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --sync-gitlab-mrs" -f -l sync-github-prs -s H -d 'Retarget GitHub PR when its base branch is different than in machete file' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --sync-github-prs" -f -l sync-gitlab-mrs -s L -d 'Retarget GitLab MR when its target branch is different than in machete file' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --merge -M" -f -l merge -s M -d 'Update by merge rather than by rebase' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --yes -y" -f -s n -d 'If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-detect-squash-merges" -f -l no-detect-squash-merges -d 'Only consider "strict" (fast-forward or 2-parent) merges, rather than rebase/squash merges, when detecting if a branch is merged into its upstream (parent)' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-edit-merge" -f -l no-edit-merge -d 'If updating by merge, skip opening the editor for merge commit message while doing git merge (i.e. pass --no-edit flag to underlying git merge). Not allowed if updating by rebase' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-interactive-rebase" -f -l no-interactive-rebase -d 'If updating by rebase, run git rebase in non-interactive mode (without -i/--interactive flag). Not allowed if updating by merge' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-push" -f -l no-push -d 'Do not push any (neither tracked nor untracked) branches to remote, re-enable via --push' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --no-push-untracked" -f -l no-push-untracked -d 'Do not push untracked branches to remote, re-enable via --push-untracked' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --push" -f -l push -d 'Push all (both tracked and untracked) branches to remote - default behavior' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --push-untracked" -f -l push-untracked -d 'Push untracked branches to remote - default behavior' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --return-to" -x -l return-to -a 'stay here nearest-remaining' -d 'Specifies the branch to return after traversal is successfully completed' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --start-from" -x -l start-from -a 'here root first-root' -d 'Specifies the branch to start the traversal from' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --whole -w" -f -l whole -s w -d 'Equivalent to -n --start-from=first-root --return-to=nearest-remaining' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from -W" -f -s W -d 'Equivalent to --fetch --whole; useful for even more automated traversal of all branches' +complete -c git-machete -n "__fish_seen_subcommand_from traverse t; and not __fish_seen_subcommand_from --yes -y" -f -l yes -s y -d 'Do not ask for any interactive input, including confirmation of rebase/push/pull. Implies -n' # git machete update complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a update -d 'Sync the current branch with its upstream (parent) branch via rebase or merge' diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index c79fb82b8..9f69fbf24 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -29,7 +29,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .. .TH "GIT-MACHETE" "1" "Dec 17, 2024" "" "git-machete" .SH NAME -git-machete \- git-machete 3.31.2 +git-machete \- git-machete 3.32.0 .sp git machete is a robust tool that \fBsimplifies your git workflows\fP\&. .sp @@ -2032,6 +2032,7 @@ by setting \fBgit config machete.status.extraSpaceBeforeBranchName true\fP\&. git machete t[raverse] [\-F|\-\-fetch] [\-l|\-\-list\-commits] [\-M|\-\-merge] [\-n|\-\-no\-edit\-merge|\-\-no\-interactive\-rebase] [\-\-[no\-]push] [\-\-[no\-]push\-untracked] [\-\-return\-to=WHERE] [\-\-start\-from=WHERE] [\-\-squash\-merge\-detection=MODE] + [\-H|\-\-sync\-github\-prs|\-L|\-\-sync\-gitlab\-mrs] [\-w|\-\-whole] [\-W] [\-y|\-\-yes] .EE .UNINDENT @@ -2078,6 +2079,13 @@ otherwise, if the branch is behind its remote counterpart: asks the user whether to \fBpull\fP the branch; .UNINDENT .IP \(bu 2 +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 +retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, +just as \fBgit machete github retarget\-pr\fP and \fBgit machete gitlab retarget\-mr\fP would do; +.UNINDENT +.IP \(bu 2 and finally, if any of the above operations has been successfully completed: .INDENT 2.0 .IP \(bu 2 @@ -2122,6 +2130,14 @@ when the current user is NOT the author of the PR/MR associated with that branch .B \-F\fP,\fB \-\-fetch Fetch the remotes of all managed branches at the beginning of traversal (no \fBgit pull\fP involved, only \fBgit fetch\fP). .TP +.B \-H\fP,\fB \-\-sync\-github\-prs +Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, +just as \fBgit machete github retarget\-pr\fP would do +.TP +.B \-L\fP,\fB \-\-sync\-gitlab\-mrs +Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, +just as \fBgit machete gitlab retarget\-mr\fP would do +.TP .B \-l\fP,\fB \-\-list\-commits When printing the status, additionally list the messages of commits introduced on each branch. .TP diff --git a/docs/source/cli/traverse.rst b/docs/source/cli/traverse.rst index 9d350ff64..1df72fe35 100644 --- a/docs/source/cli/traverse.rst +++ b/docs/source/cli/traverse.rst @@ -21,6 +21,7 @@ traverse git machete t[raverse] [-F|--fetch] [-l|--list-commits] [-M|--merge] [-n|--no-edit-merge|--no-interactive-rebase] [--[no-]push] [--[no-]push-untracked] [--return-to=WHERE] [--start-from=WHERE] [--squash-merge-detection=MODE] + [-H|--sync-github-prs|-L|--sync-gitlab-mrs] [-w|--whole] [-W] [-y|--yes] Traverses the branches in the order as they occur in branch layout file. @@ -52,6 +53,11 @@ For each branch, the command: - asks the user whether to **pull** the branch; +* if ``-H``/``--sync-github-prs`` or ``-L``/``--sync-gitlab-mrs`` option is present: + + - retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, + just as ``git machete github retarget-pr`` and ``git machete gitlab retarget-mr`` would do; + * and finally, if any of the above operations has been successfully completed: - prints the updated ``status``. @@ -88,6 +94,12 @@ when the current user is NOT the author of the PR/MR associated with that branch -F, --fetch Fetch the remotes of all managed branches at the beginning of traversal (no ``git pull`` involved, only ``git fetch``). +-H, --sync-github-prs Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, + just as ``git machete github retarget-pr`` would do + +-L, --sync-gitlab-mrs Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, + just as ``git machete gitlab retarget-mr`` would do + -l, --list-commits When printing the status, additionally list the messages of commits introduced on each branch. -M, --merge Update by merge rather than by rebase. diff --git a/git_machete/__init__.py b/git_machete/__init__.py index 06bad3f9b..c657b2862 100644 --- a/git_machete/__init__.py +++ b/git_machete/__init__.py @@ -1 +1 @@ -__version__ = '3.31.2' +__version__ = '3.32.0' diff --git a/git_machete/cli.py b/git_machete/cli.py index 4414a9ec1..2a636971a 100644 --- a/git_machete/cli.py +++ b/git_machete/cli.py @@ -380,21 +380,23 @@ def add_code_hosting_parser(command: str, pr_or_mr: str, include_sync: bool) -> add_help=False, parents=[common_args_parser]) traverse_parser.add_argument('-F', '--fetch', action='store_true') + traverse_parser.add_argument('-H', '--sync-github-prs', action='store_true') traverse_parser.add_argument('-l', '--list-commits', action='store_true') + traverse_parser.add_argument('-L', '--sync-gitlab-mrs', action='store_true') traverse_parser.add_argument('-M', '--merge', action='store_true') traverse_parser.add_argument('-n', action='store_true') + traverse_parser.add_argument('--no-detect-squash-merges', action='store_true') traverse_parser.add_argument('--no-edit-merge', action='store_true') traverse_parser.add_argument('--no-interactive-rebase', action='store_true') - traverse_parser.add_argument('--no-detect-squash-merges', action='store_true') - traverse_parser.add_argument('--squash-merge-detection') - traverse_parser.add_argument('--push', action='store_true') traverse_parser.add_argument('--no-push', action='store_true') - traverse_parser.add_argument('--push-untracked', action='store_true') traverse_parser.add_argument('--no-push-untracked', action='store_true') + traverse_parser.add_argument('--push', action='store_true') + traverse_parser.add_argument('--push-untracked', action='store_true') traverse_parser.add_argument('--return-to') + traverse_parser.add_argument('--squash-merge-detection') traverse_parser.add_argument('--start-from') - traverse_parser.add_argument('-w', '--whole', action='store_true') traverse_parser.add_argument('-W', action='store_true') + traverse_parser.add_argument('-w', '--whole', action='store_true') traverse_parser.add_argument('-y', '--yes', action='store_true') update_parser = subparsers.add_parser( @@ -960,6 +962,8 @@ def strip_remote_name(remote_branch: RemoteBranchShortName) -> LocalBranchShortN opt_return_to=opt_return_to, opt_squash_merge_detection=opt_squash_merge_detection, opt_start_from=opt_start_from, + opt_sync_github_prs=cli_opts.opt_sync_github_prs, + opt_sync_gitlab_mrs=cli_opts.opt_sync_gitlab_mrs, opt_yes=cli_opts.opt_yes) elif cmd == "update": machete_client.read_branch_layout_file(perform_interactive_slide_out=should_perform_interactive_slide_out) diff --git a/git_machete/client.py b/git_machete/client.py index e5375ff47..e3e4ed395 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -27,6 +27,8 @@ GitFormatPatterns, GitLogEntry, LocalBranchShortName, RemoteBranchShortName, SyncToRemoteStatus) +from .github import GitHubClient +from .gitlab import GitLabClient from .utils import (AnsiEscapeCodes, PopenResult, bold, colored, debug, dim, excluding, flat_map, fmt, get_pretty_choices, get_right_arrow, get_second, tupled, underline, warn) @@ -780,6 +782,8 @@ def traverse( opt_return_to: TraverseReturnTo, opt_squash_merge_detection: SquashMergeDetection, opt_start_from: TraverseStartFrom, + opt_sync_github_prs: bool, + opt_sync_gitlab_mrs: bool, opt_yes: bool ) -> None: self.expect_at_least_one_managed_branch() @@ -795,6 +799,12 @@ def traverse( if self.__git.get_remotes(): print("") + current_user: Optional[str] = None + if opt_sync_github_prs or opt_sync_gitlab_mrs: + spec = GitHubClient.spec() if opt_sync_github_prs else GitLabClient.spec() + self.__init_code_hosting_client(spec) + current_user = self.code_hosting_client.get_current_user_login() + initial_branch = nearest_remaining_branch = self.__git.get_current_branch() if opt_start_from == TraverseStartFrom.ROOT: @@ -839,6 +849,18 @@ def traverse( else: needs_remote_sync = False + if opt_sync_github_prs or opt_sync_gitlab_mrs: + prs = list(filter(lambda pr: pr.head == branch, self.get_all_open_prs())) + if len(prs) > 1: + spec = self.code_hosting_client._spec + raise MacheteException( + f"Multiple {spec.pr_short_name}s have {branch} as its {spec.head_branch_name} branch: " + + ", ".join(_pr.short_display_text() for _pr in prs)) + pr = prs[0] if prs else None + needs_retarget_pr = pr and upstream and pr.base != upstream + else: + needs_retarget_pr = False + use_merge = opt_merge or (branch in self.annotations and self.annotations[branch].qualifiers.update_with_merge) if needs_slide_out: @@ -864,7 +886,7 @@ def traverse( if needs_parent_sync and branch in self.annotations: needs_parent_sync = self.annotations[branch].qualifiers.rebase - if branch != current_branch and (needs_slide_out or needs_parent_sync or needs_remote_sync): + if branch != current_branch and (needs_slide_out or needs_parent_sync or needs_remote_sync or needs_retarget_pr): self.__print_new_line(False) print(f"Checking out {bold(branch)}") self.__git.checkout(branch) @@ -986,6 +1008,36 @@ def traverse( elif ans in ('q', 'quit'): return + if needs_retarget_pr: + any_action_suggested = True + assert pr is not None + assert upstream is not None + spec = self.code_hosting_client._spec + ans_intro = f"Branch {bold(str(branch))} has a different {spec.pr_short_name} {spec.base_branch_name} ({bold(pr.base)}) " \ + f"in {spec.display_name} than in machete file ({bold(str(upstream))}).\n" + ans = self.ask_if( + ans_intro + f"Retarget {pr.display_text()} to {bold(str(upstream))}?" + get_pretty_choices('y', 'N', 'q', 'yq'), + ans_intro + f"Retargeting {pr.display_text()} to {bold(str(upstream))}...", + opt_yes=opt_yes) + if ans in ('y', 'yes', 'yq'): + self.code_hosting_client.set_base_of_pull_request(pr.number, base=upstream) + print(f'{spec.base_branch_name.capitalize()} branch of {pr.display_text()} has been switched to {bold(str(upstream))}') + pr.base = upstream + + new_description = self.__get_updated_pull_request_description(pr) + if pr.description != new_description: + self.code_hosting_client.set_description_of_pull_request(pr.number, description=new_description) + print(f'Description of {pr.display_text()} has been updated') + pr.description = new_description + + anno = self.__annotations.get(branch) + self.__annotations[branch] = Annotation(self.__pull_request_annotation(spec, pr, current_user), + anno.qualifiers if anno else Qualifiers()) + self.save_branch_layout_file() + + elif ans in ('q', 'quit'): + return + if needs_remote_sync: any_action_suggested = True try: diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index 8ae69fe6d..ff293e788 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -1436,6 +1436,7 @@ git machete t[raverse] [-F|--fetch] [-l|--list-commits] [-M|--merge] [-n|--no-edit-merge|--no-interactive-rebase] [--[no-]push] [--[no-]push-untracked] [--return-to=WHERE] [--start-from=WHERE] [--squash-merge-detection=MODE] + [-H|--sync-github-prs|-L|--sync-gitlab-mrs] [-w|--whole] [-W] [-y|--yes] Traverses the branches in the order as they occur in branch layout file. @@ -1456,6 +1457,9 @@ - asks the user whether to reset (`git reset --keep`) the branch to its remote counterpart * otherwise, if the branch is behind its remote counterpart: - asks the user whether to pull the branch; + * if `-H`/`--sync-github-prs` or `-L`/`--sync-gitlab-mrs` option is present: + - retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, + just as `git machete github retarget-pr` and `git machete gitlab retarget-mr` would do; * and finally, if any of the above operations has been successfully completed: - prints the updated `status`. @@ -1488,6 +1492,12 @@ Options: -F, --fetch Fetch the remotes of all managed branches at the beginning of traversal (no `git pull` involved, only `git fetch`). + -H, --sync-github-prs + Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, + just as `git machete github retarget-pr` would do + -L, --sync-gitlab-mrs + Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, + just as `git machete gitlab retarget-mr` would do -l, --list-commits When printing the status, additionally list the messages of commits introduced on each branch. -M, --merge diff --git a/tests/completion_e2e/test_completion_e2e.py b/tests/completion_e2e/test_completion_e2e.py index 9ba472f53..27b66baf5 100644 --- a/tests/completion_e2e/test_completion_e2e.py +++ b/tests/completion_e2e/test_completion_e2e.py @@ -129,11 +129,15 @@ "git machete status --color=": "always auto never", "git machete t -": - "-F -M -W --debug --fetch -h --help -l --list-commits --merge -n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase " - "--no-push --no-push-untracked --push --push-untracked --return-to --start-from -v --verbose -w --whole -y --yes", + "-F -H -L -M -W --debug --fetch -h --help -l --list-commits --merge " + "-n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase " + "--no-push --no-push-untracked --push --push-untracked --return-to --start-from " + "--sync-github-prs --sync-gitlab-mrs -v --verbose -w --whole -y --yes", "git machete traverse -": - "-F -M -W --debug --fetch -h --help -l --list-commits --merge -n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase " - "--no-push --no-push-untracked --push --push-untracked --return-to --start-from -v --verbose -w --whole -y --yes", + "-F -H -L -M -W --debug --fetch -h --help -l --list-commits --merge " + "-n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase " + "--no-push --no-push-untracked --push --push-untracked --return-to --start-from " + "--sync-github-prs --sync-gitlab-mrs -v --verbose -w --whole -y --yes", "git machete update -": "-M --debug -f --fork-point -h --help --merge -n --no-edit-merge --no-interactive-rebase -v --verbose", "git machete update -f ": From f95c095364f4c586ff3f6df840b08d7890b7c50c Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Wed, 8 Jan 2025 20:31:16 +0100 Subject: [PATCH 2/4] Update related descriptions --- docs/man/git-machete.1 | 2 +- git_machete/client.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index 9f69fbf24..c305caac4 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "GIT-MACHETE" "1" "Dec 17, 2024" "" "git-machete" +.TH "GIT-MACHETE" "1" "Jan 08, 2025" "" "git-machete" .SH NAME git-machete \- git-machete 3.32.0 .sp diff --git a/git_machete/client.py b/git_machete/client.py index e3e4ed395..5684d8da3 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -1013,6 +1013,7 @@ def traverse( assert pr is not None assert upstream is not None spec = self.code_hosting_client._spec + self.__print_new_line(False) ans_intro = f"Branch {bold(str(branch))} has a different {spec.pr_short_name} {spec.base_branch_name} ({bold(pr.base)}) " \ f"in {spec.display_name} than in machete file ({bold(str(upstream))}).\n" ans = self.ask_if( @@ -1024,16 +1025,25 @@ def traverse( print(f'{spec.base_branch_name.capitalize()} branch of {pr.display_text()} has been switched to {bold(str(upstream))}') pr.base = upstream + anno = self.__annotations.get(branch) + self.__annotations[branch] = Annotation(self.__pull_request_annotation(spec, pr, current_user), + anno.qualifiers if anno else Qualifiers()) + self.save_branch_layout_file() + new_description = self.__get_updated_pull_request_description(pr) if pr.description != new_description: self.code_hosting_client.set_description_of_pull_request(pr.number, description=new_description) print(f'Description of {pr.display_text()} has been updated') pr.description = new_description - anno = self.__annotations.get(branch) - self.__annotations[branch] = Annotation(self.__pull_request_annotation(spec, pr, current_user), - anno.qualifiers if anno else Qualifiers()) - self.save_branch_layout_file() + applicable_prs: List[PullRequest] = self.__get_applicable_pull_requests(related_to=pr) + for pr in applicable_prs: + new_description = self.__get_updated_pull_request_description(pr) + if pr.description != new_description: + self.code_hosting_client.set_description_of_pull_request(pr.number, description=new_description) + pr.description = new_description + print(fmt(f'Description of {pr.display_text()} ' + f'({pr.head} {get_right_arrow()} {pr.base}) has been updated')) elif ans in ('q', 'quit'): return From 77a7355176b33c8c148d6f607813073b7871ef48 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Tue, 14 Jan 2025 18:22:12 +0100 Subject: [PATCH 3/4] Add missing test coverage --- docs/man/git-machete.1 | 2 +- tests/mockers.py | 12 +- tests/test_advance.py | 2 +- tests/test_file.py | 2 +- tests/test_traverse.py | 22 +--- tests/test_traverse_github.py | 229 ++++++++++++++++++++++++++++++++++ tests/test_traverse_gitlab.py | 229 ++++++++++++++++++++++++++++++++++ tests/test_update.py | 12 +- 8 files changed, 474 insertions(+), 36 deletions(-) create mode 100644 tests/test_traverse_github.py create mode 100644 tests/test_traverse_gitlab.py diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index c305caac4..dedfacb89 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "GIT-MACHETE" "1" "Jan 08, 2025" "" "git-machete" +.TH "GIT-MACHETE" "1" "Jan 14, 2025" "" "git-machete" .SH NAME git-machete \- git-machete 3.32.0 .sp diff --git a/tests/mockers.py b/tests/mockers.py index ba4c1931b..5a40f193d 100644 --- a/tests/mockers.py +++ b/tests/mockers.py @@ -58,19 +58,19 @@ def assert_success(cmd_and_args: Iterable[str], expected_result: str) -> None: assert actual_result == expected_result -def assert_failure(cmd_and_args: Iterable[str], expected_result: str, expected_exception: Type[Exception] = MacheteException) -> None: - if expected_result.startswith("\n"): +def assert_failure(cmd_and_args: Iterable[str], expected_message: str, expected_type: Type[Exception] = MacheteException) -> None: + if expected_message.startswith("\n"): # removeprefix is only available since Python 3.9 - expected_result = expected_result[1:] - expected_result = textwrap.dedent(expected_result) + expected_message = expected_message[1:] + expected_message = textwrap.dedent(expected_message) - with pytest.raises(expected_exception) as e: + with pytest.raises(expected_type) as e: launch_command(*cmd_and_args) error_message = e.value.msg # type: ignore[attr-defined] error_message = re.sub(" +$", "", error_message, flags=re.MULTILINE) if sys.platform == 'win32': error_message = error_message.replace('.git\\machete', '.git/machete') - assert error_message == expected_result + assert error_message == expected_message def read_branch_layout_file() -> str: diff --git a/tests/test_advance.py b/tests/test_advance.py index 946fa95ef..a97954895 100644 --- a/tests/test_advance.py +++ b/tests/test_advance.py @@ -28,7 +28,7 @@ def test_advance_when_detached_head(self) -> None: rewrite_branch_layout_file("master\n develop") self.repo_sandbox.check_out("HEAD~") - assert_failure(["advance"], "Not currently on any branch", expected_exception=UnderlyingGitException) + assert_failure(["advance"], "Not currently on any branch", expected_type=UnderlyingGitException) def test_advance_for_no_applicable_downstream_branches(self) -> None: ( diff --git a/tests/test_file.py b/tests/test_file.py index a1ea54e64..479c3d238 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -54,7 +54,7 @@ def test_file(self) -> None: def test_file_outside_git_repo(self) -> None: os.chdir(mkdtemp()) - assert_failure(["file", "--debug"], "Not a git repository", expected_exception=UnderlyingGitException) + assert_failure(["file", "--debug"], "Not a git repository", expected_type=UnderlyingGitException) def test_file_when_git_machete_is_a_directory(self) -> None: self.repo_sandbox.execute(f"mkdir .git{os.path.sep}machete") diff --git a/tests/test_traverse.py b/tests/test_traverse.py index 16718cc96..dd43d35b7 100644 --- a/tests/test_traverse.py +++ b/tests/test_traverse.py @@ -66,26 +66,6 @@ def setup_standard_tree(self) -> None: ignore-trailing """ rewrite_branch_layout_file(body) - assert_success( - ["status"], - """ - develop - | - x-allow-ownership-link (ahead of origin) - | | - | x-build-chain (untracked) - | - o-call-ws (ahead of origin) - | - x-drop-constraint (untracked) - - master - | - o-hotfix/add-trigger (diverged from origin) - | - o-ignore-trailing * (diverged from & older than origin) - """ - ) def test_traverse_slide_out(self, mocker: MockerFixture) -> None: ( @@ -1081,7 +1061,7 @@ def test_traverse_reset_keep_failing(self) -> None: assert_failure( ["traverse", "--fetch", "-y", "--debug"], "Cannot perform git reset --keep origin/master. This is most likely caused by local uncommitted changes.", - expected_exception=UnderlyingGitException + expected_type=UnderlyingGitException ) def test_traverse_with_stop_for_edit(self, mocker: MockerFixture) -> None: diff --git a/tests/test_traverse_github.py b/tests/test_traverse_github.py new file mode 100644 index 000000000..a0f1e9d5f --- /dev/null +++ b/tests/test_traverse_github.py @@ -0,0 +1,229 @@ +import textwrap + +from pytest_mock import MockerFixture + +from .base_test import BaseTest +from .mockers import (assert_failure, assert_success, mock_input_returning, + rewrite_branch_layout_file) +from .mockers_code_hosting import mock_from_url +from .mockers_github import MockGitHubAPIState, mock_pr_json, mock_urlopen + + +class TestTraverseGitHub(BaseTest): + + @staticmethod + def github_api_state_for_test_traverse_sync_github_prs_multiple_same_head() -> MockGitHubAPIState: + return MockGitHubAPIState.with_prs( + mock_pr_json(head='build-chain', base='develop', number=1), + mock_pr_json(head='build-chain', base='allow-ownership-link', number=2), + ) + + def test_traverse_sync_github_prs_multiple_same_head(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen( + self.github_api_state_for_test_traverse_sync_github_prs_multiple_same_head())) + + ( + self.repo_sandbox + .new_branch("develop") + .commit() + .new_branch("allow-ownership-link") + .new_branch("build-chain") + ) + + body: str = \ + """ + develop + allow-ownership-link + build-chain + """ + rewrite_branch_layout_file(body) + + assert_failure( + ["traverse", "--sync-github-prs"], + "Multiple PRs have build-chain as its head branch: #1, #2" + ) + + @staticmethod + def github_api_state_for_test_traverse_sync_github_prs() -> MockGitHubAPIState: + return MockGitHubAPIState.with_prs( + mock_pr_json(head='allow-ownership-link', base='develop', number=1), + mock_pr_json(head='build-chain', base='develop', number=2), + mock_pr_json(head='call-ws', base='build-chain', number=3), + ) + + def test_traverse_sync_github_prs(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + github_api_state = self.github_api_state_for_test_traverse_sync_github_prs() + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(github_api_state)) + + ( + self.repo_sandbox + .new_branch("develop") + .commit() + .push() + .new_branch("allow-ownership-link") + .commit() + .push() + .new_branch("build-chain") + .commit() + .push() + .new_branch("call-ws") + .commit() + .push() + ) + + body: str = \ + """ + develop + allow-ownership-link + build-chain + call-ws + """ + rewrite_branch_layout_file(body) + self.repo_sandbox.check_out("build-chain") + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("q")) + assert_success( + ["traverse", "--sync-github-prs"], + """ + Checking for open GitHub PRs... OK + Branch build-chain has a different PR base (develop) in GitHub than in machete file (allow-ownership-link). + Retarget PR #2 to allow-ownership-link? (y, N, q, yq) + """ + ) + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("n")) + assert_success( + ["traverse", "--sync-github-prs"], + """ + Checking for open GitHub PRs... OK + Branch build-chain has a different PR base (develop) in GitHub than in machete file (allow-ownership-link). + Retarget PR #2 to allow-ownership-link? (y, N, q, yq) + + develop + | + o-allow-ownership-link + | + o-build-chain * + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + """ + ) + + assert_success( + ["traverse", "--sync-github-prs", "-Wy"], + """ + Fetching origin... + + Checking out the first root branch (develop) + Checking for open GitHub PRs... OK + + Checking out build-chain + + develop + | + o-allow-ownership-link + | + o-build-chain * + | + o-call-ws + + Branch build-chain has a different PR base (develop) in GitHub than in machete file (allow-ownership-link). + Retargeting PR #2 to allow-ownership-link... + Base branch of PR #2 has been switched to allow-ownership-link + Description of PR #2 has been updated + Description of PR #3 (call-ws -> build-chain) has been updated + + develop + | + o-allow-ownership-link + | + o-build-chain * PR #2 (some_other_user) + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + Returned to the initial branch build-chain + """) + + pr2 = github_api_state.get_pull_by_number(2) + assert pr2 is not None + assert pr2['body'] == textwrap.dedent(''' + + + # Based on PR #1 + + ## Chain of upstream PRs as of 2025-01-14 + + * PR #1: + `develop` ← `allow-ownership-link` + + * **PR #2 (THIS ONE)**: + `allow-ownership-link` ← `build-chain` + + + + # Summary''')[1:] + + pr3 = github_api_state.get_pull_by_number(3) + assert pr3 is not None + assert pr3['body'] == textwrap.dedent(''' + + + # Based on PR #2 + + ## Chain of upstream PRs as of 2025-01-14 + + * PR #1: + `develop` ← `allow-ownership-link` + + * PR #2: + `allow-ownership-link` ← `build-chain` + + * **PR #3 (THIS ONE)**: + `build-chain` ← `call-ws` + + + + # Summary''')[1:] + + # Let's cover the case where the descriptions don't need to be updated after retargeting. + + pr2['base']['ref'] = 'develop' + self.repo_sandbox.check_out("build-chain") + assert_success( + ["traverse", "-HWy"], + """ + Fetching origin... + + Checking out the first root branch (develop) + Checking for open GitHub PRs... OK + + Checking out build-chain + + develop + | + o-allow-ownership-link + | + o-build-chain * PR #2 (some_other_user) + | + o-call-ws + + Branch build-chain has a different PR base (develop) in GitHub than in machete file (allow-ownership-link). + Retargeting PR #2 to allow-ownership-link... + Base branch of PR #2 has been switched to allow-ownership-link + + develop + | + o-allow-ownership-link + | + o-build-chain * PR #2 (some_other_user) + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + Returned to the initial branch build-chain + """) diff --git a/tests/test_traverse_gitlab.py b/tests/test_traverse_gitlab.py new file mode 100644 index 000000000..582e31484 --- /dev/null +++ b/tests/test_traverse_gitlab.py @@ -0,0 +1,229 @@ +import textwrap + +from pytest_mock import MockerFixture + +from .base_test import BaseTest +from .mockers import (assert_failure, assert_success, mock_input_returning, + rewrite_branch_layout_file) +from .mockers_code_hosting import mock_from_url +from .mockers_gitlab import MockGitLabAPIState, mock_mr_json, mock_urlopen + + +class TestTraverseGitLab(BaseTest): + + @staticmethod + def gitlab_api_state_for_test_traverse_sync_gitlab_mrs_multiple_same_source() -> MockGitLabAPIState: + return MockGitLabAPIState.with_mrs( + mock_mr_json(head='build-chain', base='develop', number=1), + mock_mr_json(head='build-chain', base='allow-ownership-link', number=2), + ) + + def test_traverse_sync_gitlab_mrs_multiple_same_source(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen( + self.gitlab_api_state_for_test_traverse_sync_gitlab_mrs_multiple_same_source())) + + ( + self.repo_sandbox + .new_branch("develop") + .commit() + .new_branch("allow-ownership-link") + .new_branch("build-chain") + ) + + body: str = \ + """ + develop + allow-ownership-link + build-chain + """ + rewrite_branch_layout_file(body) + + assert_failure( + ["traverse", "--sync-gitlab-mrs"], + "Multiple MRs have build-chain as its source branch: !1, !2" + ) + + @staticmethod + def gitlab_api_state_for_test_traverse_sync_gitlab_mrs() -> MockGitLabAPIState: + return MockGitLabAPIState.with_mrs( + mock_mr_json(head='allow-ownership-link', base='develop', number=1), + mock_mr_json(head='build-chain', base='develop', number=2), + mock_mr_json(head='call-ws', base='build-chain', number=3), + ) + + def test_traverse_sync_gitlab_mrs(self, mocker: MockerFixture) -> None: + self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) + gitlab_api_state = self.gitlab_api_state_for_test_traverse_sync_gitlab_mrs() + self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(gitlab_api_state)) + + ( + self.repo_sandbox + .new_branch("develop") + .commit() + .push() + .new_branch("allow-ownership-link") + .commit() + .push() + .new_branch("build-chain") + .commit() + .push() + .new_branch("call-ws") + .commit() + .push() + ) + + body: str = \ + """ + develop + allow-ownership-link + build-chain + call-ws + """ + rewrite_branch_layout_file(body) + self.repo_sandbox.check_out("build-chain") + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("q")) + assert_success( + ["traverse", "--sync-gitlab-mrs"], + """ + Checking for open GitLab MRs... OK + Branch build-chain has a different MR target (develop) in GitLab than in machete file (allow-ownership-link). + Retarget MR !2 to allow-ownership-link? (y, N, q, yq) + """ + ) + + self.patch_symbol(mocker, 'builtins.input', mock_input_returning("n")) + assert_success( + ["traverse", "--sync-gitlab-mrs"], + """ + Checking for open GitLab MRs... OK + Branch build-chain has a different MR target (develop) in GitLab than in machete file (allow-ownership-link). + Retarget MR !2 to allow-ownership-link? (y, N, q, yq) + + develop + | + o-allow-ownership-link + | + o-build-chain * + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + """ + ) + + assert_success( + ["traverse", "--sync-gitlab-mrs", "-Wy"], + """ + Fetching origin... + + Checking out the first root branch (develop) + Checking for open GitLab MRs... OK + + Checking out build-chain + + develop + | + o-allow-ownership-link + | + o-build-chain * + | + o-call-ws + + Branch build-chain has a different MR target (develop) in GitLab than in machete file (allow-ownership-link). + Retargeting MR !2 to allow-ownership-link... + Target branch of MR !2 has been switched to allow-ownership-link + Description of MR !2 has been updated + Description of MR !3 (call-ws -> build-chain) has been updated + + develop + | + o-allow-ownership-link + | + o-build-chain * MR !2 (some_other_user) + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + Returned to the initial branch build-chain + """) + + mr2 = gitlab_api_state.get_mr_by_number(2) + assert mr2 is not None + assert mr2['description'] == textwrap.dedent(''' + + + # Based on MR !1 + + ## Chain of upstream MRs as of 2025-01-14 + + * MR !1: + `develop` ← `allow-ownership-link` + + * **MR !2 (THIS ONE)**: + `allow-ownership-link` ← `build-chain` + + + + # Summary''')[1:] + + mr3 = gitlab_api_state.get_mr_by_number(3) + assert mr3 is not None + assert mr3['description'] == textwrap.dedent(''' + + + # Based on MR !2 + + ## Chain of upstream MRs as of 2025-01-14 + + * MR !1: + `develop` ← `allow-ownership-link` + + * MR !2: + `allow-ownership-link` ← `build-chain` + + * **MR !3 (THIS ONE)**: + `build-chain` ← `call-ws` + + + + # Summary''')[1:] + + # Let's cover the case where the descriptions don't need to be updated after retargeting. + + mr2['target_branch'] = 'develop' + self.repo_sandbox.check_out("build-chain") + assert_success( + ["traverse", "-LWy"], + """ + Fetching origin... + + Checking out the first root branch (develop) + Checking for open GitLab MRs... OK + + Checking out build-chain + + develop + | + o-allow-ownership-link + | + o-build-chain * MR !2 (some_other_user) + | + o-call-ws + + Branch build-chain has a different MR target (develop) in GitLab than in machete file (allow-ownership-link). + Retargeting MR !2 to allow-ownership-link... + Target branch of MR !2 has been switched to allow-ownership-link + + develop + | + o-allow-ownership-link + | + o-build-chain * MR !2 (some_other_user) + | + o-call-ws + + No successor of build-chain needs to be slid out or synced with upstream branch or remote; nothing left to update + Returned to the initial branch build-chain + """) diff --git a/tests/test_update.py b/tests/test_update.py index 70fd76b7b..b17ffd208 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -108,7 +108,7 @@ def test_update_drops_empty_commits(self) -> None: with fixed_author_and_committer_date_in_past(): expected_error_message = "git rebase --interactive --onto refs/heads/level-0-branch " \ "c0306cdd500fc39869505592200258055407bcc6 level-1-branch returned 1" - assert_failure(["update"], expected_error_message, expected_exception=UnderlyingGitException) + assert_failure(["update"], expected_error_message, expected_type=UnderlyingGitException) self.repo_sandbox.execute("git rebase --continue") new_fork_point_hash = launch_command("fork-point").strip() @@ -341,7 +341,7 @@ def test_update_during_side_effecting_operations(self) -> None: assert_failure(["update"], "git am session in progress. Conclude git am first " "with git am --continue or git am --abort.", - expected_exception=UnderlyingGitException) + expected_type=UnderlyingGitException) self.repo_sandbox.execute("git am --abort") @@ -352,7 +352,7 @@ def test_update_during_side_effecting_operations(self) -> None: assert_failure(["update"], "Cherry pick in progress. Conclude the cherry pick first with " "git cherry-pick --continue or git cherry-pick --abort.", - expected_exception=UnderlyingGitException) + expected_type=UnderlyingGitException) self.repo_sandbox.execute("git cherry-pick --abort") @@ -363,7 +363,7 @@ def test_update_during_side_effecting_operations(self) -> None: assert_failure(["update"], "Merge in progress. Conclude the merge first with " "git merge --continue or git merge --abort.", - expected_exception=UnderlyingGitException) + expected_type=UnderlyingGitException) self.repo_sandbox.execute("git merge --abort") @@ -375,7 +375,7 @@ def test_update_during_side_effecting_operations(self) -> None: assert_failure(["update"], "Rebase of develop in progress. Conclude the rebase first with " "git rebase --continue or git rebase --abort.", - expected_exception=UnderlyingGitException) + expected_type=UnderlyingGitException) self.repo_sandbox.execute("git rebase --abort") @@ -386,6 +386,6 @@ def test_update_during_side_effecting_operations(self) -> None: assert_failure(["update"], "Revert in progress. Conclude the revert first with " "git revert --continue or git revert --abort.", - expected_exception=UnderlyingGitException) + expected_type=UnderlyingGitException) self.repo_sandbox.execute("git revert --abort") From 55aacd21e04e3a40f636597dcb735dc2d5126719 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Tue, 14 Jan 2025 19:22:41 +0100 Subject: [PATCH 4/4] Tidy up --- RELEASE_NOTES.md | 2 +- completion/git-machete.completion.zsh | 4 ++-- docs/man/git-machete.1 | 11 +++++------ docs/source/cli/traverse.rst | 11 +++++------ git_machete/generated_docs.py | 11 +++++------ 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index eac08d5fe..8df35a261 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ ## New in git-machete 3.32.0 -- added: flags `-H`/`--sync-github-prs` and `-L`/`--sync-gitlab-mrs` to `traverse` to automatically retarget PRs/MRs when traversing (suggested by @chriscz) +- added: flags `-H`/`--sync-github-prs` and `-L`/`--sync-gitlab-mrs` to `traverse` to suggest retargeting PRs/MRs when traversing (suggested by @chriscz) ## New in git-machete 3.31.1 diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh index a8eaf7d35..854f1d226 100644 --- a/completion/git-machete.completion.zsh +++ b/completion/git-machete.completion.zsh @@ -140,8 +140,8 @@ _git-machete() { (t|traverse) _arguments \ '(-F --fetch)'{-F,--fetch}'[Fetch the remotes of all managed branches at the beginning of traversal]' \ - '(-H --sync-github-prs)'{-H,--sync-github-prs}'[Retarget GitHub PR when its base branch is different than in machete file]' \ - '(-L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Retarget GitLab MR when its target branch is different than in machete file]' \ + '(-H --sync-github-prs)'{-H,--sync-github-prs}'[Retarget GitHub PRs when their base branches are different than in machete file]' \ + '(-L --sync-gitlab-mrs)'{-L,--sync-gitlab-mrs}'[Retarget GitLab MRs when their target branches are different than in machete file]' \ '(-l --list-commits)'{-l,--list-commits}'[List the messages of commits introduced on each branch]' \ '(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \ '(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \ diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index dedfacb89..4df2433d9 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -2082,8 +2082,9 @@ 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 -retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, -just as \fBgit machete github retarget\-pr\fP and \fBgit machete gitlab retarget\-mr\fP would do; +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 +(just as \fBgit machete github retarget\-pr\fP and \fBgit machete gitlab retarget\-mr\fP would do); .UNINDENT .IP \(bu 2 and finally, if any of the above operations has been successfully completed: @@ -2131,12 +2132,10 @@ when the current user is NOT the author of the PR/MR associated with that branch Fetch the remotes of all managed branches at the beginning of traversal (no \fBgit pull\fP involved, only \fBgit fetch\fP). .TP .B \-H\fP,\fB \-\-sync\-github\-prs -Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, -just as \fBgit machete github retarget\-pr\fP would do +Retarget the PR if it exists for the given branch and its base branch in GitHub is different than the upstream in machete file .TP .B \-L\fP,\fB \-\-sync\-gitlab\-mrs -Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, -just as \fBgit machete gitlab retarget\-mr\fP would do +Retarget the MR if it exists for the given branch and its target branch in GitLab is different than the upstream in machete file .TP .B \-l\fP,\fB \-\-list\-commits When printing the status, additionally list the messages of commits introduced on each branch. diff --git a/docs/source/cli/traverse.rst b/docs/source/cli/traverse.rst index 1df72fe35..c504cedae 100644 --- a/docs/source/cli/traverse.rst +++ b/docs/source/cli/traverse.rst @@ -55,8 +55,9 @@ For each branch, the command: * if ``-H``/``--sync-github-prs`` or ``-L``/``--sync-gitlab-mrs`` option is present: - - retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, - just as ``git machete github retarget-pr`` and ``git machete gitlab retarget-mr`` would do; + - 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 + (just as ``git machete github retarget-pr`` and ``git machete gitlab retarget-mr`` would do); * and finally, if any of the above operations has been successfully completed: @@ -94,11 +95,9 @@ when the current user is NOT the author of the PR/MR associated with that branch -F, --fetch Fetch the remotes of all managed branches at the beginning of traversal (no ``git pull`` involved, only ``git fetch``). --H, --sync-github-prs Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, - just as ``git machete github retarget-pr`` would do +-H, --sync-github-prs Retarget the PR if it exists for the given branch and its base branch in GitHub is different than the upstream in machete file --L, --sync-gitlab-mrs Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, - just as ``git machete gitlab retarget-mr`` would do +-L, --sync-gitlab-mrs Retarget the MR if it exists for the given branch and its target branch in GitLab is different than the upstream in machete file -l, --list-commits When printing the status, additionally list the messages of commits introduced on each branch. diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index ff293e788..af7fa2746 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -1458,8 +1458,9 @@ * otherwise, if the branch is behind its remote counterpart: - asks the user whether to pull the branch; * if `-H`/`--sync-github-prs` or `-L`/`--sync-gitlab-mrs` option is present: - - retargets the PR/MR if it exists for the given branch and has a different base/target branch in GitHub/GitLab than the upstream in machete file, - just as `git machete github retarget-pr` and `git machete gitlab retarget-mr` would do; + - 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 + (just as `git machete github retarget-pr` and `git machete gitlab retarget-mr` would do); * and finally, if any of the above operations has been successfully completed: - prints the updated `status`. @@ -1493,11 +1494,9 @@ -F, --fetch Fetch the remotes of all managed branches at the beginning of traversal (no `git pull` involved, only `git fetch`). -H, --sync-github-prs - Retarget the PR if it exists for the given branch and has a different base branch in GitHub than the upstream in machete file, - just as `git machete github retarget-pr` would do + Retarget the PR if it exists for the given branch and its base branch in GitHub is different than the upstream in machete file -L, --sync-gitlab-mrs - Retarget the MR if it exists for the given branch and has a different target branch in GitLab than the upstream in machete file, - just as `git machete gitlab retarget-mr` would do + Retarget the MR if it exists for the given branch and its target branch in GitLab is different than the upstream in machete file -l, --list-commits When printing the status, additionally list the messages of commits introduced on each branch. -M, --merge