Repository: VirtusLab/git-machete
Pull request #1343 (2024-10-08 14:37:46)

Update multiple PR/MR descriptions in bulk (`update-{pr,mr}-descriptions` subcommand)
<!-- start git-machete generated -->

# Based on PR #1340

## Chain of upstream PRs as of 2024-10-08

* PR #1340:
  `develop` ← `feature/1214-traverse-sync-prs`

  * **PR #1343 (THIS ONE)**:
    `feature/1214-traverse-sync-prs` ← `feature/1325-update-all-pr-desc`

<!-- end git-machete generated -->


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) Report
Attention: Patch coverage is `62.19512%` with `31 lines` in your changes missing coverage. Please review.
> Project coverage is 97.50%. Comparing base [(`a6d490d`)](https://app.codecov.io/gh/VirtusLab/git-machete/commit/a6d490dd27562ce391a15098cdf8de1c7cbf04ed?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) to head [(`ca148af`)](https://app.codecov.io/gh/VirtusLab/git-machete/commit/ca148afa86686993e717ddb3eed4e69432f62a14?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).

| [Files with missing lines](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) | Patch % | Lines |
|---|---|---|
| [git\_machete/client.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?src=pr&el=tree&filepath=git_machete%2Fclient.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvY2xpZW50LnB5) | 43.75% | [27 Missing :warning: ](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) |
| [git\_machete/cli.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?src=pr&el=tree&filepath=git_machete%2Fcli.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvY2xpLnB5) | 87.87% | [4 Missing :warning: ](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) |

<details><summary>Additional details and impacted files</summary>


```diff
@@                      Coverage Diff                      @@
##           perf/preload-all-open-prs    #1343      +/-   ##
=============================================================
- Coverage                      98.32%   97.50%   -0.83%     
=============================================================
  Files                             14       14              
  Lines                           3890     3924      +34     
  Branches                         853      859       +6     
=============================================================
+ Hits                            3825     3826       +1     
- Misses                            41       72      +31     
- Partials                          24       26       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1343?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).


Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index cd503c9..00ed218 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 cde9a35..f75fd7c 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 9d960b3..122b702 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
       ;;
@@ -271,7 +272,7 @@ __git_machete_github_subcommands() {
           _arguments \
             '(--all)'--all'[Checkout all open PRs]' \
             '(--by)'--by='[Checkout open PRs authored by the given GitHub user]' \
-            '(--mine)'--mine='[Checkout open PRs for the current user associated with the GitHub token]' \
+            '(--mine)'--mine'[Checkout open PRs for the current user associated with the GitHub token]' \
             "${common_flags[@]}"
         ;;
 
@@ -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
       ;;
@@ -329,7 +339,7 @@ __git_machete_gitlab_subcommands() {
           _arguments \
             '(--all)'--all'[Checkout all open MRs]' \
             '(--by)'--by='[Checkout open MRs authored by the given GitLab user]' \
-            '(--mine)'--mine='[Checkout open MRs for the current user associated with the GitLab token]' \
+            '(--mine)'--mine'[Checkout open MRs for the current user associated with the GitLab token]' \
             "${common_flags[@]}"
         ;;
 
@@ -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 a568877..d853df4 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 e87a296..21bee4e 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 <subcommand>
 .UNINDENT
 .UNINDENT
 .sp
-where \fB<subcommand>\fP is one of: \fBanno\-prs\fP, \fBcheckout\-prs\fP, \fBcreate\-pr\fP, \fBretarget\-pr\fP or \fBrestack\-pr\fP\&.
+where \fB<subcommand>\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
@@ -1067,6 +1067,25 @@ deletes unmanaged branches,
 .IP 3. 3
 deletes untracked managed branches that have no downstream branch.
 .UNINDENT
+.TP
+.B \fBupdate\-pr\-descriptions\fP:
+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
+.INDENT 7.0
+.TP
+.B  \-\-all
+Update PR descriptions for all PRs in the repository.
+.TP
+.B  \-\-mine
+Update PR descriptions for all PRs opened by the current user associated with the GitHub token.
+.TP
+.B  \-\-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
@@ -1238,7 +1257,7 @@ Note that you need to use a global (not per\-project) milestone id. Look for som
 .sp
 If \fB\&.git/info/reviewers\fP file is present, its contents (one GitLab login per line) are used to set reviewers.
 .sp
-The subject of the first unique commit of the branch is used as MR title.
+Unless \fB\-\-title\fP is provided, the subject of the first unique commit of the branch is used as MR title.
 If \fB\&.git/info/description\fP or \fB\&.gitlab/merge_request_templates/Default.md\fP template is present, its contents are used as MR description.
 Otherwise (or if \fBmachete.gitlab.forceDescriptionFromCommitMessage\fP is set), MR description is taken from message body of the first unique commit of the branch.
 .sp
@@ -1293,6 +1312,25 @@ Specify the branch for which the associated MR source branch will be set to its
 .B  \-\-ignore\-if\-missing
 Ignore errors and quietly terminate execution if there is no MR opened for current (or specified) branch.
 .UNINDENT
+.TP
+.B \fBupdate\-mr\-descriptions\fP:
+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
+.INDENT 7.0
+.TP
+.B  \-\-all
+Update MR descriptions for all MRs in the project.
+.TP
+.B  \-\-mine
+Update MR descriptions for all MRs opened by the current user associated with the GitLab token.
+.TP
+.B  \-\-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
diff --git a/docs/source/cli/github.rst b/docs/source/cli/github.rst
index 5e0667b..bac036d 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,11 @@ 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.
+    --yes                              Do not ask for confirmation whether to push the branch.
 
 ``restack-pr``:
     Perform the following sequence of actions:
@@ -121,9 +121,9 @@ 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.
 
 ``sync``:
     **Deprecated.** Use ``github checkout-prs --mine``, ``delete-unmanaged`` and ``slide-out --removed-from-remote``.
@@ -135,6 +135,20 @@ 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 a5d8bf4..7d31d5f 100644
--- a/docs/source/cli/gitlab.rst
+++ b/docs/source/cli/gitlab.rst
@@ -84,7 +84,7 @@ Creates, checks out and manages GitLab MRs while keeping them reflected in branc
 
     If ``.git/info/reviewers`` file is present, its contents (one GitLab login per line) are used to set reviewers.
 
-    The subject of the first unique commit of the branch is used as MR title.
+    Unless ``--title`` is provided, the subject of the first unique commit of the branch is used as MR title.
     If ``.git/info/description`` or ``.gitlab/merge_request_templates/Default.md`` template is present, its contents are used as MR description.
     Otherwise (or if ``machete.gitlab.forceDescriptionFromCommitMessage`` is set), MR description is taken from message body of the first unique commit of the branch.
 
@@ -126,6 +126,20 @@ 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 7861b42..7aeb245 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.30.1'
+__version__ = '3.31.0'
diff --git a/git_machete/cli.py b/git_machete/cli.py
index 940f366..d448636 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 create_cli_parser() -> argparse.ArgumentParser:
             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 create_cli_parser() -> argparse.ArgumentParser:
         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":
@@ -727,61 +730,78 @@ def launch(orig_args: List[str]) -> None:
                 git.checkout(dest)
         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"
+            spec = GitHubClient.spec() if cmd == "github" else GitLabClient.spec()
+            pr_or_mr = spec.pr_short_name.lower()
 
             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.")
-            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":
-                machete_client.sync_annotations_to_prs(config, include_urls=cli_opts.opt_with_urls)
-            elif subcommand == f"checkout-{subcommand_suffix}s":
+            if "request_id" in parsed_cli and subcommand != f"checkout-{pr_or_mr}s":
+                raise MacheteException(f"{spec.pr_short_name} number is only valid with `checkout-{pr_or_mr}s` subcommand.")
+            for option in ("all", "mine"):
+                if option in parsed_cli and subcommand not in (f"checkout-{pr_or_mr}s", f"update-{pr_or_mr}-descriptions"):
+                    raise MacheteException(f"`--{option}` option is only valid with "
+                                           f"`checkout-{pr_or_mr}s` and `update-{pr_or_mr}-descriptions` subcommands.")
+            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 "by" in parsed_cli and subcommand != f"checkout-{pr_or_mr}s":
+                raise MacheteException(f"`--by` option is only valid with `checkout-{pr_or_mr}s` 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 "related" in parsed_cli and subcommand != f"update-{pr_or_mr}-descriptions":
+                raise MacheteException(f"`--related` option is only valid with `update-{pr_or_mr}-descriptions` 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(spec, include_urls=cli_opts.opt_with_urls)
+            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(
+                    spec,
+                    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,
+                    spec,
                     head=current_branch,
                     opt_draft=cli_opts.opt_draft,
                     opt_onto=cli_opts.opt_onto,
                     opt_title=cli_opts.opt_title,
                     opt_yes=cli_opts.opt_yes)
-            elif subcommand == f"restack-{subcommand_suffix}":
-                machete_client.restack_pull_request(config)
-            elif subcommand == f"retarget-{subcommand_suffix}":
+            elif subcommand == f"restack-{pr_or_mr}":
+                machete_client.restack_pull_request(spec)
+            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
-                machete_client.retarget_pr(config, head=branch, ignore_if_missing=ignore_if_missing)
+                machete_client.retarget_pr(spec, head=branch, ignore_if_missing=ignore_if_missing)
             elif subcommand == "sync":  # GitHub only
-                machete_client.checkout_pull_requests(config, pr_numbers=[], mine=True)
+                machete_client.checkout_pull_requests(spec, 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(
+                    spec,
+                    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 ad4755f..46dd1fa 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -28,8 +28,8 @@ from .git_operations import (HEAD, AnyBranchName, AnyRevision, BranchPair,
                              LocalBranchShortName, RemoteBranchShortName,
                              SyncToRemoteStatus)
 from .utils import (AnsiEscapeCodes, PopenResult, bold, colored, debug, dim,
-                    excluding, flat_map, fmt, get_pretty_choices, get_second,
-                    tupled, underline, warn)
+                    excluding, flat_map, fmt, get_pretty_choices,
+                    get_right_arrow, get_second, tupled, underline, warn)
 
 
 class SyncToParentStatus(Enum):
@@ -2162,6 +2162,42 @@ class MacheteClient:
                 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(fmt(f'Description of {pr.display_text()} (<b>{pr.head} {get_right_arrow()} {pr.base}</b>) has been updated'))
+
     def checkout_pull_requests(self,
                                spec: CodeHostingSpec,
                                pr_numbers: Optional[List[int]],
@@ -2191,7 +2227,7 @@ class MacheteClient:
 
         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 @@ class MacheteClient:
             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 @@ class MacheteClient:
                      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 @@ class MacheteClient:
         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 @@ class MacheteClient:
             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 @@ class MacheteClient:
         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 @@ class MacheteClient:
     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 @@ class MacheteClient:
             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 6c138a3..7d2e6ad 100644
--- a/git_machete/code_hosting.py
+++ b/git_machete/code_hosting.py
@@ -139,6 +139,9 @@ class CodeHostingClient(metaclass=ABCMeta):  # pragma: no cover
             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 fb4ff9d..798d697 100644
--- a/git_machete/generated_docs.py
+++ b/git_machete/generated_docs.py
@@ -564,7 +564,7 @@ long_docs: Dict[str, str] = {
         <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 @@ long_docs: Dict[str, str] = {
               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.
 
@@ -706,6 +706,24 @@ long_docs: Dict[str, str] = {
 
                  * 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>
+
+                 <b>--all</b>
+              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.
+
+                 <b>--related</b>
+              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):
  
@@ -831,7 +849,7 @@ long_docs: Dict[str, str] = {
 
               If `.git/info/reviewers` file is present, its contents (one GitLab login per line) are used to set reviewers.
 
-              The subject of the first unique commit of the branch is used as MR title.
+              Unless `--title` is provided, the subject of the first unique commit of the branch is used as MR title.
               If `.git/info/description` or `.gitlab/merge_request_templates/Default.md` template is present, its contents are used as MR description.
               Otherwise (or if `machete.gitlab.forceDescriptionFromCommitMessage` is set), MR description is taken from message body of the first unique commit of the branch.
 
@@ -886,6 +904,24 @@ long_docs: Dict[str, str] = {
 
               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>
+
+                 <b>--all</b>
+              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.
+
+                 <b>--related</b>
+              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/tox.ini b/tox.ini
index ae93af5..5adc8e0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,8 +19,9 @@ deps =
 allowlist_externals = mkdir
 commands =
   mkdir -p test-results/
-  # `python -m pytest` so that the local plugin can be loaded, see https://stackoverflow.com/a/48306599
-  python -m pytest -p tests.pytest_full_operands --numprocesses=auto --junitxml=test-results/testenv-{envname}.xml -m "not completion_e2e" {posargs}
+  # `python -m pytest` so that the local plugin for `--full-operands` can be loaded, see https://stackoverflow.com/a/48306599
+  python -m pytest -p tests.pytest_full_operands \
+    --numprocesses=auto --junitxml=test-results/testenv-{envname}.xml -m "not completion_e2e" {posargs}
 
 [testenv:test-completions]
 description = "Test shell completions"
@@ -87,8 +88,9 @@ deps =
 passenv = PYTHON_VERSION
 allowlist_externals = cp
 commands =
+  # `python -m pytest` so that the local plugin for `--full-operands` can be loaded, see https://stackoverflow.com/a/48306599
   # Generate (or append results to the existing) .coverage binary file (SQLite database)
-  pytest --cov=git_machete --cov-append --cov-branch \
+  python -m pytest --cov=git_machete --cov-append --cov-branch -p tests.pytest_full_operands \
     --numprocesses=auto --junitxml=test-results/testenv-{envname}.xml -m "not completion_e2e" {posargs}
   # Save a report to htmlcov/
   coverage html --show-contexts


Test patch:
diff --git a/tests/completion_e2e/test_completion_e2e.py b/tests/completion_e2e/test_completion_e2e.py
index 5f94fc5..753f8d2 100644
--- a/tests/completion_e2e/test_completion_e2e.py
+++ b/tests/completion_e2e/test_completion_e2e.py
@@ -59,7 +59,7 @@ test_cases: Dict[str, str] = {
     "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 @@ test_cases: Dict[str, str] = {
         "--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 @@ test_cases: Dict[str, str] = {
         "--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 ":
diff --git a/tests/mockers_github.py b/tests/mockers_github.py
index 8e95b05..24a3c7c 100644
--- a/tests/mockers_github.py
+++ b/tests/mockers_github.py
@@ -146,7 +146,7 @@ def __mock_urlopen_impl(github_api_state: MockGitHubAPIState, request: Request)
                 return MockAPIResponse(HTTPStatus.OK, pull)
             raise error_404()
         elif url_path_matches('/user'):
-            return MockAPIResponse(HTTPStatus.OK, {'login': 'github_user', 'type': 'User', 'company': 'VirtusLab'})
+            return MockAPIResponse(HTTPStatus.OK, {'login': 'github_user', 'type': 'User'})
         else:
             raise error_404()
 
diff --git a/tests/test_github.py b/tests/test_github.py
index b300cd8..946f31d 100644
--- a/tests/test_github.py
+++ b/tests/test_github.py
@@ -335,9 +335,9 @@ class TestGitHub(BaseTest):
         assert_failure(["github", "anno-prs", "--draft"],
                        "--draft option is only valid with create-pr subcommand.")
         assert_failure(["github", "anno-prs", "--mine"],
-                       "--mine option is only valid with checkout-prs subcommand.")
+                       "--mine option is only valid with checkout-prs and update-pr-descriptions subcommands.")
         assert_failure(["github", "retarget-pr", "123"],
-                       "request_id option is only valid with checkout-prs subcommand.")
+                       "PR number is only valid with checkout-prs subcommand.")
         assert_failure(["github", "checkout-prs", "-b", "foo"],
                        "--branch option is only valid with retarget-pr subcommand.")
         assert_failure(["github", "create-pr", "--ignore-if-missing"],
@@ -350,3 +350,9 @@ class TestGitHub(BaseTest):
                        "--with-urls option is only valid with anno-prs subcommand.")
         assert_failure(["github", "restack-pr", "--yes"],
                        "--yes option is only valid with create-pr subcommand.")
+        assert_failure(["github", "update-pr-descriptions", "--by=other-user"],
+                       "--by option is only valid with checkout-prs subcommand.")
+        assert_failure(["github", "checkout-prs", "--related"],
+                       "--related option is only valid with update-pr-descriptions subcommand.")
+        assert_failure(["github", "update-pr-descriptions", "--all", "--related"],
+                       "update-pr-descriptions subcommand must take exactly one of the following options: --all, --mine, --related")
diff --git a/tests/test_github_update_pr_descriptions.py b/tests/test_github_update_pr_descriptions.py
new file mode 100644
index 0000000..7df98bc
--- /dev/null
+++ b/tests/test_github_update_pr_descriptions.py
@@ -0,0 +1,169 @@
+from typing import Any, Dict, List
+
+from pytest_mock import MockerFixture
+
+from tests.base_test import BaseTest
+from tests.mockers import (assert_failure, assert_success,
+                           rewrite_branch_layout_file)
+from tests.mockers_code_hosting import mock_from_url
+from tests.mockers_github import (MockGitHubAPIState,
+                                  mock_github_token_for_domain_fake,
+                                  mock_github_token_for_domain_none,
+                                  mock_pr_json, mock_urlopen)
+
+
+class TestGitHubUpdatePRDescriptions(BaseTest):
+    @staticmethod
+    def prs_for_test_update_pr_descriptions() -> List[Dict[str, Any]]:
+        return [
+            mock_pr_json(head='chore/redundant_checks', base='restrict_access', number=18),
+            mock_pr_json(head='restrict_access', base='allow-ownership-link', number=17, user='github_user'),
+            mock_pr_json(head='allow-ownership-link', base='bugfix/feature', number=12),
+            mock_pr_json(head='bugfix/feature', base='enhance/feature', number=6),
+            mock_pr_json(head='enhance/add_user', base='develop', number=19),
+            mock_pr_json(head='testing/add_user', base='bugfix/add_user', number=22),
+            mock_pr_json(head='chore/comments', base='testing/add_user', number=24),
+            mock_pr_json(head='ignore-trailing', base='hotfix/add-trigger', number=3),
+            mock_pr_json(head='bugfix/remove-n-option', base='develop', number=5, state='closed', repo_id=2)
+        ]
+
+    def test_github_update_pr_descriptions(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)
+        self.patch_symbol(mocker, 'git_machete.github.GitHubToken.for_domain', mock_github_token_for_domain_fake)
+        github_api_state = MockGitHubAPIState.with_prs(*self.prs_for_test_update_pr_descriptions())
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(github_api_state))
+
+        (
+            self.repo_sandbox.new_branch("root")
+            .commit("initial commit")
+            .new_branch("develop")
+            .commit("first commit")
+            .push()
+            .new_branch("enhance/feature")
+            .commit("introduce feature")
+            .push()
+            .new_branch("bugfix/feature")
+            .commit("bugs removed")
+            .push()
+            .new_branch("allow-ownership-link")
+            .commit("fixes")
+            .push()
+            .new_branch('restrict_access')
+            .commit('authorized users only')
+            .push()
+            .new_branch("chore/redundant_checks")
+            .commit('remove some checks')
+            .push()
+            .check_out("root")
+            .new_branch("master")
+            .commit("Master commit")
+            .push()
+            .new_branch("hotfix/add-trigger")
+            .commit("HOTFIX Add the trigger")
+            .push()
+            .new_branch("ignore-trailing")
+            .commit("Ignore trailing data")
+            .push()
+            .delete_branch("root")
+            .new_branch('chore/fields')
+            .commit("remove outdated fields")
+            .push()
+            .check_out('develop')
+            .new_branch('enhance/add_user')
+            .commit('allow externals to add users')
+            .push()
+            .new_branch('bugfix/add_user')
+            .commit('first round of fixes')
+            .push()
+            .new_branch('testing/add_user')
+            .commit('add test set for add_user feature')
+            .push()
+            .new_branch('chore/comments')
+            .commit('code maintenance')
+            .push()
+            .check_out('allow-ownership-link')
+        )
+
+        body: str = \
+            """
+            develop
+              enhance/feature
+                bugfix/feature
+                  allow-ownership-link
+                    restrict_access
+                      chore/redundant_checks
+            bugfix/add_user
+              testing/add_user
+                chore/comments
+            """
+        rewrite_branch_layout_file(body)
+
+        assert_success(
+            ['github', 'update-pr-descriptions', '--mine'],
+            """
+            Checking for open GitHub PRs... OK
+            Description of PR #17 (restrict_access -> allow-ownership-link) has been updated
+            """
+        )
+
+        assert_success(
+            ['github', 'update-pr-descriptions', '--related'],
+            """
+            Checking for open GitHub PRs... OK
+            Description of PR #18 (chore/redundant_checks -> restrict_access) has been updated
+            """
+        )
+        self.repo_sandbox.set_git_config_key("machete.github.prDescriptionIntroStyle", "full")
+        assert_success(
+            ['github', 'update-pr-descriptions', '--related'],
+            """
+            Checking for open GitHub PRs... OK
+            Description of PR #6 (bugfix/feature -> enhance/feature) has been updated
+            Description of PR #12 (allow-ownership-link -> bugfix/feature) has been updated
+            Description of PR #17 (restrict_access -> allow-ownership-link) has been updated
+            """
+        )
+
+        assert_success(
+            ['github', 'update-pr-descriptions', '--all'],
+            """
+            Checking for open GitHub PRs... OK
+            Description of PR #19 (enhance/add_user -> develop) has been updated
+            Description of PR #22 (testing/add_user -> bugfix/add_user) has been updated
+            Description of PR #24 (chore/comments -> testing/add_user) has been updated
+            Description of PR #3 (ignore-trailing -> hotfix/add-trigger) has been updated
+            """
+        )
+
+    def test_github_update_pr_descriptions_misc_failures_and_warns(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(MockGitHubAPIState.with_prs()))
+
+        self.patch_symbol(mocker, 'git_machete.github.GitHubToken.for_domain', mock_github_token_for_domain_none)
+        assert_success(
+            ["github", "update-pr-descriptions", "--all"],
+            """
+            Checking for open GitHub PRs... OK
+            Warn: Currently there are no pull requests opened in repository example-org/example-repo
+            """
+        )
+
+        assert_failure(
+            ["github", "update-pr-descriptions", "--mine"],
+            """
+            Could not determine current user name, please check that the GitHub API token provided by one of the:
+            \t1. GITHUB_TOKEN environment variable
+            \t2. Content of the ~/.github-token file
+            \t3. Current auth token from the gh GitHub CLI
+            \t4. Current auth token from the hub GitHub CLI
+            is valid."""
+        )
+
+        self.patch_symbol(mocker, 'git_machete.github.GitHubToken.for_domain', mock_github_token_for_domain_fake)
+        assert_success(
+            ["github", "update-pr-descriptions", "--mine"],
+            """
+            Checking for open GitHub PRs... OK
+            Warn: Current user github_user has no open pull request in repository example-org/example-repo
+            """
+        )
diff --git a/tests/test_gitlab.py b/tests/test_gitlab.py
index e426576..3ac8e53 100644
--- a/tests/test_gitlab.py
+++ b/tests/test_gitlab.py
@@ -285,9 +285,9 @@ class TestGitLab(BaseTest):
         assert_failure(["gitlab", "anno-mrs", "--draft"],
                        "--draft option is only valid with create-mr subcommand.")
         assert_failure(["gitlab", "anno-mrs", "--mine"],
-                       "--mine option is only valid with checkout-mrs subcommand.")
+                       "--mine option is only valid with checkout-mrs and update-mr-descriptions subcommands.")
         assert_failure(["gitlab", "retarget-mr", "123"],
-                       "request_id option is only valid with checkout-mrs subcommand.")
+                       "MR number is only valid with checkout-mrs subcommand.")
         assert_failure(["gitlab", "checkout-mrs", "-b", "foo"],
                        "--branch option is only valid with retarget-mr subcommand.")
         assert_failure(["gitlab", "create-mr", "--ignore-if-missing"],
@@ -300,3 +300,9 @@ class TestGitLab(BaseTest):
                        "--with-urls option is only valid with anno-mrs subcommand.")
         assert_failure(["gitlab", "restack-mr", "--yes"],
                        "--yes option is only valid with create-mr subcommand.")
+        assert_failure(["gitlab", "update-mr-descriptions", "--by=other-user"],
+                       "--by option is only valid with checkout-mrs subcommand.")
+        assert_failure(["gitlab", "checkout-mrs", "--related"],
+                       "--related option is only valid with update-mr-descriptions subcommand.")
+        assert_failure(["gitlab", "update-mr-descriptions", "--all", "--related"],
+                       "update-mr-descriptions subcommand must take exactly one of the following options: --all, --mine, --related")
diff --git a/tests/test_gitlab_update_mr_descriptions.py b/tests/test_gitlab_update_mr_descriptions.py
new file mode 100644
index 0000000..988a1d0
--- /dev/null
+++ b/tests/test_gitlab_update_mr_descriptions.py
@@ -0,0 +1,168 @@
+from typing import Any, Dict, List
+
+from pytest_mock import MockerFixture
+
+from tests.base_test import BaseTest
+from tests.mockers import (assert_failure, assert_success,
+                           rewrite_branch_layout_file)
+from tests.mockers_code_hosting import mock_from_url
+from tests.mockers_gitlab import (MockGitLabAPIState,
+                                  mock_gitlab_token_for_domain_fake,
+                                  mock_gitlab_token_for_domain_none,
+                                  mock_mr_json, mock_urlopen)
+
+
+class TestGitLabUpdateMRDescriptions(BaseTest):
+    @staticmethod
+    def mrs_for_test_update_mr_descriptions() -> List[Dict[str, Any]]:
+        return [
+            mock_mr_json(head='chore/redundant_checks', base='restrict_access', number=18),
+            mock_mr_json(head='restrict_access', base='allow-ownership-link', number=17, user='gitlab_user'),
+            mock_mr_json(head='allow-ownership-link', base='bugfix/feature', number=12),
+            mock_mr_json(head='bugfix/feature', base='enhance/feature', number=6),
+            mock_mr_json(head='enhance/add_user', base='develop', number=19),
+            mock_mr_json(head='testing/add_user', base='bugfix/add_user', number=22),
+            mock_mr_json(head='chore/comments', base='testing/add_user', number=24),
+            mock_mr_json(head='ignore-trailing', base='hotfix/add-trigger', number=3),
+            mock_mr_json(head='bugfix/remove-n-option', base='develop', number=5, state='closed', repo_id=2)
+        ]
+
+    def test_gitlab_update_mr_descriptions(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)
+        self.patch_symbol(mocker, 'git_machete.gitlab.GitLabToken.for_domain', mock_gitlab_token_for_domain_fake)
+        gitlab_api_state = MockGitLabAPIState.with_mrs(*self.mrs_for_test_update_mr_descriptions())
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(gitlab_api_state))
+
+        (
+            self.repo_sandbox.new_branch("root")
+            .commit("initial commit")
+            .new_branch("develop")
+            .commit("first commit")
+            .push()
+            .new_branch("enhance/feature")
+            .commit("introduce feature")
+            .push()
+            .new_branch("bugfix/feature")
+            .commit("bugs removed")
+            .push()
+            .new_branch("allow-ownership-link")
+            .commit("fixes")
+            .push()
+            .new_branch('restrict_access')
+            .commit('authorized users only')
+            .push()
+            .new_branch("chore/redundant_checks")
+            .commit('remove some checks')
+            .push()
+            .check_out("root")
+            .new_branch("master")
+            .commit("Master commit")
+            .push()
+            .new_branch("hotfix/add-trigger")
+            .commit("HOTFIX Add the trigger")
+            .push()
+            .new_branch("ignore-trailing")
+            .commit("Ignore trailing data")
+            .push()
+            .delete_branch("root")
+            .new_branch('chore/fields')
+            .commit("remove outdated fields")
+            .push()
+            .check_out('develop')
+            .new_branch('enhance/add_user')
+            .commit('allow externals to add users')
+            .push()
+            .new_branch('bugfix/add_user')
+            .commit('first round of fixes')
+            .push()
+            .new_branch('testing/add_user')
+            .commit('add test set for add_user feature')
+            .push()
+            .new_branch('chore/comments')
+            .commit('code maintenance')
+            .push()
+            .check_out('allow-ownership-link')
+        )
+
+        body: str = \
+            """
+            develop
+              enhance/feature
+                bugfix/feature
+                  allow-ownership-link
+                    restrict_access
+                      chore/redundant_checks
+            bugfix/add_user
+              testing/add_user
+                chore/comments
+            """
+        rewrite_branch_layout_file(body)
+
+        assert_success(
+            ['gitlab', 'update-mr-descriptions', '--mine'],
+            """
+            Checking for open GitLab MRs... OK
+            Description of MR !17 (restrict_access -> allow-ownership-link) has been updated
+            """
+        )
+
+        assert_success(
+            ['gitlab', 'update-mr-descriptions', '--related'],
+            """
+            Checking for open GitLab MRs... OK
+            Description of MR !18 (chore/redundant_checks -> restrict_access) has been updated
+            """
+        )
+        self.repo_sandbox.set_git_config_key("machete.gitlab.mrDescriptionIntroStyle", "full")
+        assert_success(
+            ['gitlab', 'update-mr-descriptions', '--related'],
+            """
+            Checking for open GitLab MRs... OK
+            Description of MR !6 (bugfix/feature -> enhance/feature) has been updated
+            Description of MR !12 (allow-ownership-link -> bugfix/feature) has been updated
+            Description of MR !17 (restrict_access -> allow-ownership-link) has been updated
+            """
+        )
+
+        assert_success(
+            ['gitlab', 'update-mr-descriptions', '--all'],
+            """
+            Checking for open GitLab MRs... OK
+            Description of MR !19 (enhance/add_user -> develop) has been updated
+            Description of MR !22 (testing/add_user -> bugfix/add_user) has been updated
+            Description of MR !24 (chore/comments -> testing/add_user) has been updated
+            Description of MR !3 (ignore-trailing -> hotfix/add-trigger) has been updated
+            """
+        )
+
+    def test_gitlab_update_mr_descriptions_misc_failures_and_warns(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(MockGitLabAPIState.with_mrs()))
+
+        self.patch_symbol(mocker, 'git_machete.gitlab.GitLabToken.for_domain', mock_gitlab_token_for_domain_none)
+        assert_success(
+            ["gitlab", "update-mr-descriptions", "--all"],
+            """
+            Checking for open GitLab MRs... OK
+            Warn: Currently there are no merge requests opened in project example-org/example-repo
+            """
+        )
+
+        assert_failure(
+            ["gitlab", "update-mr-descriptions", "--mine"],
+            """
+            Could not determine current user name, please check that the GitLab API token provided by one of the:
+            \t1. GITLAB_TOKEN environment variable
+            \t2. Content of the ~/.gitlab-token file
+            \t3. Current auth token from the glab GitLab CLI
+            is valid."""
+        )
+
+        self.patch_symbol(mocker, 'git_machete.gitlab.GitLabToken.for_domain', mock_gitlab_token_for_domain_fake)
+        assert_success(
+            ["gitlab", "update-mr-descriptions", "--mine"],
+            """
+            Checking for open GitLab MRs... OK
+            Warn: Current user gitlab_user has no open merge request in project example-org/example-repo
+            """
+        )