Repository: VirtusLab/git-machete
Pull request #1299 (2024-08-07 16:43:16)

Release v3.26.4


Patch:
diff --git a/PACKAGES.md b/PACKAGES.md
index e301982..3f56259 100644
--- a/PACKAGES.md
+++ b/PACKAGES.md
@@ -45,7 +45,7 @@ the **edge-most** channel that corresponds to the **released** versions of git-m
 
 ## [Repology](https://repology.org/project/git-machete/versions)
 
-May duplicate badges already included in the above table,
+Duplicates some badges already included in the above table,
 but may also include newly-added packages that we're not aware of yet.
 
 [![Packaging status](https://repology.org/badge/vertical-allrepos/git-machete.svg)](https://repology.org/project/git-machete/versions)
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 4907009..6d49d25 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,11 @@
 # Release notes
 
+## New in git-machete 3.26.4
+
+- fixed: avoid detecting a cycle when there's a PR/MR from `main` or `master` in a fork to the original repo (reported by @Joibel)
+- fixed: `git machete git{hub,lab} create-{pr,mr}` no longer fails when creating a PR/MR across forks; instead, a forked PR is created (reported by @cspotcode)
+- improved: `git machete git{hub,lab} checkout-{prs,mrs}` only adds annotations to affected branches (and not to every branch)
+
 ## New in git-machete 3.26.3
 
 - improved: performance of listing commits for red-edge branches on large repos
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index 30be20f..c3a22ab 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" "Jul 05, 2024" "" "git-machete"
+.TH "GIT-MACHETE" "1" "Aug 07, 2024" "" "git-machete"
 .SH NAME
-git-machete \- git-machete 3.26.3
+git-machete \- git-machete 3.26.4
 .sp
 git machete is a robust tool that \fBsimplifies your git workflows\fP\&.
 .sp
@@ -133,11 +133,9 @@ Print version and exit.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete add [\-o|\-\-onto=<target\-upstream\-branch>] [\-R|\-\-as\-root] [\-y|\-\-yes] [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -182,11 +180,9 @@ Don\(aqt ask for confirmation whether to create the branch or whether to add ont
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete advance [\-y|\-\-yes]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -210,8 +206,7 @@ As an example, if \fBgit machete status \-\-color=never \-\-list\-commits\fP is
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 master
 |
 m\-develop *
@@ -221,8 +216,7 @@ m\-develop *
     |
     | Add support and sample for machete\-post\-slide\-out hook
     o\-feature/post\-slide\-out\-hook
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -232,8 +226,7 @@ After \fBadvance\fP completes, \fBstatus\fP will show:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 master
 |
 | Enable adding remote branch in the manner similar to git checkout
@@ -241,8 +234,7 @@ o\-develop *
   |
   | Add support and sample for machete\-post\-slide\-out hook
   o\-feature/post\-slide\-out\-hook
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -265,13 +257,11 @@ Fails if the current branch has more than one green\-edge downstream branch.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete anno [\-b|\-\-branch=<branch>] [<annotation text>]
 git machete anno \-H|\-\-sync\-github\-prs
 git machete anno \-L|\-\-sync\-gitlab\-mrs
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -282,11 +272,9 @@ If invoked with a single empty string <annotation text>, like:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 $ git machete anno \(aq\(aq
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -436,22 +424,20 @@ whether \fBtraverse\fP suggests to slide out the branch.
 .TP
 .B \fBmachete.status.extraSpaceBeforeBranchName\fP:
 To make it easier to select branch name from the \fBstatus\fP output on certain terminals
-(like \fI\%Alacritty\fP), you can add an extra space between └─ and \fBbranch name\fP
+(like \X'tty: link https://github.com/alacritty/alacritty'\fI\%Alacritty\fP\X'tty: link'), you can add an extra space between └─ and \fBbranch name\fP
 by setting \fBgit config machete.status.extraSpaceBeforeBranchName true\fP\&.
 .sp
 For example, by default the status is displayed as:
 .INDENT 7.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
 │
 ├─feature_branch1
 │
 └─feature_branch2
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -459,15 +445,13 @@ With \fBmachete.status.extraSpaceBeforeBranchName\fP config set to \fBtrue\fP:
 .INDENT 7.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
 │
 ├─ feature_branch1
 │
 └─ feature_branch2
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .TP
@@ -508,11 +492,9 @@ Used to store GitLab API token. Used by commands such as \fBanno \-\-sync\-gitla
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete clean [\-c|\-\-checkout\-my\-github\-prs] [\-y|\-\-yes]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -563,11 +545,9 @@ GitHub API token.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete completion <shell>
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -583,12 +563,10 @@ Put the following into \fB~/.bashrc\fP or \fB~/.bash_profile\fP:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 eval \(dq$(git machete completion bash)\(dq  # or, if it doesn\(aqt work:
 source <(git machete completion bash)
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -598,11 +576,9 @@ Put the following into \fB~/.config/fish/config.fish\fP:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete completion fish | source
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -612,12 +588,10 @@ Put the following into \fB~/.zshrc\fP:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 eval \(dq$(git machete completion zsh)\(dq  # or, if it doesn\(aqt work:
 source <(git machete completion zsh)
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .SH DELETE-UNMANAGED
@@ -626,11 +600,9 @@ source <(git machete completion zsh)
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete delete\-unmanaged [\-y|\-\-yes]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -654,11 +626,9 @@ Don\(aqt ask for confirmation.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete d[iff] [\-s|\-\-stat] [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -680,11 +650,9 @@ Makes \fBgit machete diff\fP pass \fB\-\-stat\fP option to \fBgit diff\fP, so th
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete discover [\-C|\-\-checked\-out\-since=<date>] [\-l|\-\-list\-commits] [\-r|\-\-roots=<branch1>,<branch2>,...] [\-y|\-\-yes]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -722,11 +690,9 @@ Mostly useful in scripts; not recommended for manual use.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete e[dit]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -772,11 +738,9 @@ Name of the editor executable.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete file
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -806,13 +770,11 @@ if \fBgit machete\fP is executed from a \fBsubmodule\fP, this file is located in
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete fork\-point [\-\-inferred] [<branch>]
 git machete fork\-point \-\-override\-to=<revision>|\-\-override\-to\-inferred|\-\-override\-to\-parent [<branch>]
 git machete fork\-point \-\-unset\-override [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -874,8 +836,7 @@ The format of the branch layout file should be as follows:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
     adjust\-reads\-prec PR #234 rebase=no push=no
         block\-cancel\-order PR #235 rebase=no
@@ -886,8 +847,7 @@ develop
     grep\-errors\-script
 master
     hotfix/receipt\-trigger PR #236
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -907,15 +867,13 @@ It\(aqs only important to use indentation characters consistently between all li
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete github <subcommand>
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
-where \fB<subcommand>\fP is one of: \fBanno\-prs\fP, \fBcheckout\-prs\fP, \fBcreate\-pr\fP, \fBretarget\-pr\fP\&.
+where \fB<subcommand>\fP is one of: \fBanno\-prs\fP, \fBcheckout\-prs\fP, \fBcreate\-pr\fP, \fBretarget\-pr\fP or \fBrestack\-pr\fP\&.
 .sp
 Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file.
 .sp
@@ -923,7 +881,7 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc
 .INDENT 0.0
 .INDENT 3.5
 To allow GitHub API access for private repositories (and also to perform side\-effecting actions like opening a PR,
-even in case of public repositories), a GitHub API token with \fBrepo\fP scope is required, see \fI\%https://github.com/settings/tokens\fP\&.
+even in case of public repositories), a GitHub API token with \fBrepo\fP scope is required, see \X'tty: link https://github.com/settings/tokens'\fI\%https://github.com/settings/tokens\fP\X'tty: link'\&.
 This will be resolved from the first of:
 .INDENT 0.0
 .IP 1. 3
@@ -946,13 +904,11 @@ GitHub Enterprise domains are supported.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 ghp_mytoken_for_github_com
 ghp_myothertoken_for_git_example_org git.example.org
 ghp_yetanothertoken_for_git_example_com git.example.com
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .UNINDENT
@@ -1049,7 +1005,7 @@ The branch is (force\-)pushed into remote.
 If the PR has been converted to draft in step 1, it\(aqs reverted to ready for review state.
 .UNINDENT
 .sp
-The drafting/undrafting is useful in case the GitHub repository has set up \fI\%CODEOWNERS\fP\&.
+The drafting/undrafting is useful in case the GitHub repository has set up \X'tty: link https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners'\fI\%CODEOWNERS\fP\X'tty: link'\&.
 Draft PRs don\(aqt get code owners automatically added as reviewers.
 .TP
 .B \fBretarget\-pr [\-b|\-\-branch=<branch>] [\-\-ignore\-if\-missing]\fP:
@@ -1135,15 +1091,13 @@ GitHub API token.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete gitlab <subcommand>
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
-where \fB<subcommand>\fP is one of: \fBanno\-mrs\fP, \fBcheckout\-mrs\fP, \fBcreate\-mr\fP, \fBretarget\-mr\fP\&.
+where \fB<subcommand>\fP is one of: \fBanno\-mrs\fP, \fBcheckout\-mrs\fP, \fBcreate\-mr\fP, \fBretarget\-mr\fP or \fBrestack\-mr\fP\&.
 .sp
 Creates, checks out and manages GitLab MRs while keeping them reflected in branch layout file.
 .sp
@@ -1151,7 +1105,7 @@ Creates, checks out and manages GitLab MRs while keeping them reflected in branc
 .INDENT 0.0
 .INDENT 3.5
 To allow GitLab API access for private repositories (and also to perform side\-effecting actions like opening a PR,
-even in case of public projects), a GitLab API token with \fBapi\fP scope is required, see \fI\%https://gitlab.com/\-/user_settings/personal_access_tokens\fP\&.
+even in case of public projects), a GitLab API token with \fBapi\fP scope is required, see \X'tty: link https://gitlab.com/-/user_settings/personal_access_tokens'\fI\%https://gitlab.com/\-/user_settings/personal_access_tokens\fP\X'tty: link'\&.
 This will be resolved from the first of:
 .INDENT 0.0
 .IP 1. 3
@@ -1172,13 +1126,11 @@ Self\-managed GitLab domains are supported.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 glpat\-mytoken_for_gitlab_com
 glpat\-myothertoken_for_git_example_org git.example.org
 glpat\-yetanothertoken_for_git_example_com git.example.com
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .UNINDENT
@@ -1277,7 +1229,7 @@ The branch is (force\-)pushed into remote.
 If the MR has been converted to draft in step 1, it\(aqs reverted to ready for review state.
 .UNINDENT
 .sp
-The drafting/undrafting is useful in case the GitLab project has set up \fI\%code owners\fP\&.
+The drafting/undrafting is useful in case the GitLab project has set up \X'tty: link https://docs.gitlab.com/ee/user/project/codeowners/'\fI\%code owners\fP\X'tty: link'\&.
 Draft MRs don\(aqt get code owners automatically added as reviewers.
 .TP
 .B \fBretarget\-mr [\-b|\-\-branch=<branch>] [\-\-ignore\-if\-missing]\fP:
@@ -1349,11 +1301,9 @@ GitLab API token.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete g[o] <direction>
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1388,11 +1338,9 @@ Roughly equivalent to \fBgit checkout $(git machete show <direction>)\fP\&.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete help [<command>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1475,7 +1423,7 @@ If \fBstatus\fP runs in ASCII\-only mode (if \fB\-\-color=auto\fP and stdout is
 then \fBASCII_ONLY=true\fP, otherwise \fBASCII_ONLY=false\fP\&.
 .UNINDENT
 .sp
-Please see \fI\%hook_samples\fP directory in git\-machete project for examples.
+Please see \X'tty: link https://github.com/VirtusLab/git-machete/tree/master/hook_samples'\fI\%hook_samples\fP\X'tty: link' directory in git\-machete project for examples.
 An example of using the standard git \fBpost\-commit hook\fP to \fBgit machete add\fP branches automatically is also included.
 .SH IS-MANAGED
 .sp
@@ -1483,11 +1431,9 @@ An example of using the standard git \fBpost\-commit hook\fP to \fBgit machete a
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete is\-managed [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1508,11 +1454,9 @@ the <branch> isn\(aqt provided and there\(aqs no current branch (detached HEAD).
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete list <category>
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1545,11 +1489,9 @@ This command is generally not meant for a day\-to\-day use, it\(aqs mostly neede
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete l[og] [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1563,11 +1505,9 @@ Note: the branch in question does not need to occur in the branch layout file.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete reapply [\-f|\-\-fork\-point=<fork\-point\-commit>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1601,11 +1541,9 @@ Example: \fBGIT_MACHETE_REBASE_OPTS=\(dq\-\-keep\-empty \-\-rebase\-merges\(dq g
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete show <direction> [<branch>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1641,14 +1579,12 @@ since all branches are usually meant to be ultimately merged to one of those.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete slide\-out \-\-removed\-from\-remote [\-\-delete]
 git machete slide\-out [\-d|\-\-down\-fork\-point=<down\-fork\-point\-commit>] [\-\-delete]
                       [\-M|\-\-merge] [\-n|\-\-no\-edit\-merge|\-\-no\-interactive\-rebase]
                       [<branch> [<branch> [<branch> ...]]]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1664,16 +1600,14 @@ For example, let\(aqs assume the following dependency tree:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
     adjust\-reads\-prec
         block\-cancel\-order
             change\-table
                 drop\-location\-type
             add\-notification
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1681,14 +1615,12 @@ After running \fBgit machete slide\-out adjust\-reads\-prec block\-cancel\-order
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
     change\-table
         drop\-location\-type
     add\-notification
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1769,11 +1701,9 @@ Example: \fBGIT_MACHETE_REBASE_OPTS=\(dq\-\-keep\-empty \-\-rebase\-merges\(dq g
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete squash [\-f|\-\-fork\-point=<fork\-point\-commit>]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1801,13 +1731,11 @@ Specifies the alternative fork point commit after which the squashed part of his
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete s[tatus] [\-\-color=WHEN]
                      [\-l|\-\-list\-commits] [\-L|\-\-list\-commits\-with\-hashes]
                      [\-\-squash\-merge\-detection=MODE]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1857,8 +1785,7 @@ When colors are disabled, relation between branches is represented in the follow
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 <branch0>
 |
 o\-<branch1> *   # green (in sync with parent; asterisk for the current branch)
@@ -1868,28 +1795,25 @@ o\-<branch1> *   # green (in sync with parent; asterisk for the current branch)
 |   ?\-<branch3> # yellow (in sync with parent, but parent is not the fork point)
 |
 m\-<branch4>     # grey (merged to parent)
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
 To make it easier to select branch name from the \fBstatus\fP output on certain terminals
-(like \fI\%Alacritty\fP), you can add an extra space between └─ and \fBbranch name\fP
+(like \X'tty: link https://github.com/alacritty/alacritty'\fI\%Alacritty\fP\X'tty: link'), you can add an extra space between └─ and \fBbranch name\fP
 by setting \fBgit config machete.status.extraSpaceBeforeBranchName true\fP\&.
 .sp
 For example, by default the status is displayed as:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
 │
 ├─feature_branch1
 │
 └─feature_branch2
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1897,15 +1821,13 @@ With \fBmachete.status.extraSpaceBeforeBranchName\fP config set to \fBtrue\fP:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 develop
 │
 ├─ feature_branch1
 │
 └─ feature_branch2
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -1959,7 +1881,7 @@ whether \fBtraverse\fP suggests to slide out the branch.
 .TP
 .B \fBmachete.status.extraSpaceBeforeBranchName\fP
 To make it easier to select branch name from the \fBstatus\fP output on certain terminals
-(like \fI\%Alacritty\fP), you can add an extra space between └─ and \fBbranch name\fP
+(like \X'tty: link https://github.com/alacritty/alacritty'\fI\%Alacritty\fP\X'tty: link'), you can add an extra space between └─ and \fBbranch name\fP
 by setting \fBgit config machete.status.extraSpaceBeforeBranchName true\fP\&.
 .UNINDENT
 .SH TRAVERSE
@@ -1968,14 +1890,12 @@ by setting \fBgit config machete.status.extraSpaceBeforeBranchName true\fP\&.
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete t[raverse] [\-F|\-\-fetch] [\-l|\-\-list\-commits] [\-M|\-\-merge]
                        [\-n|\-\-no\-edit\-merge|\-\-no\-interactive\-rebase] [\-\-[no\-]push] [\-\-[no\-]push\-untracked]
                        [\-\-return\-to=WHERE] [\-\-start\-from=WHERE] [\-\-squash\-merge\-detection=MODE]
                        [\-w|\-\-whole] [\-W] [\-y|\-\-yes]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -2044,15 +1964,13 @@ Example machete file with branch qualifiers:
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 master
   develop  rebase=no slide\-out=no
     my\-branch  PR #123
     someone\-elses\-branch  PR #124 rebase=no push=no
     branch\-for\-local\-experiments  push=no
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -2176,11 +2094,9 @@ Configuration key value can be overridden by the presence of the \fB\-\-push\fP
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete update [\-f|\-\-fork\-point=<fork\-point\-commit>] [\-M|\-\-merge] [\-n|\-\-no\-edit\-merge|\-\-no\-interactive\-rebase]
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
@@ -2229,11 +2145,9 @@ Example: \fBGIT_MACHETE_REBASE_OPTS=\(dq\-\-keep\-empty \-\-rebase\-merges\(dq g
 .INDENT 0.0
 .INDENT 3.5
 .sp
-.nf
-.ft C
+.EX
 git machete version
-.ft P
-.fi
+.EE
 .UNINDENT
 .UNINDENT
 .sp
diff --git a/docs/source/cli/github.rst b/docs/source/cli/github.rst
index 1276346..f765937 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``.
+where ``<subcommand>`` is one of: ``anno-prs``, ``checkout-prs``, ``create-pr``, ``retarget-pr`` or ``restack-pr``.
 
 Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file.
 
diff --git a/docs/source/cli/gitlab.rst b/docs/source/cli/gitlab.rst
index c6ef38c..8bb8df0 100644
--- a/docs/source/cli/gitlab.rst
+++ b/docs/source/cli/gitlab.rst
@@ -8,7 +8,7 @@ gitlab
 
     git machete gitlab <subcommand>
 
-where ``<subcommand>`` is one of: ``anno-mrs``, ``checkout-mrs``, ``create-mr``, ``retarget-mr``.
+where ``<subcommand>`` is one of: ``anno-mrs``, ``checkout-mrs``, ``create-mr``, ``retarget-mr`` or ``restack-mr``.
 
 Creates, checks out and manages GitLab MRs while keeping them reflected in branch layout file.
 
diff --git a/git_machete/__init__.py b/git_machete/__init__.py
index e8867b7..bcf02c5 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.26.3'
+__version__ = '3.26.4'
diff --git a/git_machete/client.py b/git_machete/client.py
index dc7dead..e5786af 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -528,7 +528,7 @@ class MacheteClient:
             self.expect_in_managed_branches(branch)
             anno = self.annotations.get(branch)
             if anno and not anno.qualifiers.slide_out:
-                raise MacheteException(f"Branch `{branch}` is annotated with `slide-out=no` qualifier, aborting.\n"
+                raise MacheteException(f"Branch {bold(branch)} is annotated with `slide-out=no` qualifier, aborting.\n"
                                        f"Remove the qualifier using `git machete anno` or edit branch layout file directly.")
             new_upstream = self.__up_branch.get(branch)
             if not new_upstream:
@@ -1200,7 +1200,7 @@ class MacheteClient:
     def rebase(self, onto: AnyRevision, from_exclusive: AnyRevision, branch: LocalBranchShortName, opt_no_interactive_rebase: bool) -> None:
         anno = self.annotations.get(branch)
         if anno and not anno.qualifiers.rebase:
-            raise MacheteException(f"Branch `{branch}` is annotated with `rebase=no` qualifier, aborting.\n"
+            raise MacheteException(f"Branch {bold(branch)} is annotated with `rebase=no` qualifier, aborting.\n"
                                    f"Remove the qualifier using `git machete anno` or edit branch layout file directly.")
         # Let's use `OPTS` suffix for consistency with git's built-in env var `GIT_DIFF_OPTS`
         extra_rebase_opts = os.environ.get('GIT_MACHETE_REBASE_OPTS', '').split()
@@ -2132,7 +2132,7 @@ class MacheteClient:
         debug(f'organization is {org_repo_remote.organization}, repository is {org_repo_remote.repository}')
         self.__git.fetch_remote(org_repo_remote.remote)
 
-        pr: Optional[PullRequest] = None
+        prs_to_annotate = set(applicable_prs)
         for pr in sorted(applicable_prs, key=lambda x: x.number):
             head_org_repo_and_git_url = code_hosting_client.get_org_repo_and_git_url_by_repo_id_or_none(pr.head_repo_id)
             if head_org_repo_and_git_url:
@@ -2164,18 +2164,19 @@ class MacheteClient:
                 warn(f'{pr.display_text()} is already closed.')
             debug(f'found {pr}')
 
-            path: List[PullRequest] = self.__get_path_from_pr_chain(spec, pr, all_open_prs)
-            reversed_path: List[PullRequest] = path[::-1]  # need to add from root downwards
-            if reversed_path[0].base not in self.managed_branches:
+            pr_path: List[PullRequest] = self.__get_path_from_pr_chain(spec, pr, all_open_prs)
+            prs_to_annotate.update(pr_path)
+            reversed_pr_path: List[PullRequest] = pr_path[::-1]  # need to add from root downwards
+            if reversed_pr_path[0].base not in self.managed_branches:
                 self.add(
-                    branch=LocalBranchShortName.of(reversed_path[0].base),
+                    branch=LocalBranchShortName.of(reversed_pr_path[0].base),
                     opt_as_first_child=False,
                     opt_as_root=True,
                     opt_onto=None,
                     opt_yes=True,
                     verbose=False,
                     switch_head_if_new_branch=False)
-            for pr_on_path in reversed_path:
+            for pr_on_path in reversed_pr_path:
                 if pr_on_path.head not in self.managed_branches:
                     self.add(
                         branch=LocalBranchShortName.of(pr_on_path.head),
@@ -2188,9 +2189,10 @@ class MacheteClient:
                     print(fmt(f"{pr_on_path.display_text()} checked out at local branch {bold(pr_on_path.head)}"))
 
         debug(f'Current {spec.display_name} user is ' + (current_user or '<none>'))
-        self.__sync_annotations_to_branch_layout_file(spec, all_open_prs, current_user=current_user, include_urls=False, verbose=False)
+        self.__sync_annotations_to_branch_layout_file(spec, list(prs_to_annotate),
+                                                      current_user=current_user, include_urls=False, verbose=False)
         if len(applicable_prs) == 1:
-            self.__git.checkout(LocalBranchShortName.of(pr.head))
+            self.__git.checkout(LocalBranchShortName.of(applicable_prs[0].head))
 
     @staticmethod
     def __get_path_from_pr_chain(spec: CodeHostingSpec, original_pr: PullRequest, all_open_prs: List[PullRequest]) -> List[PullRequest]:
@@ -2198,6 +2200,11 @@ class MacheteClient:
         path: List[PullRequest] = [original_pr]
         pr_base: Optional[str] = original_pr.base
         while pr_base:
+            # The chain needs to stop at main/master branches
+            # to avoid false-positive cycles when there's a PR from main in fork to main in the original repo.
+            # See https://github.com/VirtusLab/git-machete/issues/1276
+            if pr_base in ('main', 'master'):
+                return path
             if pr_base in visited_head_branches:
                 raise MacheteException(f"There is a cycle between {spec.display_name} {spec.pr_short_name}s: " +
                                        " -> ".join(visited_head_branches + [pr_base]))
@@ -2280,9 +2287,11 @@ class MacheteClient:
 
         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 have `{head}` as its {spec.head_branch_name} branch")
+            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 have `{head}` as its {spec.head_branch_name} branch: " +
+            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]
 
@@ -2391,12 +2400,15 @@ class MacheteClient:
         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 have `{head}` as its {spec.head_branch_name} branch")
+                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 have `{head}` as its {spec.head_branch_name} branch")
+                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 have `{head}` as its {spec.head_branch_name} branch: " +
+            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}')
@@ -2491,6 +2503,7 @@ class MacheteClient:
                 (remote, OrganizationAndRepository.from_url(domain, url)) for remote, url in url_for_remote.items()
             ) if oar
         }
+        debug(f"remote_and_organization_and_repository_from_urls = {remote_and_organization_and_repository_from_urls}")
 
         if not remote_and_organization_and_repository_from_urls:
             raise MacheteException(
@@ -2504,9 +2517,6 @@ class MacheteClient:
         if len(remote_and_organization_and_repository_from_urls) == 1:
             return remote_and_organization_and_repository_from_urls[list(remote_and_organization_and_repository_from_urls.keys())[0]]
 
-        if 'origin' in remote_and_organization_and_repository_from_urls:
-            return remote_and_organization_and_repository_from_urls['origin']
-
         if len(remote_and_organization_and_repository_from_urls) > 1 and branch_used_for_tracking_data is not None:
             remote_for_fetching_of_branch = self.__git.get_combined_remote_for_fetching_of_branch(
                 branch=branch_used_for_tracking_data,
@@ -2514,6 +2524,9 @@ class MacheteClient:
             if remote_for_fetching_of_branch is not None:
                 return remote_and_organization_and_repository_from_urls[remote_for_fetching_of_branch]
 
+        if 'origin' in remote_and_organization_and_repository_from_urls:
+            return remote_and_organization_and_repository_from_urls['origin']
+
         raise MacheteException(
             f'Multiple non-origin remotes correspond to {spec.display_name} in this repository: '
             f'{", ".join(remote_and_organization_and_repository_from_urls.keys())} -> aborting. \n'
@@ -2573,31 +2586,50 @@ class MacheteClient:
                                              f'Branch {bold(head)} is a root branch.')
 
         domain = self.__derive_code_hosting_domain(spec)
-        org_repo_remote = self.__derive_org_repo_and_remote(spec, domain=domain, branch_used_for_tracking_data=head)
-        code_hosting_client = spec.create_client(domain=domain, organization=org_repo_remote.organization,
-                                                 repository=org_repo_remote.repository)
-
-        remote_branch = RemoteBranchShortName(f"{org_repo_remote.remote}/{base}")
-        remote_base_branch_exists_locally = remote_branch in self.__git.get_remote_branches()
+        head_org_repo_remote = self.__derive_org_repo_and_remote(spec, domain=domain, branch_used_for_tracking_data=head)
+        base_org_repo_remote = self.__derive_org_repo_and_remote(spec, domain=domain, branch_used_for_tracking_data=base)
+        debug(f"head_org_repo_remote={head_org_repo_remote}, base_org_repo_remote={base_org_repo_remote}")
+
+        base_org_repo = base_org_repo_remote.extract_org_and_repo()
+        head_org_repo = head_org_repo_remote.extract_org_and_repo()
+        if base_org_repo != head_org_repo:
+            arrow = utils.get_right_arrow()
+            warn(f"{spec.base_branch_name.capitalize()} branch <b>{base}</b> lives in <b>{base_org_repo}</b> {spec.repository_name},\n"
+                 f"while {spec.head_branch_name} branch <b>{head}</b> lives in <b>{head_org_repo}</b> {spec.repository_name}.\n"
+                 f"git-machete will now attempt to create {spec.pr_short_name_article} {spec.pr_short_name} in <b>{base_org_repo}</b>.\n"
+                 "\n"
+                 f"Note that due to the limitations of {spec.display_name}'s {spec.pr_short_name} model, "
+                 f"it is <b>not</b> possible to cleanly create stacked {spec.pr_short_name}s from forks.\n"
+                 f"For example, in a hypothetical chain <b>some-other-branch</b> {arrow} <b>{head}</b> {arrow} <b>{base}</b>, "
+                 f"{spec.pr_short_name_article} {spec.pr_short_name} from <b>some-other-branch</b> to <b>{head}</b>\n"
+                 f"could <b>not</b> be created in <b>{base_org_repo}</b>, "
+                 f"since its {spec.head_branch_name} branch <b>{head}</b> lives in <b>{head_org_repo}</b>.\n"
+                 f"Generally, {spec.pr_short_name}s need to be created "
+                 f"in whatever {spec.repository_name} the {spec.base_branch_name} branch lives.\n")
+
+        base_remote_branch = RemoteBranchShortName(f"{base_org_repo_remote.remote}/{base}")
+        remote_base_branch_exists_locally = base_remote_branch in self.__git.get_remote_branches()
         print(f"Checking if {spec.base_branch_name} branch {bold(base)} "
-              f"exists in {bold(org_repo_remote.remote)} remote... ", end='', flush=True)
-        base_branch_found_on_remote = self.__git.does_remote_branch_exist(org_repo_remote.remote, base)
+              f"exists in {bold(base_org_repo_remote.remote)} remote... ", end='', flush=True)
+        base_branch_found_on_remote = self.__git.does_remote_branch_exist(base_org_repo_remote.remote, base)
         print(fmt('<green><b>YES</b></green>' if base_branch_found_on_remote else '<red><b>NO</b></red>'))
         if not base_branch_found_on_remote and remote_base_branch_exists_locally:
-            self.__git.delete_remote_branch(remote_branch)
+            self.__git.delete_remote_branch(base_remote_branch)
 
         if not base_branch_found_on_remote:
             self.__handle_untracked_branch(
                 branch=base,
-                new_remote=org_repo_remote.remote,
+                new_remote=base_org_repo_remote.remote,
                 is_called_from_traverse=False,
                 is_called_from_code_hosting=True,
                 opt_push_tracked=False,
                 opt_push_untracked=True,
                 opt_yes=opt_yes)
 
+        code_hosting_client = spec.create_client(domain=domain, organization=base_org_repo_remote.organization,
+                                                 repository=base_org_repo_remote.repository)
         current_user: Optional[str] = code_hosting_client.get_current_user_login()
-        debug(f'organization is {org_repo_remote.organization}, repository is {org_repo_remote.repository}')
+        debug(f'organization is {base_org_repo_remote.organization}, repository is {base_org_repo_remote.repository}')
         debug(f'current {spec.display_name} user is ' + (current_user or '<none>'))
 
         fork_point = self.fork_point(head, use_overrides=True)
@@ -2628,8 +2660,9 @@ class MacheteClient:
         ok_str = '<green><b>OK</b></green>'
         print(f'Creating a {"draft " if opt_draft else ""}{spec.pr_short_name} from {bold(head)} to {bold(base)}... ', end='', flush=True)
 
-        pr: PullRequest = code_hosting_client.create_pull_request(head=head, base=base, title=title,
-                                                                  description=description, draft=opt_draft)
+        pr: PullRequest = code_hosting_client.create_pull_request(
+            head=head, head_org_repo=head_org_repo, base=base,
+            title=title, description=description, draft=opt_draft)
         print(fmt(f'{ok_str}, see `{pr.html_url}`'))
 
         # If base branch has NOT originally been found on the remote,
diff --git a/git_machete/code_hosting.py b/git_machete/code_hosting.py
index e75f5e4..55a2fe4 100644
--- a/git_machete/code_hosting.py
+++ b/git_machete/code_hosting.py
@@ -33,6 +33,9 @@ class OrganizationAndRepository(NamedTuple):
     organization: str
     repository: str
 
+    def __str__(self) -> str:
+        return f"{self.organization}/{self.repository}"
+
     @classmethod
     def from_url(cls, domain: str, url: str) -> Optional["OrganizationAndRepository"]:
         url = url if url.endswith('.git') else url + '.git'
@@ -50,6 +53,9 @@ class OrganizationAndRepositoryAndRemote(NamedTuple):
     repository: str
     remote: str
 
+    def extract_org_and_repo(self) -> OrganizationAndRepository:
+        return OrganizationAndRepository(organization=self.organization, repository=self.repository)
+
 
 class OrganizationAndRepositoryAndGitUrl(NamedTuple):
     organization: str
@@ -98,6 +104,7 @@ class CodeHostingSpec(NamedTuple):
     pr_description_path: List[str]
     pr_full_name: str
     pr_ordinal_char: str
+    pr_short_name_article: str
     pr_short_name: str
     repository_name: str
     token_providers_message: str
@@ -121,7 +128,8 @@ class CodeHostingClient(metaclass=ABCMeta):  # pragma: no cover
         self.__org_repo_and_git_url_by_repo_id: Dict[int, Optional[OrganizationAndRepositoryAndGitUrl]] = {}
 
     @abstractmethod
-    def create_pull_request(self, head: str, base: str, title: str, description: str, draft: bool) -> PullRequest:
+    def create_pull_request(self, head: str, head_org_repo: OrganizationAndRepository,
+                            base: str, title: str, description: str, draft: bool) -> PullRequest:
         pass
 
     @abstractmethod
diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py
index 8a986bc..ea1a285 100644
--- a/git_machete/generated_docs.py
+++ b/git_machete/generated_docs.py
@@ -543,7 +543,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`.
+        where `<subcommand>` is one of: `anno-prs`, `checkout-prs`, `create-pr`, `retarget-pr` or `restack-pr`.
 
         Creates, checks out and manages GitHub PRs while keeping them reflected in branch layout file.
 
@@ -723,7 +723,7 @@ long_docs: Dict[str, str] = {
         <b>Usage:</b><b>
            git machete gitlab <subcommand></b>
 
-        where `<subcommand>` is one of: `anno-mrs`, `checkout-mrs`, `create-mr`, `retarget-mr`.
+        where `<subcommand>` is one of: `anno-mrs`, `checkout-mrs`, `create-mr`, `retarget-mr` or `restack-mr`.
 
         Creates, checks out and manages GitLab MRs while keeping them reflected in branch layout file.
 
diff --git a/git_machete/github.py b/git_machete/github.py
index aa1ccb3..35f6641 100644
--- a/git_machete/github.py
+++ b/git_machete/github.py
@@ -10,8 +10,8 @@ from pathlib import Path
 from typing import Any, Dict, List, NamedTuple, Optional, Tuple
 
 from .code_hosting import (CodeHostingClient, CodeHostingGitConfigKeys,
-                           CodeHostingSpec, OrganizationAndRepositoryAndGitUrl,
-                           PullRequest)
+                           CodeHostingSpec, OrganizationAndRepository,
+                           OrganizationAndRepositoryAndGitUrl, PullRequest)
 from .exceptions import MacheteException, UnexpectedMacheteException
 from .git_operations import LocalBranchShortName
 from .utils import bold, compact_dict, debug, popen_cmd, warn
@@ -164,6 +164,7 @@ class GitHubClient(CodeHostingClient):
             pr_ordinal_char='#',
             pr_full_name='pull request',
             pr_short_name='PR',
+            pr_short_name_article='a',
             repository_name='repository',
             token_providers_message=(
                 f'\n\t1. `{GITHUB_TOKEN_ENV_VAR}` environment variable\n'
@@ -326,9 +327,11 @@ class GitHubClient(CodeHostingClient):
         else:
             return str(response)
 
-    def create_pull_request(self, head: str, base: str, title: str, description: str, draft: bool) -> PullRequest:
+    def create_pull_request(self, head: str, head_org_repo: OrganizationAndRepository,
+                            base: str, title: str, description: str, draft: bool) -> PullRequest:
         request_body: Dict[str, Any] = {
             'head': head,
+            'head_repo': f'{head_org_repo.organization}/{head_org_repo.repository}',
             'base': base,
             'title': title,
             'body': description,
diff --git a/git_machete/gitlab.py b/git_machete/gitlab.py
index bc59d6f..f3100d9 100644
--- a/git_machete/gitlab.py
+++ b/git_machete/gitlab.py
@@ -12,6 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional
 from git_machete.code_hosting import (CodeHostingClient,
                                       CodeHostingGitConfigKeys,
                                       CodeHostingSpec,
+                                      OrganizationAndRepository,
                                       OrganizationAndRepositoryAndGitUrl,
                                       PullRequest)
 from git_machete.exceptions import MacheteException, UnexpectedMacheteException
@@ -119,6 +120,7 @@ class GitLabClient(CodeHostingClient):
             pr_full_name='merge request',
             pr_ordinal_char='!',
             pr_short_name='MR',
+            pr_short_name_article='an',
             repository_name='project',
             token_providers_message=(
                 f'\n\t1. `{GITLAB_TOKEN_ENV_VAR}` environment variable\n'
@@ -135,7 +137,8 @@ class GitLabClient(CodeHostingClient):
             )
         )
 
-    def __get_merge_request_from_json(self, mr_json: Dict[str, Any]) -> PullRequest:
+    @staticmethod
+    def __get_merge_request_from_json(mr_json: Dict[str, Any]) -> PullRequest:
         return PullRequest(
             number=int(mr_json['iid']),
             display_prefix='MR !',
@@ -225,8 +228,12 @@ class GitLabClient(CodeHostingClient):
         except OSError as e:  # pragma: no cover
             raise MacheteException(f'Could not connect to {url_prefix}: {e}')
 
+    @staticmethod
+    def __url_encode_project_name(organization: str, repository: str) -> str:
+        return urllib.parse.quote(f"{organization}/{repository}", safe='')  # `safe` empty, so that `/` is encoded as well
+
     def __fire_gitlab_api_project_request(self, method: str, path_suffix: str, request_body: Optional[Dict[str, Any]] = None) -> Any:
-        project = urllib.parse.quote(f"{self.organization}/{self.repository}", safe='')  # `safe` empty, so that `/` is encoded as well
+        project = self.__url_encode_project_name(self.organization, self.repository)
         path = f'/projects/{project}{path_suffix}'
         return self.__fire_gitlab_api_request(method=method, path=path, request_body=request_body)
 
@@ -240,14 +247,25 @@ class GitLabClient(CodeHostingClient):
         else:
             return str(response)
 
-    def create_pull_request(self, head: str, base: str, title: str, description: str, draft: bool) -> PullRequest:
+    def __get_repo_id_by_org_repo(self) -> int:
+        project = self.__url_encode_project_name(self.organization, self.repository)
+        return int(self.__fire_gitlab_api_request(method='GET', path=f'/projects/{project}')['id'])  # cast to int to satisfy mypy
+
+    def create_pull_request(self, head: str, head_org_repo: OrganizationAndRepository,
+                            base: str, title: str, description: str, draft: bool) -> PullRequest:
+        # The GitLab API for creating MRs across projects is somewhat confusing:
+        # URL path needs to point to the source project, but the MR is still created in the target project,
+        # which needs to pointed to by `target_project_id` JSON property.
         request_body: Dict[str, Any] = {
             'source_branch': head,
             'target_branch': base,
+            'target_project_id': self.__get_repo_id_by_org_repo(),
             'title': ('Draft: ' if draft else '') + title,
             'description': description,
         }
-        mr = self.__fire_gitlab_api_project_request(method='POST', path_suffix='/merge_requests', request_body=request_body)
+        head_project = self.__url_encode_project_name(head_org_repo.organization, head_org_repo.repository)
+        path = f'/projects/{head_project}/merge_requests'
+        mr = self.__fire_gitlab_api_request(method='POST', path=path, request_body=request_body)
         return self.__get_merge_request_from_json(mr)
 
     def __get_user_id_by_username(self, username: str) -> Optional[int]:
diff --git a/requirements/coverage.txt b/requirements/coverage.txt
index d7b9621..487a728 100644
--- a/requirements/coverage.txt
+++ b/requirements/coverage.txt
@@ -1,6 +1,6 @@
 coverage==6.2; python_version <= '3.6'
 coverage==7.2.7; python_version >= '3.7' and python_version < '3.8'
-coverage==7.6.0; python_version >= '3.8'
+coverage==7.6.1; python_version >= '3.8'
 
 pytest-cov==4.0.0; python_version <= '3.6'
 pytest-cov==4.1.0; python_version >= '3.7' and python_version < '3.8'
diff --git a/requirements/mypy.txt b/requirements/mypy.txt
index dfcfaaf..c592b49 100644
--- a/requirements/mypy.txt
+++ b/requirements/mypy.txt
@@ -1,3 +1,3 @@
 mypy==0.971; python_version <= '3.6'
 mypy==1.4.1; python_version >= '3.7' and python_version < '3.8'
-mypy==1.11.0; python_version >= '3.8'
+mypy==1.11.1; python_version >= '3.8'
diff --git a/requirements/pep8-check.txt b/requirements/pep8-check.txt
index ca57f58..b7ac419 100644
--- a/requirements/pep8-check.txt
+++ b/requirements/pep8-check.txt
@@ -1,3 +1,3 @@
-flake8==7.1.0
+flake8==7.1.1
 flake8-unused-arguments==0.0.13
 flake8-use-fstring==1.4
diff --git a/requirements/pypi-deploy.txt b/requirements/pypi-deploy.txt
index dce8fa9..c3dee8b 100644
--- a/requirements/pypi-deploy.txt
+++ b/requirements/pypi-deploy.txt
@@ -1,3 +1,3 @@
-setuptools==71.1.0
+setuptools==72.1.0
 twine==5.1.1
-wheel==0.43.0
+wheel==0.44.0
diff --git a/requirements/sphinx-docs.txt b/requirements/sphinx-docs.txt
index e607dd2..1f34b53 100644
--- a/requirements/sphinx-docs.txt
+++ b/requirements/sphinx-docs.txt
@@ -3,5 +3,5 @@
 # broke the build despite no explicit update to deps.
 pydata-sphinx-theme==0.15.4
 
-sphinx==6.2.1
+sphinx==8.0.2
 sphinx-book-theme==1.1.3
diff --git a/requirements/typos.txt b/requirements/typos.txt
index 35facb7..ff34e31 100644
--- a/requirements/typos.txt
+++ b/requirements/typos.txt
@@ -1,1 +1,1 @@
-typos==1.23.2
+typos==1.23.6


Test patch:
diff --git a/requirements/testenv.txt b/requirements/testenv.txt
index 626935c..22c9d5c 100644
--- a/requirements/testenv.txt
+++ b/requirements/testenv.txt
@@ -1,6 +1,6 @@
 pytest==7.0.1; python_version <= '3.6'
 pytest==7.4.4; python_version >= '3.7' and python_version < '3.8'
-pytest==8.3.1; python_version >= '3.8'
+pytest==8.3.2; python_version >= '3.8'
 
 pytest-mock==3.6.1; python_version <= '3.6'
 pytest-mock==3.11.1; python_version >= '3.7' and python_version < '3.8'
diff --git a/tests/mockers_code_hosting.py b/tests/mockers_code_hosting.py
index f08111c..7e169b5 100644
--- a/tests/mockers_code_hosting.py
+++ b/tests/mockers_code_hosting.py
@@ -1,4 +1,5 @@
 import json
+import os
 from collections import defaultdict
 from typing import Any, Callable, Dict, List, Optional, Union
 from urllib.error import HTTPError
@@ -34,7 +35,16 @@ class MockAPIResponse:
 
 
 def mock_from_url(domain: str, url: str) -> "OrganizationAndRepository":  # noqa: U100
-    return OrganizationAndRepository("example-org", "example-repo")
+    if url[:8] == "https://":
+        return OrganizationAndRepository(url.split("/")[3], url.split("/")[4].replace(".git", ""))
+    dir_to_repo = {
+        "remote": "example-repo",
+        "remote-1": "example-repo-1",
+        "remote-2": "example-repo-2",
+        "second-remote": "second-example-repo",
+    }
+    dir = url.split(os.path.sep)[-1]
+    return OrganizationAndRepository("example-org", dir_to_repo[dir])
 
 
 def mock_shutil_which(path: Optional[str]) -> Callable[[Any], Optional[str]]:
diff --git a/tests/mockers_gitlab.py b/tests/mockers_gitlab.py
index b50bbe0..ae4cf12 100644
--- a/tests/mockers_gitlab.py
+++ b/tests/mockers_gitlab.py
@@ -1,5 +1,6 @@
 import json
 import re
+import urllib
 from contextlib import AbstractContextManager, contextmanager
 from http import HTTPStatus
 from typing import Any, Callable, Dict, Iterator, List, Optional
@@ -55,10 +56,14 @@ class MockGitLabAPIState:
     @staticmethod
     def with_mrs(*mrs: Dict[str, Any]) -> "MockGitLabAPIState":
         projects = {
-            1: {'namespace': {'full_path': 'tester/tester'}, 'name': 'repo_sandbox',
+            1: {'id': 1, 'namespace': {'full_path': 'tester/tester/repo_sandbox'}, 'name': 'repo_sandbox',
                 'http_url_to_repo': 'https://gitlab.com/tester/tester/repo_sandbox.git'},
-            2: {'namespace': {'full_path': 'example-org'}, 'name': 'example-repo',
-                'http_url_to_repo': 'https://github.com/example-org/example-repo.git'},
+            2: {'id': 2, 'namespace': {'full_path': 'example-org/example-repo'}, 'name': 'example-repo',
+                'http_url_to_repo': 'https://gitlab.com/example-org/example-repo.git'},
+            3: {'id': 3, 'namespace': {'full_path': 'example-org/example-repo-1'}, 'name': 'example-repo-1',
+                'http_url_to_repo': 'https://gitlab.com/example-org/example-repo-1.git'},
+            4: {'id': 4, 'namespace': {'full_path': 'example-org/example-repo-2'}, 'name': 'example-repo-2',
+                'http_url_to_repo': 'https://gitlab.com/example-org/example-repo-2.git'},
         }
         return MockGitLabAPIState(projects, *mrs)
 
@@ -126,6 +131,12 @@ def __mock_urlopen_impl(gitlab_api_state: MockGitLabAPIState, request: Request)
             if repo_no in gitlab_api_state.projects:
                 return MockAPIResponse(HTTPStatus.OK, gitlab_api_state.projects[repo_no])
             raise error_404()
+        elif url_path_matches('/projects/*'):
+            repo_name = urllib.parse.unquote(url_segments[-1])
+            for project in gitlab_api_state.projects.values():
+                if project['namespace']['full_path'] == repo_name:
+                    return MockAPIResponse(HTTPStatus.OK, project)
+            raise error_404()
         elif url_path_matches('/projects/*/merge_requests'):
             head: Optional[str] = query_params.get('source_branch')
             if head:
diff --git a/tests/test_github_checkout_prs.py b/tests/test_github_checkout_prs.py
index 868c8ff..113063b 100644
--- a/tests/test_github_checkout_prs.py
+++ b/tests/test_github_checkout_prs.py
@@ -119,7 +119,7 @@ class TestGitHubCheckoutPRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  PR #3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -145,7 +145,7 @@ class TestGitHubCheckoutPRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  PR #3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -178,7 +178,7 @@ class TestGitHubCheckoutPRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  PR #3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -630,3 +630,36 @@ class TestGitHubCheckoutPRs(BaseTest):
             'Checking for open GitHub PRs... OK\n'
             'PR #18 checked out at local branch develop\n'
         )
+
+    @staticmethod
+    def prs_for_test_checkout_prs_main_to_main_pr() -> List[Dict[str, Any]]:
+        return [
+            mock_pr_json(head='fix-10341', base='main', number=2),
+            mock_pr_json(head='main', base='main', number=1, repo_id=2)
+        ]
+
+    def test_github_checkout_prs_main_to_main_pr(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.git_operations.GitContext.fetch_remote', lambda _self, _remote: 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_none)
+        second_remote_path = self.repo_sandbox.create_repo("second-remote", bare=True)
+        github_api_state = MockGitHubAPIState(
+            self.repositories_for_test_github_checkout_prs(second_remote_path),
+            *self.prs_for_test_checkout_prs_main_to_main_pr())
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(github_api_state))
+
+        (
+            self.repo_sandbox
+            .new_branch("main")
+            .commit()
+            .push()
+            .new_branch("fix-10341")
+            .commit()
+            .push()
+        )
+
+        assert_success(
+            ['github', 'checkout-prs', '2'],
+            'Checking for open GitHub PRs... OK\n'
+            'PR #2 checked out at local branch fix-10341\n'
+        )
diff --git a/tests/test_github_create_pr.py b/tests/test_github_create_pr.py
index 858c2c7..332e579 100644
--- a/tests/test_github_create_pr.py
+++ b/tests/test_github_create_pr.py
@@ -570,7 +570,16 @@ class TestGitHubCreatePR(BaseTest):
             |
             o-feature *
 
-        Checking if base branch branch-1 exists in origin_1 remote... YES
+        Warn: Base branch branch-1 lives in example-org/example-repo-2 repository,
+        while head branch feature lives in example-org/example-repo-1 repository.
+        git-machete will now attempt to create a PR in example-org/example-repo-2.
+
+        Note that due to the limitations of GitHub's PR model, it is not possible to cleanly create stacked PRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature -> branch-1, a PR from some-other-branch to feature
+        could not be created in example-org/example-repo-2, since its head branch feature lives in example-org/example-repo-1.
+        Generally, PRs need to be created in whatever repository the base branch lives.
+
+        Checking if base branch branch-1 exists in origin_2 remote... YES
         Creating a PR from feature to branch-1... OK, see www.github.com
         Checking for open GitHub PRs (to determine PR chain)... OK
         Updating description of PR #16 to include the chain of PRs... OK
@@ -623,7 +632,16 @@ class TestGitHubCreatePR(BaseTest):
         expected_result = """
         Add feature_1 onto the inferred upstream (parent) branch feature? (y, N)
         Added branch feature_1 onto feature
-        Checking if base branch feature exists in origin_2 remote... YES
+        Warn: Base branch feature lives in example-org/example-repo-1 repository,
+        while head branch feature_1 lives in example-org/example-repo-2 repository.
+        git-machete will now attempt to create a PR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitHub's PR model, it is not possible to cleanly create stacked PRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_1 -> feature, a PR from some-other-branch to feature_1
+        could not be created in example-org/example-repo-1, since its head branch feature_1 lives in example-org/example-repo-2.
+        Generally, PRs need to be created in whatever repository the base branch lives.
+
+        Checking if base branch feature exists in origin_1 remote... YES
         Creating a PR from feature_1 to feature... OK, see www.github.com
         Checking for open GitHub PRs (to determine PR chain)... OK
         Updating description of PR #17 to include the chain of PRs... OK
@@ -705,9 +723,19 @@ class TestGitHubCreatePR(BaseTest):
         expected_result = """
         Add feature_4 onto the inferred upstream (parent) branch feature_3? (y, N)
         Added branch feature_4 onto feature_3
-        Checking if base branch feature_3 exists in origin_2 remote... NO
-        Push untracked branch feature_3 to origin_2? (y, Q)
+        Warn: Base branch feature_3 lives in example-org/example-repo-1 repository,
+        while head branch feature_4 lives in example-org/example-repo-2 repository.
+        git-machete will now attempt to create a PR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitHub's PR model, it is not possible to cleanly create stacked PRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_4 -> feature_3, a PR from some-other-branch to feature_4
+        could not be created in example-org/example-repo-1, since its head branch feature_4 lives in example-org/example-repo-2.
+        Generally, PRs need to be created in whatever repository the base branch lives.
+
+        Checking if base branch feature_3 exists in origin_1 remote... YES
         Creating a PR from feature_4 to feature_3... OK, see www.github.com
+        Checking for open GitHub PRs (to determine PR chain)... OK
+        Updating description of PR #20 to include the chain of PRs... OK
         """
         assert_success(
             ['github', 'create-pr'],
@@ -727,9 +755,19 @@ class TestGitHubCreatePR(BaseTest):
         expected_result = """
         Add feature_5 onto the inferred upstream (parent) branch feature_3? (y, N)
         Added branch feature_5 onto feature_3
-        Checking if base branch feature_3 exists in origin remote... NO
-        Push untracked branch feature_3 to origin? (y, Q)
+        Warn: Base branch feature_3 lives in example-org/example-repo-1 repository,
+        while head branch feature_5 lives in example-org/example-repo-2 repository.
+        git-machete will now attempt to create a PR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitHub's PR model, it is not possible to cleanly create stacked PRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_5 -> feature_3, a PR from some-other-branch to feature_5
+        could not be created in example-org/example-repo-1, since its head branch feature_5 lives in example-org/example-repo-2.
+        Generally, PRs need to be created in whatever repository the base branch lives.
+
+        Checking if base branch feature_3 exists in origin_1 remote... YES
         Creating a PR from feature_5 to feature_3... OK, see www.github.com
+        Checking for open GitHub PRs (to determine PR chain)... OK
+        Updating description of PR #21 to include the chain of PRs... OK
         """
         assert_success(
             ['github', 'create-pr'],
diff --git a/tests/test_github_restack_pr.py b/tests/test_github_restack_pr.py
index 8f40f25..be0bc93 100644
--- a/tests/test_github_restack_pr.py
+++ b/tests/test_github_restack_pr.py
@@ -39,13 +39,13 @@ class TestGitHubRestackPR(BaseTest):
 
         assert_failure(
             ['github', 'restack-pr'],
-            "No PRs have develop as its head branch"
+            "No PRs in example-org/example-repo have develop as its head branch"
         )
 
         self.repo_sandbox.new_branch("multiple-pr-branch").commit()
         assert_failure(
             ['github', 'restack-pr'],
-            "Multiple PRs have multiple-pr-branch as its head branch: #16, #17"
+            "Multiple PRs in example-org/example-repo have multiple-pr-branch as its head branch: #16, #17"
         )
 
     def test_github_restack_pr_branch_in_sync(self, mocker: MockerFixture) -> None:
diff --git a/tests/test_github_retarget_pr.py b/tests/test_github_retarget_pr.py
index 099122a..5b1a1bd 100644
--- a/tests/test_github_retarget_pr.py
+++ b/tests/test_github_retarget_pr.py
@@ -102,7 +102,7 @@ class TestGitHubRetargetPR(BaseTest):
 
         assert_failure(
             ['github', 'retarget-pr'],
-            'Multiple PRs have feature_4 as its head branch: #35, #40'
+            'Multiple PRs in example-org/old-example-repo have feature_4 as its head branch: #35, #40'
         )
 
     @staticmethod
@@ -180,11 +180,11 @@ class TestGitHubRetargetPR(BaseTest):
 
         assert_failure(
             ["github", "retarget-pr", "--branch", "branch-without-pr"],
-            "No PRs have branch-without-pr as its head branch")
+            "No PRs in user/repo have branch-without-pr as its head branch")
 
         assert_success(
             ['github', 'retarget-pr', '--branch', 'branch-without-pr', '--ignore-if-missing'],
-            "Warn: no PRs have branch-without-pr as its head branch\n")
+            "Warn: no PRs in user/repo have branch-without-pr as its head branch\n")
 
     def test_github_retarget_pr_multiple_non_origin_remotes(self, mocker: MockerFixture) -> None:
         self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)
diff --git a/tests/test_gitlab_checkout_mrs.py b/tests/test_gitlab_checkout_mrs.py
index 89b296e..09480aa 100644
--- a/tests/test_gitlab_checkout_mrs.py
+++ b/tests/test_gitlab_checkout_mrs.py
@@ -121,7 +121,7 @@ class TestGitLabCheckoutMRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  MR !3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -147,7 +147,7 @@ class TestGitLabCheckoutMRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  MR !3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -180,7 +180,7 @@ class TestGitLabCheckoutMRs(BaseTest):
             |
             o-hotfix/add-trigger
               |
-              o-ignore-trailing  MR !3 (some_other_user) rebase=no push=no
+              o-ignore-trailing
                 |
                 o-chore/fields
 
@@ -580,17 +580,7 @@ class TestGitLabCheckoutMRs(BaseTest):
         )
         assert_failure(
             ["gitlab", "checkout-mrs", "--all"],
-            "Could not check out MR !18 because branch develop is already deleted from origin-1."
-        )
-
-        (
-            self.repo_sandbox
-            .remove_remote("origin-1")
-            .add_remote("tester", 'https://gitlab.com/tester/lolxd.git')
-        )
-        assert_failure(
-            ["gitlab", "checkout-mrs", "--all"],
-            "Could not check out MR !18 because branch develop is already deleted from tester/tester."
+            "Could not check out MR !18 because branch develop is already deleted from tester/tester/repo_sandbox."
         )
 
     def test_gitlab_checkout_mrs_org_and_repo_from_config(self, mocker: MockerFixture) -> None:
@@ -631,3 +621,36 @@ class TestGitLabCheckoutMRs(BaseTest):
             'Checking for open GitLab MRs... OK\n'
             'MR !18 checked out at local branch develop\n'
         )
+
+    @staticmethod
+    def mrs_for_test_checkout_mrs_main_to_main_pr() -> List[Dict[str, Any]]:
+        return [
+            mock_mr_json(head='fix-10341', base='main', number=2),
+            mock_mr_json(head='main', base='main', number=1, repo_id=2)
+        ]
+
+    def test_gitlab_checkout_mrs_main_to_main_mr(self, mocker: MockerFixture) -> None:
+        self.patch_symbol(mocker, 'git_machete.git_operations.GitContext.fetch_remote', lambda _self, _remote: 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_none)
+        second_remote_path = self.repo_sandbox.create_repo("second-remote", bare=True)
+        gitlab_api_state = MockGitLabAPIState(
+            self.projects_for_test_gitlab_checkout_prs(second_remote_path),
+            *self.mrs_for_test_checkout_mrs_main_to_main_pr())
+        self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(gitlab_api_state))
+
+        (
+            self.repo_sandbox
+            .new_branch("main")
+            .commit()
+            .push()
+            .new_branch("fix-10341")
+            .commit()
+            .push()
+        )
+
+        assert_success(
+            ['gitlab', 'checkout-mrs', '2'],
+            'Checking for open GitLab MRs... OK\n'
+            'MR !2 checked out at local branch fix-10341\n'
+        )
diff --git a/tests/test_gitlab_create_mr.py b/tests/test_gitlab_create_mr.py
index f46b38e..63e51f0 100644
--- a/tests/test_gitlab_create_mr.py
+++ b/tests/test_gitlab_create_mr.py
@@ -565,7 +565,16 @@ class TestGitLabCreateMR(BaseTest):
             |
             o-feature *
 
-        Checking if target branch branch-1 exists in origin_1 remote... YES
+        Warn: Target branch branch-1 lives in example-org/example-repo-2 project,
+        while source branch feature lives in example-org/example-repo-1 project.
+        git-machete will now attempt to create an MR in example-org/example-repo-2.
+
+        Note that due to the limitations of GitLab's MR model, it is not possible to cleanly create stacked MRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature -> branch-1, an MR from some-other-branch to feature
+        could not be created in example-org/example-repo-2, since its source branch feature lives in example-org/example-repo-1.
+        Generally, MRs need to be created in whatever project the target branch lives.
+
+        Checking if target branch branch-1 exists in origin_2 remote... YES
         Creating a MR from feature to branch-1... OK, see www.gitlab.com
         Checking for open GitLab MRs (to determine MR chain)... OK
         Updating description of MR !16 to include the chain of MRs... OK
@@ -618,7 +627,16 @@ class TestGitLabCreateMR(BaseTest):
         expected_result = """
         Add feature_1 onto the inferred upstream (parent) branch feature? (y, N)
         Added branch feature_1 onto feature
-        Checking if target branch feature exists in origin_2 remote... YES
+        Warn: Target branch feature lives in example-org/example-repo-1 project,
+        while source branch feature_1 lives in example-org/example-repo-2 project.
+        git-machete will now attempt to create an MR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitLab's MR model, it is not possible to cleanly create stacked MRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_1 -> feature, an MR from some-other-branch to feature_1
+        could not be created in example-org/example-repo-1, since its source branch feature_1 lives in example-org/example-repo-2.
+        Generally, MRs need to be created in whatever project the target branch lives.
+
+        Checking if target branch feature exists in origin_1 remote... YES
         Creating a MR from feature_1 to feature... OK, see www.gitlab.com
         Checking for open GitLab MRs (to determine MR chain)... OK
         Updating description of MR !17 to include the chain of MRs... OK
@@ -700,9 +718,19 @@ class TestGitLabCreateMR(BaseTest):
         expected_result = """
         Add feature_4 onto the inferred upstream (parent) branch feature_3? (y, N)
         Added branch feature_4 onto feature_3
-        Checking if target branch feature_3 exists in origin_2 remote... NO
-        Push untracked branch feature_3 to origin_2? (y, Q)
+        Warn: Target branch feature_3 lives in example-org/example-repo-1 project,
+        while source branch feature_4 lives in example-org/example-repo-2 project.
+        git-machete will now attempt to create an MR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitLab's MR model, it is not possible to cleanly create stacked MRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_4 -> feature_3, an MR from some-other-branch to feature_4
+        could not be created in example-org/example-repo-1, since its source branch feature_4 lives in example-org/example-repo-2.
+        Generally, MRs need to be created in whatever project the target branch lives.
+
+        Checking if target branch feature_3 exists in origin_1 remote... YES
         Creating a MR from feature_4 to feature_3... OK, see www.gitlab.com
+        Checking for open GitLab MRs (to determine MR chain)... OK
+        Updating description of MR !20 to include the chain of MRs... OK
         """
         assert_success(
             ['gitlab', 'create-mr'],
@@ -722,9 +750,19 @@ class TestGitLabCreateMR(BaseTest):
         expected_result = """
         Add feature_5 onto the inferred upstream (parent) branch feature_3? (y, N)
         Added branch feature_5 onto feature_3
-        Checking if target branch feature_3 exists in origin remote... NO
-        Push untracked branch feature_3 to origin? (y, Q)
+        Warn: Target branch feature_3 lives in example-org/example-repo-1 project,
+        while source branch feature_5 lives in example-org/example-repo-2 project.
+        git-machete will now attempt to create an MR in example-org/example-repo-1.
+
+        Note that due to the limitations of GitLab's MR model, it is not possible to cleanly create stacked MRs from forks.
+        For example, in a hypothetical chain some-other-branch -> feature_5 -> feature_3, an MR from some-other-branch to feature_5
+        could not be created in example-org/example-repo-1, since its source branch feature_5 lives in example-org/example-repo-2.
+        Generally, MRs need to be created in whatever project the target branch lives.
+
+        Checking if target branch feature_3 exists in origin_1 remote... YES
         Creating a MR from feature_5 to feature_3... OK, see www.gitlab.com
+        Checking for open GitLab MRs (to determine MR chain)... OK
+        Updating description of MR !21 to include the chain of MRs... OK
         """
         assert_success(
             ['gitlab', 'create-mr'],
diff --git a/tests/test_gitlab_restack_mr.py b/tests/test_gitlab_restack_mr.py
index 210cf84..c6ab92d 100644
--- a/tests/test_gitlab_restack_mr.py
+++ b/tests/test_gitlab_restack_mr.py
@@ -39,13 +39,13 @@ class TestGitLabRestackMR(BaseTest):
 
         assert_failure(
             ['gitlab', 'restack-mr'],
-            "No MRs have develop as its source branch"
+            "No MRs in example-org/example-repo have develop as its source branch"
         )
 
         self.repo_sandbox.new_branch("multiple-mr-branch").commit()
         assert_failure(
             ['gitlab', 'restack-mr'],
-            "Multiple MRs have multiple-mr-branch as its source branch: !16, !17"
+            "Multiple MRs in example-org/example-repo have multiple-mr-branch as its source branch: !16, !17"
         )
 
     def test_gitlab_restack_mr_branch_in_sync(self, mocker: MockerFixture) -> None:
diff --git a/tests/test_gitlab_retarget_mr.py b/tests/test_gitlab_retarget_mr.py
index a8bb248..61c43d8 100644
--- a/tests/test_gitlab_retarget_mr.py
+++ b/tests/test_gitlab_retarget_mr.py
@@ -108,7 +108,7 @@ class TestGitLabRetargetMR(BaseTest):
 
         assert_failure(
             ['gitlab', 'retarget-mr'],
-            'Multiple MRs have feature_4 as its source branch: !35, !40'
+            'Multiple MRs in example-org/example-repo have feature_4 as its source branch: !35, !40'
         )
 
     @staticmethod
@@ -186,11 +186,11 @@ class TestGitLabRetargetMR(BaseTest):
 
         assert_failure(
             ["gitlab", "retarget-mr", "--branch", "branch-without-mr"],
-            "No MRs have branch-without-mr as its source branch")
+            "No MRs in user/repo have branch-without-mr as its source branch")
 
         assert_success(
             ['gitlab', 'retarget-mr', '--branch', 'branch-without-mr', '--ignore-if-missing'],
-            "Warn: no MRs have branch-without-mr as its source branch\n")
+            "Warn: no MRs in user/repo have branch-without-mr as its source branch\n")
 
     def test_gitlab_retarget_mr_multiple_non_origin_remotes(self, mocker: MockerFixture) -> None:
         self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url)