Repository: VirtusLab/git-machete
Pull request #1330 (2024-09-17 14:47:05)

Make `create-{pr,mr}` take the existing `<!-- {start,end} git-machete generated -->` markers into account


Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index ac06249..142390c 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -3,7 +3,8 @@
 ## New in git-machete 3.29.3
 
 - changed: no longer publish RPM files with GitHub releases
-- fixed: work around the parsing bug in `git patch-id` v2.46.1
+- fixed: work around the parsing bug in `git patch-id` v2.46.1 (reported by @ilai-deutel)
+- fixed: `git machete git{hub,lab} create-{pr,mr}` takes into account `<!-- {start,end} git-machete generated -->` in PR/MR template (suggested by @frank-west-iii)
 
 ## New in git-machete 3.29.2
 
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index fcdfc03..eff9283 100644
--- a/docs/man/git-machete.1
+++ b/docs/man/git-machete.1
@@ -363,7 +363,7 @@ from the message body of the first unique commit of the branch, even if \fB\&.gi
 .B \fBmachete.github.prDescriptionIntroStyle\fP:
 .INDENT 7.0
 .TP
-.B Select the style of the intro prepended to PR description:
+.B Select the style of the generated section (\(dqintro\(dq) added to the PR description:
 .INDENT 7.0
 .IP \(bu 2
 \fBfull\fP    \-\-\- include both a chain of upstream PRs (typically leading to \fBmain\fP, \fBmaster\fP, \fBdevelop\fP etc.) and a tree of downstream PRs
@@ -409,7 +409,7 @@ from the message body of the first unique commit of the branch, even if \fB\&.gi
 .B \fBmachete.gitlab.mrDescriptionIntroStyle\fP:
 .INDENT 7.0
 .TP
-.B Select the style of the intro prepended to MR description:
+.B Select the style of the generated section (\(dqintro\(dq) added to the MR description:
 .INDENT 7.0
 .IP \(bu 2
 \fBfull\fP    \-\-\- include both a chain of upstream MRs (typically leading to \fBmain\fP, \fBmaster\fP, \fBdevelop\fP etc.) and a tree of downstream MRs
@@ -999,11 +999,13 @@ If \fB\&.git/info/milestone\fP file is present, its contents (a single number \-
 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.
-If \fB\&.git/info/description\fP or \fB\&.github/pull_request_template.md\fP file is present, its contents are used as PR description.
+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
-If the newly\-created PR is stacked atop another PR, the actual PR description posted to GitHub will be prepended with a section
-listing the entire related chain of PRs.
+If the newly\-created PR is stacked atop another PR, the actual PR description posted to GitHub will include a generated section (\(dqintro\(dq)
+listing the entire related chain of PRs. This section will be delimited with \fB<!\-\- start git\-machete generated \-\->\fP
+and \fB<!\-\- end git\-machete generated \-\->\fP comments in Markdown. If a PR template file exists and contains these comments already,
+the generated section will be placed between them; otherwise, it will be placed at the beginning.
 .sp
 \fBOptions:\fP
 .INDENT 7.0
@@ -1037,8 +1039,8 @@ Draft PRs don\(aqt get code owners automatically added as reviewers.
 .B \fBretarget\-pr [\-b|\-\-branch=<branch>] [\-\-ignore\-if\-missing]\fP:
 Sets the base of the current (or specified) branch\(aqs PR to upstream (parent) branch, as seen by git machete (see \fBgit machete show up\fP).
 .sp
-If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will be prepended
-with an intro section listing the entire related chain of PRs.
+If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will include
+a generated section (\(dqintro\(dq) listing the entire related chain of PRs.
 .sp
 This intro will be updated or removed accordingly with the subsequent runs of \fBretarget\-pr\fP, even if the base branch is already up to date.
 .sp
@@ -1107,7 +1109,7 @@ from the message body of the first unique commit of the branch, even if \fB\&.gi
 .B \fBmachete.github.prDescriptionIntroStyle\fP (\fBcreate\-pr\fP, \fBrestack\-pr\fP and \fBretarget\-pr\fP):
 .INDENT 7.0
 .TP
-.B Select the style of the intro prepended to PR description:
+.B Select the style of the generated section (\(dqintro\(dq) added to the PR description:
 .INDENT 7.0
 .IP \(bu 2
 \fBfull\fP    \-\-\- include both a chain of upstream PRs (typically leading to \fBmain\fP, \fBmaster\fP, \fBdevelop\fP etc.) and a tree of downstream PRs
@@ -1237,11 +1239,13 @@ Note that you need to use a global (not per\-project) milestone id. Look for som
 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.
-If \fB\&.git/info/description\fP or \fB\&.gitlab/merge_request_templates/Default.md\fP file is present, its contents are used as MR description.
+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
-If the newly\-created MR is stacked atop another MR, the actual MR description posted to GitLab will be prepended with a section
-listing the entire related chain of MRs.
+If the newly\-created MR is stacked atop another MR, the actual MR description posted to GitLab will include a generated section (\(dqintro\(dq)
+listing the entire related chain of MRs. This section will be delimited with \fB<!\-\- start git\-machete generated \-\->\fP
+and \fB<!\-\- end git\-machete generated \-\->\fP comments in Markdown. If an MR template file exists and contains these comments already,
+the generated section will be placed between them; otherwise, it will be placed at the beginning.
 .sp
 \fBOptions:\fP
 .INDENT 7.0
@@ -1275,8 +1279,8 @@ Draft MRs don\(aqt get code owners automatically added as reviewers.
 .B \fBretarget\-mr [\-b|\-\-branch=<branch>] [\-\-ignore\-if\-missing]\fP:
 Sets the target of the current (or specified) branch\(aqs MR to upstream (parent) branch, as seen by git machete (see \fBgit machete show up\fP).
 .sp
-If after changing the target the MR ends up stacked atop another MR, the MR description posted to GitLab will be prepended
-with an intro section listing the entire related chain of MRs.
+If after changing the base the MR ends up stacked atop another MR, the MR description posted to GitLab will include
+a generated section (\(dqintro\(dq) listing the entire related chain of MRs.
 .sp
 This intro will be updated or removed accordingly with the subsequent runs of \fBretarget\-mr\fP, even if the target branch is already up to date.
 .sp
@@ -1331,7 +1335,7 @@ from the message body of the first unique commit of the branch, even if \fB\&.gi
 .B \fBmachete.gitlab.mrDescriptionIntroStyle\fP (\fBcreate\-mr\fP, \fBrestack\-mr\fP and \fBretarget\-mr\fP):
 .INDENT 7.0
 .TP
-.B Select the style of the intro prepended to MR description:
+.B Select the style of the generated section (\(dqintro\(dq) added to the MR description:
 .INDENT 7.0
 .IP \(bu 2
 \fBfull\fP    \-\-\- include both a chain of upstream MRs (typically leading to \fBmain\fP, \fBmaster\fP, \fBdevelop\fP etc.) and a tree of downstream MRs
diff --git a/docs/source/cli/github.rst b/docs/source/cli/github.rst
index 272f8c3..5e0667b 100644
--- a/docs/source/cli/github.rst
+++ b/docs/source/cli/github.rst
@@ -84,11 +84,13 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc
     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.
-    If ``.git/info/description`` or ``.github/pull_request_template.md`` file is present, its contents are used as PR description.
+    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.
 
-    If the newly-created PR is stacked atop another PR, the actual PR description posted to GitHub will be prepended with a section
-    listing the entire related chain of PRs.
+    If the newly-created PR is stacked atop another PR, the actual PR description posted to GitHub will include a generated section ("intro")
+    listing the entire related chain of PRs. This section will be delimited with ``<!-- start git-machete generated -->``
+    and ``<!-- end git-machete generated -->`` comments in Markdown. If a PR template file exists and contains these comments already,
+    the generated section will be placed between them; otherwise, it will be placed at the beginning.
 
     **Options:**
 
@@ -112,8 +114,8 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc
 ``retarget-pr [-b|--branch=<branch>] [--ignore-if-missing]``:
     Sets the base of the current (or specified) branch's PR to upstream (parent) branch, as seen by git machete (see ``git machete show up``).
 
-    If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will be prepended
-    with an intro section listing the entire related chain of PRs.
+    If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will include
+    a generated section ("intro") listing the entire related chain of PRs.
 
     This intro will be updated or removed accordingly with the subsequent runs of ``retarget-pr``, even if the base branch is already up to date.
 
diff --git a/docs/source/cli/gitlab.rst b/docs/source/cli/gitlab.rst
index 947444b..a5d8bf4 100644
--- a/docs/source/cli/gitlab.rst
+++ b/docs/source/cli/gitlab.rst
@@ -85,11 +85,13 @@ 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.
-    If ``.git/info/description`` or ``.gitlab/merge_request_templates/Default.md`` file is present, its contents are used as MR description.
+    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.
 
-    If the newly-created MR is stacked atop another MR, the actual MR description posted to GitLab will be prepended with a section
-    listing the entire related chain of MRs.
+    If the newly-created MR is stacked atop another MR, the actual MR description posted to GitLab will include a generated section ("intro")
+    listing the entire related chain of MRs. This section will be delimited with ``<!-- start git-machete generated -->``
+    and ``<!-- end git-machete generated -->`` comments in Markdown. If an MR template file exists and contains these comments already,
+    the generated section will be placed between them; otherwise, it will be placed at the beginning.
 
     **Options:**
 
@@ -113,8 +115,8 @@ Creates, checks out and manages GitLab MRs while keeping them reflected in branc
 ``retarget-mr [-b|--branch=<branch>] [--ignore-if-missing]``:
     Sets the target of the current (or specified) branch's MR to upstream (parent) branch, as seen by git machete (see ``git machete show up``).
 
-    If after changing the target the MR ends up stacked atop another MR, the MR description posted to GitLab will be prepended
-    with an intro section listing the entire related chain of MRs.
+    If after changing the base the MR ends up stacked atop another MR, the MR description posted to GitLab will include
+    a generated section ("intro") listing the entire related chain of MRs.
 
     This intro will be updated or removed accordingly with the subsequent runs of ``retarget-mr``, even if the target branch is already up to date.
 
diff --git a/docs/source/git-config-keys/github_prDescriptionIntroStyle.rst b/docs/source/git-config-keys/github_prDescriptionIntroStyle.rst
index 2948906..ea149d9 100644
--- a/docs/source/git-config-keys/github_prDescriptionIntroStyle.rst
+++ b/docs/source/git-config-keys/github_prDescriptionIntroStyle.rst
@@ -1,4 +1,4 @@
-Select the style of the intro prepended to PR description:
+Select the style of the generated section ("intro") added to the PR description:
   * ``full``    --- include both a chain of upstream PRs (typically leading to ``main``, ``master``, ``develop`` etc.) and a tree of downstream PRs
   * ``up-only`` --- default, include only a chain of upstream PRs
   * ``none``    --- prepend no intro to the PR description at all
diff --git a/docs/source/git-config-keys/gitlab_mrDescriptionIntroStyle.rst b/docs/source/git-config-keys/gitlab_mrDescriptionIntroStyle.rst
index 8465072..d19a8e4 100644
--- a/docs/source/git-config-keys/gitlab_mrDescriptionIntroStyle.rst
+++ b/docs/source/git-config-keys/gitlab_mrDescriptionIntroStyle.rst
@@ -1,4 +1,4 @@
-Select the style of the intro prepended to MR description:
+Select the style of the generated section ("intro") added to the MR description:
   * ``full``    --- include both a chain of upstream MRs (typically leading to ``main``, ``master``, ``develop`` etc.) and a tree of downstream MRs
   * ``up-only`` --- default, include only a chain of upstream MRs
   * ``none``    --- prepend no intro to the MR description at all
diff --git a/git_machete/client.py b/git_machete/client.py
index 153016a..1beb5bd 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -2465,11 +2465,12 @@ class MacheteClient:
             start_index = lines.index(self.START_GIT_MACHETE_GENERATED_COMMENT)
             end_index = lines.index(self.END_GIT_MACHETE_GENERATED_COMMENT)
             lines = lines[:start_index] + lines_to_prepend + lines[end_index + 1:]
+            lines = skip_leading_empty(lines)
         else:
             # For compatibility with pre-v3.23.0 format; only affects GitHub
             if lines and '# Based on PR #' in lines[0]:
-                lines = skip_leading_empty(lines[1:])
-            lines = lines_to_prepend + lines
+                lines = lines[1:]
+            lines = lines_to_prepend + ([''] if lines_to_prepend else []) + skip_leading_empty(lines)
         return '\n'.join(lines)
 
     def retarget_pr(self, spec: CodeHostingSpec, head: LocalBranchShortName, ignore_if_missing: bool) -> None:
@@ -2792,14 +2793,11 @@ class MacheteClient:
         if base_branch_found_on_remote or style == PRDescriptionIntroStyle.FULL:
             # As the description may include the reference to this PR itself (in case of a chain of >=2 PRs),
             # let's update the PR description after it's already created (so that we know the current PR's number).
-            text_to_prepend = self.__generate_pr_description_intro(code_hosting_client, pr, style)
-            if text_to_prepend:
-                if description:
-                    text_to_prepend += '\n'
-                description = text_to_prepend + description
+            new_description = self.__get_updated_pull_request_description(code_hosting_client, pr, description)
+            if new_description.strip() != description.strip():
                 print(f'Updating description of {pr.display_text()} to include '
                       f'the chain of {spec.pr_short_name}s... ', end='', flush=True)
-                code_hosting_client.set_description_of_pull_request(pr.number, description)
+                code_hosting_client.set_description_of_pull_request(pr.number, new_description)
                 print(fmt(ok_str))
 
         milestone_path: str = self.__git.get_main_git_subpath('info', 'milestone')
diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py
index 4a7e904..f7f0200 100644
--- a/git_machete/generated_docs.py
+++ b/git_machete/generated_docs.py
@@ -251,7 +251,7 @@ long_docs: Dict[str, str] = {
 
            `machete.github.prDescriptionIntroStyle`:
 
-                 Select the style of the intro prepended to PR description:
+                 Select the style of the generated section ("intro") added to the PR description:
 
                  * `full`    — include both a chain of upstream PRs (typically leading to `main`, `master`, `develop` etc.) and a tree of downstream PRs
 
@@ -288,7 +288,7 @@ long_docs: Dict[str, str] = {
 
            `machete.gitlab.mrDescriptionIntroStyle`:
 
-                 Select the style of the intro prepended to MR description:
+                 Select the style of the generated section ("intro") added to the MR description:
 
                  * `full`    — include both a chain of upstream MRs (typically leading to `main`, `master`, `develop` etc.) and a tree of downstream MRs
 
@@ -639,11 +639,13 @@ long_docs: Dict[str, str] = {
               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.
-              If `.git/info/description` or `.github/pull_request_template.md` file is present, its contents are used as PR description.
+              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.
 
-              If the newly-created PR is stacked atop another PR, the actual PR description posted to GitHub will be prepended with a section
-              listing the entire related chain of PRs.
+              If the newly-created PR is stacked atop another PR, the actual PR description posted to GitHub will include a generated section ("intro")
+              listing the entire related chain of PRs. This section will be delimited with `<!-- start git-machete generated -->`
+              and `<!-- end git-machete generated -->` comments in Markdown. If a PR template file exists and contains these comments already,
+              the generated section will be placed between them; otherwise, it will be placed at the beginning.
 
               <b>Options:</b>
 
@@ -676,8 +678,8 @@ long_docs: Dict[str, str] = {
  
               Sets the base of the current (or specified) branch's PR to upstream (parent) branch, as seen by git machete (see `git machete show up`).
 
-              If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will be prepended
-              with an intro section listing the entire related chain of PRs.
+              If after changing the base the PR ends up stacked atop another PR, the PR description posted to GitHub will include
+              a generated section ("intro") listing the entire related chain of PRs.
 
               This intro will be updated or removed accordingly with the subsequent runs of `retarget-pr`, even if the base branch is already up to date.
 
@@ -737,7 +739,7 @@ long_docs: Dict[str, str] = {
 
            `machete.github.prDescriptionIntroStyle` (`create-pr`, `restack-pr` and `retarget-pr`):
 
-                 Select the style of the intro prepended to PR description:
+                 Select the style of the generated section ("intro") added to the PR description:
 
                  * `full`    — include both a chain of upstream PRs (typically leading to `main`, `master`, `develop` etc.) and a tree of downstream PRs
 
@@ -830,11 +832,13 @@ 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.
-              If `.git/info/description` or `.gitlab/merge_request_templates/Default.md` file is present, its contents are used as MR description.
+              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.
 
-              If the newly-created MR is stacked atop another MR, the actual MR description posted to GitLab will be prepended with a section
-              listing the entire related chain of MRs.
+              If the newly-created MR is stacked atop another MR, the actual MR description posted to GitLab will include a generated section ("intro")
+              listing the entire related chain of MRs. This section will be delimited with `<!-- start git-machete generated -->`
+              and `<!-- end git-machete generated -->` comments in Markdown. If an MR template file exists and contains these comments already,
+              the generated section will be placed between them; otherwise, it will be placed at the beginning.
 
               <b>Options:</b>
 
@@ -867,8 +871,8 @@ long_docs: Dict[str, str] = {
  
               Sets the target of the current (or specified) branch's MR to upstream (parent) branch, as seen by git machete (see `git machete show up`).
 
-              If after changing the target the MR ends up stacked atop another MR, the MR description posted to GitLab will be prepended
-              with an intro section listing the entire related chain of MRs.
+              If after changing the base the MR ends up stacked atop another MR, the MR description posted to GitLab will include
+              a generated section ("intro") listing the entire related chain of MRs.
 
               This intro will be updated or removed accordingly with the subsequent runs of `retarget-mr`, even if the target branch is already up to date.
 
@@ -915,7 +919,7 @@ long_docs: Dict[str, str] = {
 
            `machete.gitlab.mrDescriptionIntroStyle` (`create-mr`, `restack-mr` and `retarget-mr`):
 
-                 Select the style of the intro prepended to MR description:
+                 Select the style of the generated section ("intro") added to the MR description:
 
                  * `full`    — include both a chain of upstream MRs (typically leading to `main`, `master`, `develop` etc.) and a tree of downstream MRs
 


Test patch:
diff --git a/tests/test_github_create_pr.py b/tests/test_github_create_pr.py
index db3ef23..1e6b240 100644
--- a/tests/test_github_create_pr.py
+++ b/tests/test_github_create_pr.py
@@ -118,7 +118,8 @@ class TestGitHubCreatePR(BaseTest):
 
         self.repo_sandbox.write_to_file(".git/info/milestone", "42")
         self.repo_sandbox.write_to_file(".git/info/reviewers", "foo\n\nbar")
-        self.repo_sandbox.write_to_file(".github/pull_request_template.md", "# PR title\n## Summary\n## Test plan\n")
+        template = "# PR title\n## Summary\n## Test plan\n\n<!-- start git-machete generated -->\n<!-- end git-machete generated -->\n"
+        self.repo_sandbox.write_to_file(".github/pull_request_template.md", template)
         assert_success(
             ["github", "create-pr", "--draft"],
             """
@@ -155,6 +156,10 @@ class TestGitHubCreatePR(BaseTest):
         assert pr is not None
         assert pr['title'] == 'remove outdated fields'
         assert pr['body'] == textwrap.dedent('''
+            # PR title
+            ## Summary
+            ## Test plan
+
             <!-- start git-machete generated -->
 
             # Based on PR #3
@@ -167,12 +172,7 @@ class TestGitHubCreatePR(BaseTest):
               * **PR #5 (THIS ONE)**:
                 `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->
-
-            # PR title
-            ## Summary
-            ## Test plan
-        ''')[1:]
+            <!-- end git-machete generated -->''')[1:]
         assert pr['draft'] is True
         assert pr['milestone'] == '42'
         assert pr['assignees'] == ['github_user']
@@ -407,8 +407,7 @@ class TestGitHubCreatePR(BaseTest):
 
             # PR title
             ## Summary
-            ## Test plan
-        ''')[1:]
+            ## Test plan''')[1:]
 
     @staticmethod
     def github_api_state_for_test_create_pr_missing_base_branch_on_remote() -> MockGitHubAPIState:
diff --git a/tests/test_github_retarget_pr.py b/tests/test_github_retarget_pr.py
index 78ac32c..ea7e18d 100644
--- a/tests/test_github_retarget_pr.py
+++ b/tests/test_github_retarget_pr.py
@@ -273,6 +273,7 @@ class TestGitHubRetargetPR(BaseTest):
                 `feature` ← `feature_1`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         # branch feature_2 is not present in any of the remotes
@@ -357,6 +358,7 @@ class TestGitHubRetargetPR(BaseTest):
                   `feature_2` ← `feature_3`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         body = \
@@ -393,6 +395,7 @@ class TestGitHubRetargetPR(BaseTest):
                 `feature` ← `feature_3`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         body = \
@@ -447,6 +450,7 @@ class TestGitHubRetargetPR(BaseTest):
                   `feature` ← `feature_4`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
     @staticmethod
diff --git a/tests/test_gitlab_create_mr.py b/tests/test_gitlab_create_mr.py
index 4578ea4..30e8d23 100644
--- a/tests/test_gitlab_create_mr.py
+++ b/tests/test_gitlab_create_mr.py
@@ -117,7 +117,8 @@ class TestGitLabCreateMR(BaseTest):
 
         self.repo_sandbox.write_to_file(".git/info/milestone", "42")
         self.repo_sandbox.write_to_file(".git/info/reviewers", "foo\n\nbar")
-        self.repo_sandbox.write_to_file(".gitlab/merge_request_templates/Default.md", "# MR title\n## Summary\n## Test plan\n")
+        template = "# MR title\n## Summary\n## Test plan\n\n<!-- start git-machete generated -->\n<!-- end git-machete generated -->\n"
+        self.repo_sandbox.write_to_file(".gitlab/merge_request_templates/Default.md", template)
         assert_success(
             ["gitlab", "create-mr", "--draft"],
             """
@@ -154,6 +155,10 @@ class TestGitLabCreateMR(BaseTest):
         assert pr is not None
         assert pr['title'] == 'Draft: remove outdated fields'
         assert pr['description'] == textwrap.dedent('''
+            # MR title
+            ## Summary
+            ## Test plan
+
             <!-- start git-machete generated -->
 
             # Based on MR !3
@@ -166,12 +171,7 @@ class TestGitLabCreateMR(BaseTest):
               * **MR !5 (THIS ONE)**:
                 `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->
-
-            # MR title
-            ## Summary
-            ## Test plan
-        ''')[1:]
+            <!-- end git-machete generated -->''')[1:]
         assert pr['milestone_id'] == '42'
         assert pr['assignee_ids'] == [123456]
         assert pr['reviewer_ids'] == [123, 456]
@@ -387,8 +387,7 @@ class TestGitLabCreateMR(BaseTest):
 
             # MR title
             ## Summary
-            ## Test plan
-        ''')[1:]
+            ## Test plan''')[1:]
 
     @staticmethod
     def gitlab_api_state_for_test_create_mr_missing_base_branch_on_remote() -> MockGitLabAPIState:
diff --git a/tests/test_gitlab_retarget_mr.py b/tests/test_gitlab_retarget_mr.py
index c85945c..0d218de 100644
--- a/tests/test_gitlab_retarget_mr.py
+++ b/tests/test_gitlab_retarget_mr.py
@@ -279,6 +279,7 @@ class TestGitLabRetargetMR(BaseTest):
                 `feature` ← `feature_1`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         # branch feature_2 is not present in any of the remotes
@@ -328,7 +329,8 @@ class TestGitLabRetargetMR(BaseTest):
               * **MR !25 (THIS ONE)**:
                 `feature` ← `feature_2`
 
-            <!-- end git-machete generated -->''')[1:]
+            <!-- end git-machete generated -->
+        ''')[1:]
 
         # branch feature_3 present in only one remote: origin_1 and has tracking data
         (
@@ -375,6 +377,7 @@ class TestGitLabRetargetMR(BaseTest):
                   `feature_2` ← `feature_3`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         body = \
@@ -411,6 +414,7 @@ class TestGitLabRetargetMR(BaseTest):
                 `feature` ← `feature_3`
 
             <!-- end git-machete generated -->
+
             # Summary''')[1:]
 
         body = \