From ca148afa86686993e717ddb3eed4e69432f62a14 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) --- RELEASE_NOTES.md | 4 +- completion/git-machete.completion.bash | 57 ++++++----- completion/git-machete.completion.zsh | 18 ++++ completion/git-machete.fish | 68 ++++++------ docs/man/git-machete.1 | 70 ++++++++++++- docs/source/cli/github.rst | 40 ++++++-- docs/source/cli/gitlab.rst | 15 +++ git_machete/__init__.py | 2 +- git_machete/cli.py | 95 +++++++++-------- git_machete/client.py | 108 ++++++++++++++------ git_machete/code_hosting.py | 3 + git_machete/generated_docs.py | 43 +++++++- tests/completion_e2e/test_completion_e2e.py | 8 +- 13 files changed, 388 insertions(+), 143 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cd503c99a..00ed21894 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,8 @@ # Release notes -## New in git-machete 3.30.1 +## New in git-machete 3.31.0 + +- added: `git machete git{hub,lab} update-{pr,mr}-descriptions` subcommands ## New in git-machete 3.30.0 diff --git a/completion/git-machete.completion.bash b/completion/git-machete.completion.bash index cde9a3507..f75fd7c6b 100644 --- a/completion/git-machete.completion.bash +++ b/completion/git-machete.completion.bash @@ -6,8 +6,8 @@ _git_machete() { local categories="addable childless managed slidable slidable-after unmanaged with-overridden-fork-point" local directions="down first last next prev root up" - local github_subcommands="anno-prs checkout-prs create-pr restack-pr retarget-pr" - local gitlab_subcommands="anno-mrs checkout-mrs create-mr restack-mr retarget-mr" + local github_subcommands="anno-prs checkout-prs create-pr restack-pr retarget-pr update-pr-descriptions" + local gitlab_subcommands="anno-mrs checkout-mrs create-mr restack-mr retarget-mr update-mr-descriptions" local locations="current $directions" local opt_color_args="always auto never" local opt_return_to_args="here nearest-remaining stay" @@ -22,14 +22,11 @@ _git_machete() { local diff_opts="-s --stat" local discover_opts="-C --checked-out-since= -l --list-commits -r --roots= -y --yes" local fork_point_opts="--inferred --override-to= --override-to-inferred --override-to-parent --unset-override" - local github_anno_prs_opts="--with-urls" - local github_create_pr_opts="--draft --title= --yes" - local github_checkout_prs_opts="--all --by= --mine" - local github_retarget_pr_opts="-b --branch= --ignore-if-missing" - local gitlab_anno_mrs_opts="--with-urls" - local gitlab_create_mr_opts="--draft --title= --yes" - local gitlab_checkout_mrs_opts="--all --by= --mine" - local gitlab_retarget_mr_opts="-b --branch= --ignore-if-missing" + local githublab_anno_opts="--with-urls" + local githublab_create_opts="--draft --title= --yes" + local githublab_checkout_opts="--all --by= --mine" + local githublab_retarget_opts="-b --branch= --ignore-if-missing" + local githublab_update_descriptions_opts="--all --mine --related" local reapply_opts="-f --fork-point=" 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=" @@ -57,25 +54,29 @@ _git_machete() { fork-point) __gitcomp "$common_opts $fork_point_opts" ;; github) if [[ ${COMP_WORDS[3]} == "anno-prs" ]]; then - __gitcomp "$common_opts $github_anno_prs_opts" + __gitcomp "$common_opts $githublab_anno_opts" elif [[ ${COMP_WORDS[3]} == "create-pr" ]]; then - __gitcomp "$common_opts $github_create_pr_opts" + __gitcomp "$common_opts $githublab_create_opts" elif [[ ${COMP_WORDS[3]} == "checkout-prs" ]]; then - __gitcomp "$common_opts $github_checkout_prs_opts" + __gitcomp "$common_opts $githublab_checkout_opts" elif [[ ${COMP_WORDS[3]} == "retarget-pr" ]]; then - __gitcomp "$common_opts $github_retarget_pr_opts" + __gitcomp "$common_opts $githublab_retarget_opts" + elif [[ ${COMP_WORDS[3]} == "update-pr-descriptions" ]]; then + __gitcomp "$common_opts $githublab_update_descriptions_opts" else __gitcomp "$common_opts" fi ;; gitlab) if [[ ${COMP_WORDS[3]} == "anno-mrs" ]]; then - __gitcomp "$common_opts $gitlab_anno_mrs_opts" + __gitcomp "$common_opts $githublab_anno_opts" elif [[ ${COMP_WORDS[3]} == "create-mr" ]]; then - __gitcomp "$common_opts $gitlab_create_mr_opts" + __gitcomp "$common_opts $githublab_create_opts" elif [[ ${COMP_WORDS[3]} == "checkout-mrs" ]]; then - __gitcomp "$common_opts $gitlab_checkout_mrs_opts" + __gitcomp "$common_opts $githublab_checkout_opts" elif [[ ${COMP_WORDS[3]} == "retarget-mr" ]]; then - __gitcomp "$common_opts $gitlab_retarget_mr_opts" + __gitcomp "$common_opts $githublab_retarget_opts" + elif [[ ${COMP_WORDS[3]} == "update-mr-descriptions" ]]; then + __gitcomp "$common_opts $githublab_update_descriptions_opts" else __gitcomp "$common_opts" fi ;; @@ -133,13 +134,15 @@ _git_machete() { if [[ $COMP_CWORD -eq 3 ]]; then __gitcomp "$github_subcommands" elif [[ ${COMP_WORDS[3]} == "anno-prs" ]]; then - __gitcomp "$common_opts $github_anno_prs_opts" + __gitcomp "$common_opts $githublab_anno_opts" elif [[ ${COMP_WORDS[3]} == "create-pr" ]]; then - __gitcomp "$common_opts $github_create_pr_opts" + __gitcomp "$common_opts $githublab_create_opts" elif [[ ${COMP_WORDS[3]} == "checkout-prs" ]]; then - __gitcomp "$common_opts $github_checkout_prs_opts" + __gitcomp "$common_opts $githublab_checkout_opts" elif [[ ${COMP_WORDS[3]} == "retarget-pr" ]]; then - __gitcomp "$common_opts $github_retarget_pr_opts" + __gitcomp "$common_opts $githublab_retarget_opts" + elif [[ ${COMP_WORDS[3]} == "update-pr-descriptions" ]]; then + __gitcomp "$common_opts $githublab_update_descriptions_opts" else COMPREPLY=('') fi ;; @@ -147,13 +150,15 @@ _git_machete() { if [[ $COMP_CWORD -eq 3 ]]; then __gitcomp "$gitlab_subcommands" elif [[ ${COMP_WORDS[3]} == "anno-mrs" ]]; then - __gitcomp "$common_opts $gitlab_anno_mrs_opts" + __gitcomp "$common_opts $githublab_anno_opts" elif [[ ${COMP_WORDS[3]} == "create-mr" ]]; then - __gitcomp "$common_opts $gitlab_create_mr_opts" + __gitcomp "$common_opts $githublab_create_opts" elif [[ ${COMP_WORDS[3]} == "checkout-mrs" ]]; then - __gitcomp "$common_opts $gitlab_checkout_mrs_opts" + __gitcomp "$common_opts $githublab_checkout_opts" elif [[ ${COMP_WORDS[3]} == "retarget-mr" ]]; then - __gitcomp "$common_opts $gitlab_retarget_mr_opts" + __gitcomp "$common_opts $githublab_retarget_opts" + elif [[ ${COMP_WORDS[3]} == "update-mr-descriptions" ]]; then + __gitcomp "$common_opts $githublab_update_descriptions_opts" else COMPREPLY=('') fi ;; diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh index 9d960b3db..d2cb4d217 100644 --- a/completion/git-machete.completion.zsh +++ b/completion/git-machete.completion.zsh @@ -254,6 +254,7 @@ __git_machete_github_subcommands() { 'create-pr:create a PR for the current branch, using the upstream (parent) branch as the PR base' 'restack-pr:(force-)push and retarget the PR, without adding code owners as reviewers in the process' 'retarget-pr:set the base of the current branch PR to upstream (parent) branch' + 'update-pr-descriptions:update the generated sections of PR descriptions that lists the upstream and/or downstream PRs' ) _describe 'subcommand' github_subcommands ;; @@ -289,6 +290,14 @@ __git_machete_github_subcommands() { '(--ignore-if-missing)'--ignore-if-missing'[Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch]' \ "${common_flags[@]}" ;; + + (update-pr-descriptions) + _arguments \ + '(--all)'--all'[Update PR descriptions for all PRs in the repository]' \ + '(--mine)'--mine='[Update PR descriptions for all PRs opened by the current user associated with the GitHub token]' \ + '(--related)'--related='[Update PR descriptions for all PRs that are upstream and/or downstream of the PR for the current branch]' \ + "${common_flags[@]}" + ;; esac ;; esac @@ -312,6 +321,7 @@ __git_machete_gitlab_subcommands() { 'create-mr:create a MR for the current branch, using the upstream (parent) branch as the MR source branch' 'restack-mr:(force-)push and retarget the MR, without adding code owners as reviewers in the process' 'retarget-mr:set the source branch of the current branch MR to upstream (parent) branch' + 'update-mr-descriptions:update the generated sections of MR descriptions that list the upstream and/or downstream MRs' ) _describe 'subcommand' gitlab_subcommands ;; @@ -347,6 +357,14 @@ __git_machete_gitlab_subcommands() { '(--ignore-if-missing)'--ignore-if-missing'[Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch]' \ "${common_flags[@]}" ;; + + (update-mr-descriptions) + _arguments \ + '(--all)'--all'[Update MR descriptions for all MRs in the project]' \ + '(--mine)'--mine='[Update MR descriptions for all MRs opened by the current user associated with the GitLab token]' \ + '(--related)'--related='[Update MR descriptions for all MRs that are upstream and/or downstream of the MR for the current branch]' \ + "${common_flags[@]}" + ;; esac ;; esac diff --git a/completion/git-machete.fish b/completion/git-machete.fish index a5688778f..d853df4ce 100644 --- a/completion/git-machete.fish +++ b/completion/git-machete.fish @@ -93,38 +93,46 @@ complete -c git-machete -n "__fish_seen_subcommand_from fork-point; and not __fi complete -c git-machete -n "__fish_seen_subcommand_from fork-point; and not __fish_seen_subcommand_from --inferred --override-to --override-to-inferred --override-to-parent" -x -l unset-override -a '(__machete_with_overridden_fork_point_branches)' # git machete github -complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a github -d 'Create, check out and manage GitHub PRs while keeping them reflected in git machete' -complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync" -f -a anno-prs -d 'Annotate the branches based on their corresponding GitHub PR numbers and authors' -complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync" -x -a checkout-prs -d 'Check out the head branch of the given pull requests (specified by number), also traverse chain of pull requests upwards, adding branches one by one to git-machete and check them out locally' -complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync" -f -a create-pr -d 'Create a PR for the current branch, using the upstream (parent) branch as the PR base' -complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync" -f -a restack-pr -d '(Force-)pushes and retargets the PR, without adding code owners as reviewers in the process' -complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync" -f -a retarget-pr -d 'Sets the base of PR for the current branch to upstream (parent) branch, as seen by git machete (see git machete show up)' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from anno-prs; and not __fish_seen_subcommand_from --with-urls" -f -l with-urls -d 'Include PR URLs in the annotations' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --all" -f -l all -d 'Checkout all open PRs' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --by" -x -l by -d "Checkout someone's open PRs" -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Checkout open PRs for the current user associated with the GitHub token' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --draft" -f -l draft -d 'Create the new PR as a draft' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --title" -x -l title -d 'Set the title for new PR explicitly' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --yes" -f -l yes -d 'Do not ask for confirmation whether to push the branch' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from retarget-pr; and not __fish_seen_subcommand_from --branch" -x -l branch -s b -a '(__machete_managed_branches)' -d 'Specify the branch for which the associated PR base will be set to its upstream (parent) branch' -complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from retarget-pr; and not __fish_seen_subcommand_from --ignore-if-missing" -f -l ignore-if-missing -d 'Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch' +complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a github -d 'Create, check out and manage GitHub PRs while keeping them reflected in git machete' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -f -a anno-prs -d 'Annotate the branches based on their corresponding GitHub PR numbers and authors' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -x -a checkout-prs -d 'Check out the head branch of the given pull requests (specified by number), also traverse chain of pull requests upwards, adding branches one by one to git-machete and check them out locally' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -f -a create-pr -d 'Create a PR for the current branch, using the upstream (parent) branch as the PR base' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -f -a restack-pr -d '(Force-)push and retarget the PR, without adding code owners as reviewers in the process' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -f -a retarget-pr -d 'Set the base of PR for the current branch to upstream (parent) branch, as seen by git machete (see git machete show up)' +complete -c git-machete -n "__fish_seen_subcommand_from github; and not __fish_seen_subcommand_from anno-prs checkout-prs create-pr restack-pr retarget-pr sync update-pr-descriptions" -f -a update-pr-descriptions -d 'Update the generated sections of PR descriptions that lists the upstream and/or downstream PRs' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from anno-prs; and not __fish_seen_subcommand_from --with-urls" -f -l with-urls -d 'Include PR URLs in the annotations' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --all" -f -l all -d 'Checkout all open PRs' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --by" -x -l by -d "Checkout someone's open PRs" +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from checkout-prs; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Checkout open PRs for the current user associated with the GitHub token' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --draft" -f -l draft -d 'Create the new PR as a draft' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --title" -x -l title -d 'Set the title for new PR explicitly' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from create-pr; and not __fish_seen_subcommand_from --yes" -f -l yes -d 'Do not ask for confirmation whether to push the branch' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from retarget-pr; and not __fish_seen_subcommand_from --branch" -x -l branch -s b -a '(__machete_managed_branches)' -d 'Specify the branch for which the associated PR base will be set to its upstream (parent) branch' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from retarget-pr; and not __fish_seen_subcommand_from --ignore-if-missing" -f -l ignore-if-missing -d 'Ignore errors and quietly terminate execution if there is no PR opened for current (or specified) branch' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from update-pr-descriptions; and not __fish_seen_subcommand_from --all" -f -l all -d 'Update PR descriptions for all PRs in the repository' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from update-pr-descriptions; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Update PR descriptions for all PRs opened by the current user associated with the GitHub token' +complete -c git-machete -n "__fish_seen_subcommand_from github; and __fish_seen_subcommand_from update-pr-descriptions; and not __fish_seen_subcommand_from --related" -x -l related -d 'Update PR descriptions for all PRs that are upstream and/or downstream of the PR for the current branch' # git machete gitlab -complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a gitlab -d 'Create, check out and manage GitLab MRs while keeping them reflected in git machete' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync" -f -a anno-mrs -d 'Annotate the branches based on their corresponding GitLab MR numbers and authors' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync" -x -a checkout-mrs -d 'Check out the head branch of the given merge requests (specified by number), also traverse chain of merge requests upwards, adding branches one by one to git-machete and check them out locally' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync" -f -a create-mr -d 'Create a MR for the current branch, using the upstream (parent) branch as the MR source branch' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync" -f -a restack-mr -d '(Force-)pushes and retargets the MR, without adding code owners as reviewers in the process' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync" -f -a retarget-mr -d 'Sets the base of MR for the current branch to upstream (parent) branch, as seen by git machete (see git machete show up)' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from anno-mrs; and not __fish_seen_subcommand_from --with-urls" -f -l with-urls -d 'Include MR URLs in the annotations' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --all" -f -l all -d 'Checkout all open MRs' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --by" -x -l by -d "Checkout someone's open MRs" -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Checkout open MRs for the current user associated with the GitLab token' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --draft" -f -l draft -d 'Create the new MR as a draft' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --title" -x -l title -d 'Set the title for new MR explicitly' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --yes" -f -l yes -d 'Do not ask for confirmation whether to push the branch' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from retarget-mr; and not __fish_seen_subcommand_from --branch" -x -l branch -s b -a '(__machete_managed_branches)' -d 'Specify the branch for which the associated MR source branch will be set to its upstream (parent) branch' -complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from retarget-mr; and not __fish_seen_subcommand_from --ignore-if-missing" -f -l ignore-if-missing -d 'Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch' +complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a gitlab -d 'Create, check out and manage GitLab MRs while keeping them reflected in git machete' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -f -a anno-mrs -d 'Annotate the branches based on their corresponding GitLab MR numbers and authors' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -x -a checkout-mrs -d 'Check out the head branch of the given merge requests (specified by number), also traverse chain of merge requests upwards, adding branches one by one to git-machete and check them out locally' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -f -a create-mr -d 'Create a MR for the current branch, using the upstream (parent) branch as the MR source branch' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -f -a restack-mr -d '(Force-)push and retarget the MR, without adding code owners as reviewers in the process' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -f -a retarget-mr -d 'Set the base of MR for the current branch to upstream (parent) branch, as seen by git machete (see git machete show up)' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and not __fish_seen_subcommand_from anno-mrs checkout-mrs create-mr restack-mr retarget-mr sync update-mr-descriptions" -f -a update-mr-descriptions -d 'Update the generated sections of MR descriptions that list the upstream and/or downstream MRs' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from anno-mrs; and not __fish_seen_subcommand_from --with-urls" -f -l with-urls -d 'Include MR URLs in the annotations' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --all" -f -l all -d 'Checkout all open MRs' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --by" -x -l by -d "Checkout someone's open MRs" +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from checkout-mrs; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Checkout open MRs for the current user associated with the GitLab token' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --draft" -f -l draft -d 'Create the new MR as a draft' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --title" -x -l title -d 'Set the title for new MR explicitly' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from create-mr; and not __fish_seen_subcommand_from --yes" -f -l yes -d 'Do not ask for confirmation whether to push the branch' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from retarget-mr; and not __fish_seen_subcommand_from --branch" -x -l branch -s b -a '(__machete_managed_branches)' -d 'Specify the branch for which the associated MR source branch will be set to its upstream (parent) branch' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from retarget-mr; and not __fish_seen_subcommand_from --ignore-if-missing" -f -l ignore-if-missing -d 'Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from update-mr-descriptions; and not __fish_seen_subcommand_from --all" -f -l all -d 'Update MR descriptions for all MRs in the project' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from update-mr-descriptions; and not __fish_seen_subcommand_from --mine" -x -l mine -d 'Update MR descriptions for all MRs opened by the current user associated with the GitHub token' +complete -c git-machete -n "__fish_seen_subcommand_from gitlab; and __fish_seen_subcommand_from update-mr-descriptions; and not __fish_seen_subcommand_from --related" -x -l related -d 'Update MR descriptions for all MRs that are upstream and/or downstream of the MR for the current branch' # git machete go complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a go -d 'Check out the branch relative to the position of the current branch, accepts down/first/last/next/root/prev/up argument' diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index e87a29666..62a45d7d3 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 15, 2024" "" "git-machete" +.TH "GIT-MACHETE" "1" "Oct 26, 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 (\(dqintros\(dq) of PR descriptions that list 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 @@ -1295,6 +1334,29 @@ Ignore errors and quietly terminate execution if there is no MR opened for curre .UNINDENT .UNINDENT .sp +\fBupdate\-mr\-descriptions\fP: +.INDENT 0.0 +.INDENT 3.5 +Updates the generated sections (\(dqintros\(dq) of MR descriptions that list the upstream and/or downstream MRs +(depending on \fBmachete.gitlab.mrDescriptionIntroStyle\fP git config key). +.sp +\fBOptions:\fP +\(em all Update MR descriptions for all MRs in the project. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.INDENT 3.5 +.INDENT 0.0 +.TP +.B \-\-mine +Update MR descriptions for all MRs opened by the current user associated with the GitLab token. +.UNINDENT +\(em related Update MR descriptions for all MRs that are upstream and/or downstream of the MR for the current branch. +If \fBmachete.gitlab.mrDescriptionIntroStyle\fP is \fBup\-only\fP (default), then only downstream MR descriptions are updated. +If \fBmachete.gitlab.mrDescriptionIntroStyle\fP is \fBfull\fP, then both downstream and upstream MR descriptions are updated. +.UNINDENT +.UNINDENT +.sp \fBGit config keys:\fP .INDENT 0.0 .TP diff --git a/docs/source/cli/github.rst b/docs/source/cli/github.rst index 5e0667be2..1ac191741 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 ("intros") of PR descriptions that list 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/docs/source/cli/gitlab.rst b/docs/source/cli/gitlab.rst index a5d8bf4de..f90e7d83b 100644 --- a/docs/source/cli/gitlab.rst +++ b/docs/source/cli/gitlab.rst @@ -126,6 +126,21 @@ Creates, checks out and manages GitLab MRs while keeping them reflected in branc --ignore-if-missing Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch. +``update-mr-descriptions``: + + Updates the generated sections ("intros") of MR descriptions that list the upstream and/or downstream MRs + (depending on ``machete.gitlab.mrDescriptionIntroStyle`` git config key). + + **Options:** + + --all Update MR descriptions for all MRs in the project. + + --mine Update MR descriptions for all MRs opened by the current user associated with the GitLab token. + + --related Update MR descriptions for all MRs that are upstream and/or downstream of the MR for the current branch. + If ``machete.gitlab.mrDescriptionIntroStyle`` is ``up-only`` (default), then only downstream MR descriptions are updated. + If ``machete.gitlab.mrDescriptionIntroStyle`` is ``full``, then both downstream and upstream MR descriptions are updated. + **Git config keys:** ``machete.gitlab.{domain,remote,namespace,project}`` (all subcommands): diff --git a/git_machete/__init__.py b/git_machete/__init__.py index 7861b42ff..7aeb2458c 100644 --- a/git_machete/__init__.py +++ b/git_machete/__init__.py @@ -1 +1 @@ -__version__ = '3.30.1' +__version__ = '3.31.0' diff --git a/git_machete/cli.py b/git_machete/cli.py index 940f36673..f14bf7ab8 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) @@ -461,9 +464,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": @@ -492,6 +492,9 @@ def update_cli_options_using_parsed_args( 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": @@ -728,41 +731,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, @@ -771,9 +776,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 @@ -782,6 +787,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 ad4755fe8..295075f96 100644 --- a/git_machete/client.py +++ b/git_machete/client.py @@ -2162,6 +2162,42 @@ 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: + domain = self.__derive_code_hosting_domain(spec) + org_repo_remote = self.__derive_org_repo_and_remote(spec, domain=domain) + code_hosting_client = spec.create_client(domain=domain, organization=org_repo_remote.organization, + repository=org_repo_remote.repository) + + current_user: Optional[str] = code_hosting_client.get_current_user_login() + if not current_user and mine: + msg = (f"Could not determine current user name, please check that the {spec.display_name} API token provided by one of the: " + f"{spec.token_providers_message}is valid.") + raise MacheteException(msg) + print(f'Checking for open {spec.display_name} {spec.pr_short_name}s... ', end='', flush=True) + all_open_prs: List[PullRequest] = code_hosting_client.get_open_pull_requests() + print(fmt('<green><b>OK</b></green>')) + + if related: + head = self.__git.get_current_branch() + current_pr = self.__get_sole_pull_request_for_head(code_hosting_client, head, ignore_if_missing=False) + else: + current_pr = None + applicable_prs: List[PullRequest] = self.__get_applicable_pull_requests( + pr_numbers=[], all_opened_prs=all_open_prs, code_hosting_client=code_hosting_client, + all=all, mine=mine, by=None, related_to=current_pr, user=current_user) + + for pr in applicable_prs: + new_description = self.__get_updated_pull_request_description(code_hosting_client, pr, all_open_prs_preloaded=all_open_prs) + if pr.description != new_description: + code_hosting_client.set_description_of_pull_request(pr.number, description=new_description) + print(f'Description of {pr.display_text()} has been updated') + def checkout_pull_requests(self, spec: CodeHostingSpec, pr_numbers: Optional[List[int]], @@ -2191,7 +2227,7 @@ def checkout_pull_requests(self, applicable_prs: List[PullRequest] = self.__get_applicable_pull_requests( pr_numbers, all_opened_prs=all_open_prs, code_hosting_client=code_hosting_client, - all=all, mine=mine, by=by, user=current_user) + all=all, mine=mine, by=by, related_to=None, user=current_user) debug(f'organization is {org_repo_remote.organization}, repository is {org_repo_remote.repository}') self.__git.fetch_remote(org_repo_remote.remote) @@ -2296,13 +2332,14 @@ def __get_upwards_path_including_pr(spec: CodeHostingSpec, original_pr: PullRequ pr_base = pr.base if pr else None return path - @staticmethod def __get_applicable_pull_requests( + self, pr_numbers: Optional[List[int]], all_opened_prs: List[PullRequest], code_hosting_client: CodeHostingClient, all: bool, mine: bool, + related_to: Optional[PullRequest], by: Optional[str], user: Optional[str] ) -> List[PullRequest]: @@ -2343,6 +2380,14 @@ def __get_applicable_pull_requests( f"{bold(code_hosting_client.organization)}/{bold(code_hosting_client.repository)}") return [] return result + elif related_to: + style = self.__get_pr_description_into_style_from_config(spec) + result = [] + if style == PRDescriptionIntroStyle.FULL: + result += reversed(self.__get_upwards_path_including_pr(spec, related_to, all_opened_prs)) + result += [pr_ for pr_, _ in self.__get_downwards_tree_excluding_pr(related_to, all_opened_prs)] + return result + raise UnexpectedMacheteException("All params passed to __get_applicable_pull_requests are empty.") def __get_url_for_remote(self) -> Dict[str, str]: @@ -2367,15 +2412,8 @@ def restack_pull_request(self, spec: CodeHostingSpec) -> None: code_hosting_client = spec.create_client( domain=domain, organization=org_repo_remote.organization, repository=org_repo_remote.repository) - prs: List[PullRequest] = code_hosting_client.get_open_pull_requests_by_head(head) - if not prs: - raise MacheteException(f"No {spec.pr_short_name}s in <b>{org_repo_remote.extract_org_and_repo()}</b> " - f"have <b>{head}</b> as its {spec.head_branch_name} branch") - if len(prs) > 1: - raise MacheteException(f"Multiple {spec.pr_short_name}s in <b>{org_repo_remote.extract_org_and_repo()}</b> " - f"have <b>{head}</b> as its {spec.head_branch_name} branch: " + - ", ".join(_pr.short_display_text() for _pr in prs)) - pr = prs[0] + pr: Optional[PullRequest] = self.__get_sole_pull_request_for_head(code_hosting_client, head, ignore_if_missing=False) + assert pr is not None self.__git.fetch_remote(org_repo_remote.remote) @@ -2436,7 +2474,7 @@ def restack_pull_request(self, spec: CodeHostingSpec) -> None: self.__print_new_line(False) if converted_to_draft: - code_hosting_client.set_draft_status_of_pull_request(prs[0].number, target_draft_status=False) + code_hosting_client.set_draft_status_of_pull_request(pr.number, target_draft_status=False) print(f'{pr.display_text()} has been marked as ready for review again') else: @@ -2479,23 +2517,9 @@ def retarget_pr(self, spec: CodeHostingSpec, head: LocalBranchShortName, ignore_ code_hosting_client = spec.create_client( domain=domain, organization=org_repo_remote.organization, repository=org_repo_remote.repository) - debug(f'organization is {org_repo_remote.organization}, repository is {org_repo_remote.repository}') - - prs: List[PullRequest] = code_hosting_client.get_open_pull_requests_by_head(head) - if not prs: - if ignore_if_missing: - warn(f"no {spec.pr_short_name}s in <b>{org_repo_remote.extract_org_and_repo()}</b> " - f"have <b>{head}</b> as its {spec.head_branch_name} branch") - return - else: - raise MacheteException(f"No {spec.pr_short_name}s in <b>{org_repo_remote.extract_org_and_repo()}</b> " - f"have <b>{head}</b> as its {spec.head_branch_name} branch") - if len(prs) > 1: - raise MacheteException(f"Multiple {spec.pr_short_name}s in <b>{org_repo_remote.extract_org_and_repo()}</b> " - f"have <b>{head}</b> as its {spec.head_branch_name} branch: " + - ", ".join(_pr.short_display_text() for _pr in prs)) - pr = prs[0] - debug(f'found {pr}') + pr: Optional[PullRequest] = self.__get_sole_pull_request_for_head(code_hosting_client, head, ignore_if_missing=ignore_if_missing) + if pr is None: + return new_base: Optional[LocalBranchShortName] = self.__up_branch.get(LocalBranchShortName.of(head)) if not new_base: @@ -2620,6 +2644,30 @@ def __derive_org_repo_and_remote( START_GIT_MACHETE_GENERATED_COMMENT = '<!-- start git-machete generated -->' END_GIT_MACHETE_GENERATED_COMMENT = '<!-- end git-machete generated -->' + @staticmethod + def __get_sole_pull_request_for_head( + code_hosting_client: CodeHostingClient, + head: LocalBranchShortName, + ignore_if_missing: bool + ) -> Optional[PullRequest]: + prs: List[PullRequest] = code_hosting_client.get_open_pull_requests_by_head(head) + spec = code_hosting_client._spec + if not prs: + if ignore_if_missing: + warn(f"no {spec.pr_short_name}s in <b>{code_hosting_client.get_org_and_repo()}</b> " + f"have <b>{head}</b> as its {spec.head_branch_name} branch") + return None + else: + raise MacheteException(f"No {spec.pr_short_name}s in <b>{code_hosting_client.get_org_and_repo()}</b> " + f"have <b>{head}</b> as its {spec.head_branch_name} branch") + if len(prs) > 1: + raise MacheteException(f"Multiple {spec.pr_short_name}s in <b>{code_hosting_client.get_org_and_repo()}</b> " + f"have <b>{head}</b> as its {spec.head_branch_name} branch: " + + ", ".join(_pr.short_display_text() for _pr in prs)) + pr = prs[0] + debug(f'found {pr}') + return pr + def __get_pr_description_into_style_from_config(self, spec: CodeHostingSpec) -> PRDescriptionIntroStyle: config_key = spec.git_config_keys.pr_description_intro_style return PRDescriptionIntroStyle.from_string( @@ -2663,7 +2711,7 @@ def __generate_pr_description_intro(self, code_hosting_client: CodeHostingClient pr_down_tree = [] prepend = f'{self.START_GIT_MACHETE_GENERATED_COMMENT}\n\n' - # In FULL mode, we're likely to generate the intro even when there are NO upstream PRs above + # In FULL mode, we're likely to generate a non-empty intro even when there are NO upstream PRs above if len(prs_for_base_branch) >= 1: prepend += f'# Based on {prs_for_base_branch[0].display_text(fmt=False)}\n\n' diff --git a/git_machete/code_hosting.py b/git_machete/code_hosting.py index 6c138a3b8..7d2e6adc3 100644 --- a/git_machete/code_hosting.py +++ b/git_machete/code_hosting.py @@ -139,6 +139,9 @@ def __create_ssl_context() -> ssl.SSLContext: ctx.verify_mode = ssl.CERT_NONE return ctx + def get_org_and_repo(self) -> OrganizationAndRepository: + return OrganizationAndRepository(self.organization, self.repository) + @abstractmethod def create_pull_request(self, head: str, head_org_repo: OrganizationAndRepository, base: str, title: str, description: str, draft: bool) -> PullRequest: diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py index fb4ff9dac..1b38bf7f7 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 ("intros") of PR descriptions that list 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): @@ -886,6 +914,17 @@ Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch. + `update-mr-descriptions`: + + Updates the generated sections ("intros") of MR descriptions that list the upstream and/or downstream MRs + (depending on `machete.gitlab.mrDescriptionIntroStyle` git config key). + + <b>Options:</b>—all Update MR descriptions for all MRs in the project. + <b>--mine</b> + Update MR descriptions for all MRs opened by the current user associated with the GitLab token.—related Update MR descriptions for all MRs that are upstream and/or downstream of the MR for the current branch. + If `machete.gitlab.mrDescriptionIntroStyle` is `up-only` (default), then only downstream MR descriptions are updated. + If `machete.gitlab.mrDescriptionIntroStyle` is `full`, then both downstream and upstream MR descriptions are updated. + <b>Git config keys:</b> `machete.gitlab.{domain,remote,namespace,project}` (all subcommands): diff --git a/tests/completion_e2e/test_completion_e2e.py b/tests/completion_e2e/test_completion_e2e.py index 5f94fc5d6..753f8d21d 100644 --- a/tests/completion_e2e/test_completion_e2e.py +++ b/tests/completion_e2e/test_completion_e2e.py @@ -59,7 +59,7 @@ "git machete fork-point --unset-override ": "develop", "git machete github ": - "anno-prs checkout-prs create-pr restack-pr retarget-pr", + "anno-prs checkout-prs create-pr restack-pr retarget-pr update-pr-descriptions", "git machete github anno-prs -": "--debug -h --help -v --verbose --with-urls", "git machete github checkout-prs -": @@ -70,8 +70,10 @@ "--branch --debug --help --ignore-if-missing --verbose", "git machete github retarget-pr -b ": "develop master", + "git machete github update-pr-descriptions -": + "--all --debug -h --help --mine --related -v --verbose", "git machete gitlab ": - "anno-mrs checkout-mrs create-mr restack-mr retarget-mr", + "anno-mrs checkout-mrs create-mr restack-mr retarget-mr update-mr-descriptions", "git machete gitlab anno-mrs -": "--debug -h --help -v --verbose --with-urls", "git machete gitlab checkout-mrs -": @@ -82,6 +84,8 @@ "--branch --debug --help --ignore-if-missing --verbose", "git machete gitlab retarget-mr -b ": "develop master", + "git machete gitlab update-mr-descriptions -": + "--all --debug -h --help --mine --related -v --verbose", "git machete g ": "down first last next prev root up", "git machete go ":