From 19c18df5275cea92d50cea3535a996b67753fb90 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Tue, 8 Oct 2024 16:37:37 +0200 Subject: [PATCH] Update multiple PR/MR descriptions in bulk (flags + subcommands) --- docs/man/git-machete.1 | 63 +++++++++++++++++++++-- docs/source/cli/github.rst | 40 ++++++++++++--- git_machete/cli.py | 97 ++++++++++++++++++++--------------- git_machete/client.py | 31 +++++++---- git_machete/generated_docs.py | 32 +++++++++++- git_machete/options.py | 1 + 6 files changed, 200 insertions(+), 64 deletions(-) diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index b717bab04..ccabe033a 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -27,9 +27,9 @@ 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" "Oct 10, 2024" "" "git-machete" +.TH "GIT-MACHETE" "1" "Oct 14, 2024" "" "git-machete" .SH NAME -git-machete \- git-machete 3.30.1 +git-machete \- git-machete 3.31.0 .sp git machete is a robust tool that \fBsimplifies your git workflows\fP\&. .sp @@ -899,7 +899,7 @@ git machete github .UNINDENT .UNINDENT .sp -where \fB\fP is one of: \fBanno\-prs\fP, \fBcheckout\-prs\fP, \fBcreate\-pr\fP, \fBretarget\-pr\fP or \fBrestack\-pr\fP\&. +where \fB\fP is one of: \fBanno\-prs\fP, \fBcheckout\-prs\fP, \fBcreate\-pr\fP, \fBretarget\-pr\fP, \fBrestack\-pr\fP or \fBupdate\-pr\-descriptions\fP\&. .sp Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file. .sp @@ -998,7 +998,7 @@ Once the PR is successfully created, annotates the current branch with the new P If \fB\&.git/info/milestone\fP file is present, its contents (a single number \-\-\- milestone id) are used as milestone. If \fB\&.git/info/reviewers\fP file is present, its contents (one GitHub login per line) are used to set reviewers. .sp -The subject of the first unique commit of the branch is used as PR title. +Unless \fB\-\-title\fP is provided, the subject of the first unique commit of the branch is used as PR title. If \fB\&.git/info/description\fP or \fB\&.github/pull_request_template.md\fP template is present, its contents are used as PR description. Otherwise (or if \fBmachete.github.forceDescriptionFromCommitMessage\fP is set), PR description is taken from message body of the first unique commit of the branch. .sp @@ -1016,6 +1016,10 @@ Create the new PR as a draft. .BI \-\-title\fB= Set the PR title explicitly (the default is to use the first included commit\(aqs message as the title). .TP +.B \-U\fP,\fB \-\-update\-related\-descriptions +Update the generated sections in related PR descriptions. +See help for \fBupdate\-pr\-descriptions \-\-related\fP below for extra considerations. +.TP .B \-\-yes Do not ask for confirmation whether to push the branch. .UNINDENT @@ -1035,6 +1039,14 @@ If the PR has been converted to draft in step 1, it\(aqs reverted to ready for r .sp The drafting/undrafting is useful in case the GitHub repository has set up \X'tty: link https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners'\fI\%CODEOWNERS\fP\X'tty: link'\&. Draft PRs don\(aqt get code owners automatically added as reviewers. +.sp +\fBOptions:\fP +.INDENT 7.0 +.TP +.B \-U\fP,\fB \-\-update\-related\-descriptions +Update the generated sections in related PR descriptions. +See help for \fBupdate\-pr\-descriptions \-\-related\fP below for extra considerations. +.UNINDENT .TP .B \fBretarget\-pr [\-b|\-\-branch=<branch>] [\-\-ignore\-if\-missing]\fP: Sets the base of the current (or specified) branch\(aqs PR to upstream (parent) branch, as seen by git machete (see \fBgit machete show up\fP). @@ -1052,6 +1064,10 @@ Specify the branch for which the associated PR base will be set to its upstream .TP .B \-\-ignore\-if\-missing Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch. +.TP +.B \-U\fP,\fB \-\-update\-related\-descriptions +Update the generated sections in related PR descriptions. +See help for \fBupdate\-pr\-descriptions \-\-related\fP below for extra considerations. .UNINDENT .TP .B \fBsync\fP: @@ -1069,6 +1085,29 @@ deletes untracked managed branches that have no downstream branch. .UNINDENT .UNINDENT .sp +\fBupdate\-pr\-descriptions\fP: +.INDENT 0.0 +.INDENT 3.5 +Updates the generated sections (\(dqintro\(dq) of PR descriptions that lists the upstream and/or downstream PRs +(depending on \fBmachete.github.prDescriptionIntroStyle\fP git config key). +.sp +\fBOptions:\fP +\(em all Update PR descriptions for all PRs in the repository. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.INDENT 3.5 +.INDENT 0.0 +.TP +.B \-\-mine +Update PR descriptions for all PRs opened by the current user associated with the GitHub token. +.UNINDENT +\(em related Update PR descriptions for all PRs that are upstream and/or downstream of the PR for the current branch. +If \fBmachete.github.prDescriptionIntroStyle\fP is \fBup\-only\fP (default), then only downstream PR descriptions are updated. +If \fBmachete.github.prDescriptionIntroStyle\fP is \fBfull\fP, then both downstream and upstream PR descriptions are updated. +.UNINDENT +.UNINDENT +.sp \fBGit config keys:\fP .INDENT 0.0 .TP @@ -1956,6 +1995,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 @@ -2002,6 +2042,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 @@ -2046,6 +2093,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/github.rst b/docs/source/cli/github.rst index 5e0667be2..6be63a4d5 100644 --- a/docs/source/cli/github.rst +++ b/docs/source/cli/github.rst @@ -8,7 +8,7 @@ github git machete github <subcommand> -where ``<subcommand>`` is one of: ``anno-prs``, ``checkout-prs``, ``create-pr``, ``retarget-pr`` or ``restack-pr``. +where ``<subcommand>`` is one of: ``anno-prs``, ``checkout-prs``, ``create-pr``, ``retarget-pr``, ``restack-pr`` or ``update-pr-descriptions``. Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file. @@ -83,7 +83,7 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc If ``.git/info/milestone`` file is present, its contents (a single number --- milestone id) are used as milestone. If ``.git/info/reviewers`` file is present, its contents (one GitHub login per line) are used to set reviewers. - The subject of the first unique commit of the branch is used as PR title. + Unless ``--title`` is provided, the subject of the first unique commit of the branch is used as PR title. If ``.git/info/description`` or ``.github/pull_request_template.md`` template is present, its contents are used as PR description. Otherwise (or if ``machete.github.forceDescriptionFromCommitMessage`` is set), PR description is taken from message body of the first unique commit of the branch. @@ -94,11 +94,14 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc **Options:** - --draft Create the new PR as a draft. + --draft Create the new PR as a draft. - --title=<title> Set the PR title explicitly (the default is to use the first included commit's message as the title). + --title=<title> Set the PR title explicitly (the default is to use the first included commit's message as the title). - --yes Do not ask for confirmation whether to push the branch. + -U, --update-related-descriptions Update the generated sections in related PR descriptions. + See help for ``update-pr-descriptions --related`` below for extra considerations. + + --yes Do not ask for confirmation whether to push the branch. ``restack-pr``: Perform the following sequence of actions: @@ -111,6 +114,11 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc The drafting/undrafting is useful in case the GitHub repository has set up `CODEOWNERS <https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners>`_. Draft PRs don't get code owners automatically added as reviewers. + **Options:** + + -U, --update-related-descriptions Update the generated sections in related PR descriptions. + See help for ``update-pr-descriptions --related`` below for extra considerations. + ``retarget-pr [-b|--branch=<branch>] [--ignore-if-missing]``: Sets the base of the current (or specified) branch's PR to upstream (parent) branch, as seen by git machete (see ``git machete show up``). @@ -121,9 +129,12 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc **Options:** - -b, --branch=<branch> Specify the branch for which the associated PR base will be set to its upstream (parent) branch. The current branch is used if the option is absent. + -b, --branch=<branch> Specify the branch for which the associated PR base will be set to its upstream (parent) branch. The current branch is used if the option is absent. + + --ignore-if-missing Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch. - --ignore-if-missing Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch. + -U, --update-related-descriptions Update the generated sections in related PR descriptions. + See help for ``update-pr-descriptions --related`` below for extra considerations. ``sync``: **Deprecated.** Use ``github checkout-prs --mine``, ``delete-unmanaged`` and ``slide-out --removed-from-remote``. @@ -135,6 +146,21 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc #. deletes unmanaged branches, #. deletes untracked managed branches that have no downstream branch. +``update-pr-descriptions``: + + Updates the generated sections ("intro") of PR descriptions that lists the upstream and/or downstream PRs + (depending on ``machete.github.prDescriptionIntroStyle`` git config key). + + **Options:** + + --all Update PR descriptions for all PRs in the repository. + + --mine Update PR descriptions for all PRs opened by the current user associated with the GitHub token. + + --related Update PR descriptions for all PRs that are upstream and/or downstream of the PR for the current branch. + If ``machete.github.prDescriptionIntroStyle`` is ``up-only`` (default), then only downstream PR descriptions are updated. + If ``machete.github.prDescriptionIntroStyle`` is ``full``, then both downstream and upstream PR descriptions are updated. + **Git config keys:** ``machete.github.{domain,remote,organization,repository}`` (all subcommands): diff --git a/git_machete/cli.py b/git_machete/cli.py index 8d5b1d5c2..cf3f45b4a 100644 --- a/git_machete/cli.py +++ b/git_machete/cli.py @@ -240,7 +240,7 @@ def create_cli_parser() -> argparse.ArgumentParser: fork_point_exclusive_optional_args.add_argument('--override-to-parent', action='store_true') fork_point_exclusive_optional_args.add_argument('--unset-override', action='store_true') - def add_code_hosting_parser(command: str, subcommand_suffix: str, include_sync: bool) -> Any: + def add_code_hosting_parser(command: str, pr_or_mr: str, include_sync: bool) -> Any: parser = subparsers.add_parser( command, argument_default=argparse.SUPPRESS, @@ -248,11 +248,12 @@ def add_code_hosting_parser(command: str, subcommand_suffix: str, include_sync: add_help=False, parents=[common_args_parser]) parser.add_argument('subcommand', choices=[ - f'anno-{subcommand_suffix}s', - f'checkout-{subcommand_suffix}s', - f'create-{subcommand_suffix}', - f'restack-{subcommand_suffix}', - f'retarget-{subcommand_suffix}' + f'anno-{pr_or_mr}s', + f'checkout-{pr_or_mr}s', + f'create-{pr_or_mr}', + f'restack-{pr_or_mr}', + f'retarget-{pr_or_mr}', + f'update-{pr_or_mr}-descriptions' ] + (['sync'] if include_sync else [])) parser.add_argument('request_id', nargs='*', type=int) parser.add_argument('-b', '--branch') @@ -261,9 +262,11 @@ def add_code_hosting_parser(command: str, subcommand_suffix: str, include_sync: parser.add_argument('--draft', action='store_true') parser.add_argument('--ignore-if-missing', action='store_true') parser.add_argument('--mine', action='store_true') + parser.add_argument('--related', action='store_true') parser.add_argument('--title') parser.add_argument('--with-urls', action='store_true') parser.add_argument('--yes', action='store_true') + add_code_hosting_parser('github', 'pr', include_sync=True) add_code_hosting_parser('gitlab', 'mr', include_sync=False) @@ -463,9 +466,6 @@ def update_cli_options_using_parsed_args( elif opt == "no_detect_squash_merges": warn("`--no-detect-squash-merges` is deprecated, use `--squash-merge-detection=none` instead", end="\n\n") cli_opts.opt_squash_merge_detection_string = "none" - elif opt == "squash_merge_detection" and arg is not None: # if no arg is passed, argparse will fail anyway - cli_opts.opt_squash_merge_detection_string = arg - cli_opts.opt_squash_merge_detection_origin = "`--squash-merge-detection` flag" elif opt == "no_edit_merge": cli_opts.opt_no_edit_merge = True elif opt == "no_interactive_rebase": @@ -488,12 +488,17 @@ def update_cli_options_using_parsed_args( cli_opts.opt_push_untracked = True elif opt == "push_untracked": cli_opts.opt_push_untracked = True + elif opt == "related": + cli_opts.opt_related = True elif opt == "removed_from_remote": cli_opts.opt_removed_from_remote = True elif opt == "return_to": cli_opts.opt_return_to = arg elif opt == "roots": cli_opts.opt_roots = list(map(LocalBranchShortName.of, filter(None, arg.split(",")))) + elif opt == "squash_merge_detection" and arg is not None: # if no arg is passed, argparse will fail anyway + cli_opts.opt_squash_merge_detection_string = arg + cli_opts.opt_squash_merge_detection_origin = "`--squash-merge-detection` flag" elif opt == "start_from": cli_opts.opt_start_from = arg elif opt == "stat": @@ -730,41 +735,43 @@ def print_completion_resource(name: str) -> None: elif cmd in ("github", "gitlab"): subcommand = parsed_cli.subcommand config = GitHubClient.spec() if cmd == "github" else GitLabClient.spec() - subcommand_suffix = "pr" if cmd == "github" else "mr" + pr_or_mr = "pr" if cmd == "github" else "mr" machete_client.read_branch_layout_file(perform_interactive_slide_out=should_perform_interactive_slide_out) - if 'request_id' in parsed_cli and subcommand != f'checkout-{subcommand_suffix}s': - raise MacheteException(f"`request_id` option is only valid with `checkout-{subcommand_suffix}s` subcommand.") + if 'request_id' in parsed_cli and subcommand != f'checkout-{pr_or_mr}s': + raise MacheteException(f"`request_id` option is only valid with `checkout-{pr_or_mr}s` subcommand.") for command in ('all', 'by', 'mine'): - if command in parsed_cli and subcommand != f"checkout-{subcommand_suffix}s": - raise MacheteException(f"`--{command}` option is only valid with `checkout-{subcommand_suffix}s` subcommand.") - if "branch" in parsed_cli and subcommand != f"retarget-{subcommand_suffix}": - raise MacheteException(f"`--branch` option is only valid with `retarget-{subcommand_suffix}` subcommand.") - if "draft" in parsed_cli and subcommand != f"create-{subcommand_suffix}": - raise MacheteException(f"`--draft` option is only valid with `create-{subcommand_suffix}` subcommand.") - if "ignore_if_missing" in parsed_cli and subcommand != f"retarget-{subcommand_suffix}": - raise MacheteException(f"`--ignore-if-missing` option is only valid with `retarget-{subcommand_suffix}` subcommand.") - if "title" in parsed_cli and subcommand != f"create-{subcommand_suffix}": - raise MacheteException(f"`--title` option is only valid with `create-{subcommand_suffix}` subcommand.") - if "with_urls" in parsed_cli and subcommand != f"anno-{subcommand_suffix}s": - raise MacheteException(f"`--with-urls` option is only valid with `anno-{subcommand_suffix}s` subcommand.") - if "yes" in parsed_cli and subcommand != f"create-{subcommand_suffix}": - raise MacheteException(f"`--yes` option is only valid with `create-{subcommand_suffix}` subcommand.") - - if subcommand == f"anno-{subcommand_suffix}s": + if command in parsed_cli and subcommand != f"checkout-{pr_or_mr}s": + raise MacheteException(f"`--{command}` option is only valid with `checkout-{pr_or_mr}s` subcommand.") + if "branch" in parsed_cli and subcommand != f"retarget-{pr_or_mr}": + raise MacheteException(f"`--branch` option is only valid with `retarget-{pr_or_mr}` subcommand.") + if "draft" in parsed_cli and subcommand != f"create-{pr_or_mr}": + raise MacheteException(f"`--draft` option is only valid with `create-{pr_or_mr}` subcommand.") + if "ignore_if_missing" in parsed_cli and subcommand != f"retarget-{pr_or_mr}": + raise MacheteException(f"`--ignore-if-missing` option is only valid with `retarget-{pr_or_mr}` subcommand.") + if "title" in parsed_cli and subcommand != f"create-{pr_or_mr}": + raise MacheteException(f"`--title` option is only valid with `create-{pr_or_mr}` subcommand.") + if "with_urls" in parsed_cli and subcommand != f"anno-{pr_or_mr}s": + raise MacheteException(f"`--with-urls` option is only valid with `anno-{pr_or_mr}s` subcommand.") + if "yes" in parsed_cli and subcommand != f"create-{pr_or_mr}": + raise MacheteException(f"`--yes` option is only valid with `create-{pr_or_mr}` subcommand.") + + if subcommand == f"anno-{pr_or_mr}s": machete_client.sync_annotations_to_prs(config, include_urls=cli_opts.opt_with_urls) - elif subcommand == f"checkout-{subcommand_suffix}s": + elif subcommand == f"checkout-{pr_or_mr}s": if len(set(parsed_cli_as_dict.keys()).intersection({'all', 'by', 'mine', 'request_id'})) != 1: - raise MacheteException(f"`checkout-{subcommand_suffix}s` subcommand must take exactly one of the following options: " + - ', '.join(['--all', '--by=...', '--mine', f'{subcommand_suffix}-number(s)'])) - machete_client.checkout_pull_requests(config, - pr_numbers=parsed_cli.request_id if 'request_id' in parsed_cli else [], - all=parsed_cli.all if 'all' in parsed_cli else False, - mine=parsed_cli.mine if 'mine' in parsed_cli else False, - by=parsed_cli.by if 'by' in parsed_cli else None, - fail_on_missing_current_user_for_my_opened_prs=True) - elif subcommand == f"create-{subcommand_suffix}": + raise MacheteException( + f"`checkout-{pr_or_mr}s` subcommand must take exactly one of the following options: " + f'`--all`, `--by=...`, `--mine`, `{pr_or_mr}-number(s)`') + machete_client.checkout_pull_requests( + config, + pr_numbers=parsed_cli.request_id if 'request_id' in parsed_cli else [], + all=parsed_cli.all if 'all' in parsed_cli else False, + mine=parsed_cli.mine if 'mine' in parsed_cli else False, + by=parsed_cli.by if 'by' in parsed_cli else None, + fail_on_missing_current_user_for_my_opened_prs=True) + elif subcommand == f"create-{pr_or_mr}": current_branch = git.get_current_branch() machete_client.create_pull_request( config, @@ -773,9 +780,9 @@ def print_completion_resource(name: str) -> None: opt_onto=cli_opts.opt_onto, opt_title=cli_opts.opt_title, opt_yes=cli_opts.opt_yes) - elif subcommand == f"restack-{subcommand_suffix}": + elif subcommand == f"restack-{pr_or_mr}": machete_client.restack_pull_request(config) - elif subcommand == f"retarget-{subcommand_suffix}": + elif subcommand == f"retarget-{pr_or_mr}": branch = parsed_cli.branch if 'branch' in parsed_cli else git.get_current_branch() machete_client.expect_in_managed_branches(branch) ignore_if_missing = parsed_cli.ignore_if_missing if 'ignore_if_missing' in parsed_cli else False @@ -784,6 +791,16 @@ def print_completion_resource(name: str) -> None: machete_client.checkout_pull_requests(config, pr_numbers=[], mine=True) machete_client.delete_unmanaged(opt_squash_merge_detection=SquashMergeDetection.NONE, opt_yes=False) machete_client.delete_untracked(opt_yes=cli_opts.opt_yes) + elif subcommand == f"update-{pr_or_mr}-descriptions": + if len(set(parsed_cli_as_dict.keys()).intersection({'all', 'related', 'mine'})) != 1: + raise MacheteException( + f"`update-{pr_or_mr}-descriptions` subcommand must take exactly one of the following options: " + '`--all`, `--mine`, `--related`') + machete_client.update_pull_request_descriptions( + config, + all=parsed_cli.all if 'all' in parsed_cli else False, + mine=parsed_cli.mine if 'mine' in parsed_cli else False, + related=parsed_cli.related if 'related' in parsed_cli else False) else: # an unknown subcommand is handled by argparse raise UnexpectedMacheteException(f"Unknown subcommand: `{subcommand}`") elif cmd == "is-managed": diff --git a/git_machete/client.py b/git_machete/client.py index eb9e46407..36c8231ac 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -30,8 +30,8 @@ from .github import GitHubClient from .gitlab import GitLabClient from .utils import (AnsiEscapeCodes, PopenResult, bold, colored, debug, dim, - excluding, find_or_none, flat_map, fmt, get_pretty_choices, - get_second, tupled, underline, warn) + excluding, flat_map, fmt, get_pretty_choices, get_second, + tupled, underline, warn) class SyncToParentStatus(Enum): @@ -719,9 +719,9 @@ def connected_with_green_edge(bd: LocalBranchShortName) -> bool: anno = self.annotations.get(branch) if remote and (not anno or anno.qualifiers.push): push_msg = f"\nBranch {bold(branch)} is now fast-forwarded to match {bold(down_branch)}. " \ - f"Push {bold(branch)} to {bold(remote)}?" + get_pretty_choices('y', 'N') + f"Push {bold(branch)} to {bold(remote)}?" + get_pretty_choices('y', 'N') opt_yes_push_msg = f"\nBranch {bold(branch)} is now fast-forwarded to match {bold(down_branch)}. " \ - f"Pushing {bold(branch)} to {bold(remote)}..." + f"Pushing {bold(branch)} to {bold(remote)}..." ans = self.ask_if(push_msg, opt_yes_push_msg, opt_yes=opt_yes) if ans in ('y', 'yes'): self.__git.push(remote, branch) @@ -1006,7 +1006,7 @@ def traverse( assert code_hosting_client is not None spec = 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))}). " + f"in {spec.display_name} than in machete file ({bold(str(upstream))}). " 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))}...", @@ -1203,8 +1203,8 @@ def print_line_prefix(branch_: LocalBranchShortName, suffix: str) -> None: right_arrow = colored(utils.get_right_arrow(), AnsiEscapeCodes.RED) fork_point_str = colored("fork point ???", AnsiEscapeCodes.RED) fp_suffix: str = f' {right_arrow} {fork_point_str} ' + \ - ("this commit" if opt_list_commits_with_hashes else f"commit {commit.short_hash}") + \ - f' seems to be a part of the unique history of {fp_branches_formatted}' + ("this commit" if opt_list_commits_with_hashes else f"commit {commit.short_hash}") + \ + f' seems to be a part of the unique history of {fp_branches_formatted}' else: fp_suffix = '' print_line_prefix(branch, utils.get_vertical_bar()) @@ -1297,7 +1297,7 @@ def print_line_prefix(branch_: LocalBranchShortName, suffix: str) -> None: else: affected_branches = ", ".join(map(bold, branches_in_sync_but_fork_point_off)) first_part = f"yellow edges indicate that fork points for {affected_branches} are probably incorrectly inferred,\n" \ - "or that some extra branch should be added between each of these branches and its parent" + "or that some extra branch should be added between each of these branches and its parent" if not opt_list_commits: second_part = "Run `git machete status --list-commits` or " \ @@ -2006,11 +2006,11 @@ def __pick_remote( print("\n".join(f"[{index + 1}] {rem}" for index, rem in enumerate(rems))) if is_called_from_traverse: msg = f"Select number 1..{len(rems)} to specify the destination remote " \ - "repository, or 'n' to skip this branch, or " \ - "'q' to quit the traverse: " + "repository, or 'n' to skip this branch, or " \ + "'q' to quit the traverse: " else: msg = f"Select number 1..{len(rems)} to specify the destination remote " \ - "repository, or 'q' to quit the operation: " + "repository, or 'q' to quit the operation: " ans = input(msg).lower() if ans in ('q', 'quit'): @@ -2228,6 +2228,15 @@ def check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch( f"Fork point {bold(fork_point_hash)} is not ancestor of or the tip " f"of the {bold(branch)} branch.") + def update_pull_request_descriptions(self, + spec: CodeHostingSpec, + *, + all: bool = False, + mine: bool = False, + related: bool = False, + ) -> None: + pass + def checkout_pull_requests(self, spec: CodeHostingSpec, pr_numbers: Optional[List[int]], diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index a5b95a9ad..b7829fe03 100644 --- a/git_machete/generated_docs.py +++ b/git_machete/generated_docs.py @@ -564,7 +564,7 @@ <b>Usage:</b><b> git machete github <subcommand></b> - where `<subcommand>` is one of: `anno-prs`, `checkout-prs`, `create-pr`, `retarget-pr` or `restack-pr`. + where `<subcommand>` is one of: `anno-prs`, `checkout-prs`, `create-pr`, `retarget-pr`, `restack-pr` or `update-pr-descriptions`. Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file. @@ -638,7 +638,7 @@ If `.git/info/milestone` file is present, its contents (a single number — milestone id) are used as milestone. If `.git/info/reviewers` file is present, its contents (one GitHub login per line) are used to set reviewers. - The subject of the first unique commit of the branch is used as PR title. + Unless `--title` is provided, the subject of the first unique commit of the branch is used as PR title. If `.git/info/description` or `.github/pull_request_template.md` template is present, its contents are used as PR description. Otherwise (or if `machete.github.forceDescriptionFromCommitMessage` is set), PR description is taken from message body of the first unique commit of the branch. @@ -656,6 +656,11 @@ Set the PR title explicitly (the default is to use the first included commit's message as the title). + <b>-U</b>, <b>--update-related-descriptions</b> + + Update the generated sections in related PR descriptions. + See help for `update-pr-descriptions --related` below for extra considerations. + <b>--yes</b> Do not ask for confirmation whether to push the branch. @@ -674,6 +679,13 @@ The drafting/undrafting is useful in case the GitHub repository has set up CODEOWNERS. Draft PRs don't get code owners automatically added as reviewers. + <b>Options:</b> + + <b>-U</b>, <b>--update-related-descriptions</b> + + Update the generated sections in related PR descriptions. + See help for `update-pr-descriptions --related` below for extra considerations. + `retarget-pr [-b|--branch=<branch>] [--ignore-if-missing]`: Sets the base of the current (or specified) branch's PR to upstream (parent) branch, as seen by git machete (see `git machete show up`). @@ -693,6 +705,11 @@ Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch. + <b>-U</b>, <b>--update-related-descriptions</b> + + Update the generated sections in related PR descriptions. + See help for `update-pr-descriptions --related` below for extra considerations. + `sync`: <b>Deprecated.</b> Use `github checkout-prs --mine`, `delete-unmanaged` and `slide-out --removed-from-remote`. @@ -706,6 +723,17 @@ * deletes untracked managed branches that have no downstream branch. + `update-pr-descriptions`: + + Updates the generated sections ("intro") of PR descriptions that lists the upstream and/or downstream PRs + (depending on `machete.github.prDescriptionIntroStyle` git config key). + + <b>Options:</b>—all Update PR descriptions for all PRs in the repository. + <b>--mine</b> + Update PR descriptions for all PRs opened by the current user associated with the GitHub token.—related Update PR descriptions for all PRs that are upstream and/or downstream of the PR for the current branch. + If `machete.github.prDescriptionIntroStyle` is `up-only` (default), then only downstream PR descriptions are updated. + If `machete.github.prDescriptionIntroStyle` is `full`, then both downstream and upstream PR descriptions are updated. + <b>Git config keys:</b> `machete.github.{domain,remote,organization,repository}` (all subcommands): diff --git a/git_machete/options.py b/git_machete/options.py index 68a09963a..d66b03225 100644 --- a/git_machete/options.py +++ b/git_machete/options.py @@ -29,6 +29,7 @@ def __init__(self) -> None: self.opt_override_to_parent: bool = False self.opt_push_tracked: bool = True self.opt_push_untracked: bool = True + self.opt_related: bool = False self.opt_removed_from_remote: bool = False self.opt_return_to: str = "stay" self.opt_roots: List[LocalBranchShortName] = list()