Repository: VirtusLab/git-machete
Pull request #1405 (2025-03-01 22:16:58)

Preserve trailing lines when updating PR/MR descriptions

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1405?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.51%. Comparing base [(`c564b49`)](https://app.codecov.io/gh/VirtusLab/git-machete/commit/c564b49568f3f011e871fa583a3b79c4501eccdd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) to head [(`b1fea90`)](https://app.codecov.io/gh/VirtusLab/git-machete/commit/b1fea909aa4e2de570ac458d153e21f9923c0406?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).

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


```diff
@@           Coverage Diff            @@
##           develop    #1405   +/-   ##
========================================
  Coverage    98.51%   98.51%           
========================================
  Files           14       14           
  Lines         4040     4041    +1     
  Branches       789      789           
========================================
+ Hits          3980     3981    +1     
  Misses          38       38           
  Partials        22       22           
```

</details>

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


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ebdc9f1..7164185 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -192,7 +192,8 @@ jobs:
       - run: brew install fish zsh
       - run: pip install tox
       # TODO (#1005): make zsh completion tests pass on macOS machine on CI
-      - run: tox -e test-completions -- -vv -k "not zsh"
+      # TODO (#1407): restore fish completion tests on macOS once fish-shell#11205 is resolved
+      - run: tox -e test-completions -- -vv -k "not zsh and not fish"
       - run: PYTHON_VERSION=3-macos  tox -e coverage -- -vv
       - store_test_results:
           path: test-results/
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index caefea5..ed7fc5a 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -2,6 +2,8 @@
 
 ## New in git-machete 3.33.1
 
+- fixed: preserve trailing lines when updating PR/MR descriptions
+
 ## New in git-machete 3.33.0
 
 - added: `--by=...` flag to `git machete git{hub,lab} update-{pr,mr}-descriptions`
diff --git a/ci/checks/run-all-checks.sh b/ci/checks/run-all-checks.sh
index d57c36d..d2fb00a 100755
--- a/ci/checks/run-all-checks.sh
+++ b/ci/checks/run-all-checks.sh
@@ -28,13 +28,13 @@ enforce-shell-scripts-pass-shellcheck.sh
 prohibit-bash-usages-from-python.sh
 prohibit-current-date-in-tests.sh
 prohibit-deploy-step-in-circleci.sh
+prohibit-double-backticks-in-python.sh
 prohibit-exempli-gratia-in-rst.sh
 prohibit-fork-point-in-git-context.sh
-prohibit-id-est-in-rst.sh
-prohibit-double-backticks-in-python.sh
 prohibit-github-in-gitlab-files.sh
 prohibit-github-mr-or-gitlab-pr.sh
 prohibit-gitlab-in-github-files.sh
+prohibit-id-est-in-rst.sh
 prohibit-markdown-links-in-rst.sh
 prohibit-mrs-in-github-files.sh
 prohibit-prs-in-gitlab-files.sh
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index 4a39472..667f143 100644
--- a/docs/man/git-machete.1
+++ b/docs/man/git-machete.1
@@ -27,7 +27,7 @@ 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" "Feb 24, 2025" "" "git-machete"
+.TH "GIT-MACHETE" "1" "Mar 01, 2025" "" "git-machete"
 .SH NAME
 git-machete \- git-machete 3.33.1
 .sp
diff --git a/git_machete/client.py b/git_machete/client.py
index a0c594c..6ccec13 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -2553,7 +2553,8 @@ class MacheteClient:
         def skip_leading_empty(strs: List[str]) -> List[str]:
             return list(itertools.dropwhile(lambda line: line.strip() == '', strs))
 
-        lines = skip_leading_empty(pr.description.splitlines()) if pr.description else []
+        original_trailing_newlines = ''.join(itertools.takewhile(lambda c: c == '\n', reversed(pr.description or '')))
+        lines = pr.description.strip().splitlines() if pr.description else []
         style = self.__get_pr_description_into_style_from_config()
         text_to_prepend = self.__generate_pr_description_intro(pr, style)
         lines_to_prepend = text_to_prepend.splitlines() if text_to_prepend else []
@@ -2567,7 +2568,7 @@ class MacheteClient:
             if lines and '# Based on PR #' in lines[0]:
                 lines = lines[1:]
             lines = lines_to_prepend + ([''] if lines_to_prepend else []) + skip_leading_empty(lines)
-        return '\n'.join(lines)
+        return '\n'.join(lines) + original_trailing_newlines
 
     def retarget_pull_request(self, spec: CodeHostingSpec, head: LocalBranchShortName,
                               opt_ignore_if_missing: bool, opt_update_related_descriptions: bool) -> None:


Test patch:
diff --git a/tests/test_github_create_pr.py b/tests/test_github_create_pr.py
index 733d960..b0f848f 100644
--- a/tests/test_github_create_pr.py
+++ b/tests/test_github_create_pr.py
@@ -179,7 +179,8 @@ class TestGitHubCreatePR(BaseTest):
               * **PR #5 (THIS ONE)**:
                 `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->''')[1:]
+            <!-- end git-machete generated -->
+        ''')[1:]
         assert pr['draft'] is True
         assert pr['milestone'] == '42'
         assert pr['assignees'] == ['github_user']
@@ -271,7 +272,8 @@ class TestGitHubCreatePR(BaseTest):
                 * **PR #5 (THIS ONE)**:
                   `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->""")[1:]
+            <!-- end git-machete generated -->
+        """)[1:]
 
         expected_error_message = "A pull request already exists for test_repo:hotfix/add-trigger."
         assert_failure(["github", "create-pr"], expected_error_message)
@@ -450,7 +452,8 @@ 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_update_pr_descriptions.py b/tests/test_github_update_pr_descriptions.py
index 3e2aafa..1675601 100644
--- a/tests/test_github_update_pr_descriptions.py
+++ b/tests/test_github_update_pr_descriptions.py
@@ -23,8 +23,8 @@ class TestGitHubUpdatePRDescriptions(BaseTest):
         return [
             mock_pr_json(head='chore/redundant_checks', base='restrict_access', number=18),
             mock_pr_json(head='restrict_access', base='allow-ownership-link', number=17, user='github_user'),
-            mock_pr_json(head='allow-ownership-link', base='bugfix/feature', number=12, user='other_user'),
-            mock_pr_json(head='bugfix/feature', base='enhance/feature', number=6),
+            mock_pr_json(head='allow-ownership-link', base='bugfix/feature', number=12, user='other_user', body="# Summary\n\n"),
+            mock_pr_json(head='bugfix/feature', base='enhance/feature', number=6, body="# Summary\n"),
             mock_pr_json(head='enhance/add_user', base='develop', number=19),
             mock_pr_json(head='testing/add_user', base='bugfix/add_user', number=22),
             mock_pr_json(head='chore/comments', base='testing/add_user', number=24),
@@ -167,7 +167,8 @@ class TestGitHubUpdatePRDescriptions(BaseTest):
 
             <!-- end git-machete generated -->
 
-            # Summary""")[1:]
+            # Summary
+        """)[1:]
 
         pr = github_api_state.get_pull_by_number(12)
         assert pr is not None
@@ -192,7 +193,9 @@ class TestGitHubUpdatePRDescriptions(BaseTest):
 
             <!-- end git-machete generated -->
 
-            # Summary""")[1:]
+            # Summary
+
+        """)[1:]
 
         pr = github_api_state.get_pull_by_number(19)
         assert pr is not None
diff --git a/tests/test_gitlab_create_mr.py b/tests/test_gitlab_create_mr.py
index 52535b4..7129f95 100644
--- a/tests/test_gitlab_create_mr.py
+++ b/tests/test_gitlab_create_mr.py
@@ -179,7 +179,8 @@ class TestGitLabCreateMR(BaseTest):
               * **MR !5 (THIS ONE)**:
                 `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->''')[1:]
+            <!-- end git-machete generated -->
+        ''')[1:]
         assert pr['milestone_id'] == '42'
         assert pr['assignee_ids'] == [123456]
         assert pr['reviewer_ids'] == [123, 456]
@@ -269,7 +270,8 @@ class TestGitLabCreateMR(BaseTest):
                 * **MR !5 (THIS ONE)**:
                   `ignore-trailing` ← `chore/fields`
 
-            <!-- end git-machete generated -->""")[1:]
+            <!-- end git-machete generated -->
+        """)[1:]
 
         expected_error_message = "Another open merge request already exists for this source branch: !6"
         assert_failure(["gitlab", "create-mr"], expected_error_message)
@@ -430,7 +432,8 @@ 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_update_mr_descriptions.py b/tests/test_gitlab_update_mr_descriptions.py
index 8448c14..c991888 100644
--- a/tests/test_gitlab_update_mr_descriptions.py
+++ b/tests/test_gitlab_update_mr_descriptions.py
@@ -23,8 +23,8 @@ class TestGitLabUpdateMRDescriptions(BaseTest):
         return [
             mock_mr_json(head='chore/redundant_checks', base='restrict_access', number=18),
             mock_mr_json(head='restrict_access', base='allow-ownership-link', number=17, user='gitlab_user'),
-            mock_mr_json(head='allow-ownership-link', base='bugfix/feature', number=12, user='other_user'),
-            mock_mr_json(head='bugfix/feature', base='enhance/feature', number=6),
+            mock_mr_json(head='allow-ownership-link', base='bugfix/feature', number=12, user='other_user', body="# Summary\n\n"),
+            mock_mr_json(head='bugfix/feature', base='enhance/feature', number=6, body="# Summary\n"),
             mock_mr_json(head='enhance/add_user', base='develop', number=19),
             mock_mr_json(head='testing/add_user', base='bugfix/add_user', number=22),
             mock_mr_json(head='chore/comments', base='testing/add_user', number=24),
@@ -167,7 +167,8 @@ class TestGitLabUpdateMRDescriptions(BaseTest):
 
             <!-- end git-machete generated -->
 
-            # Summary""")[1:]
+            # Summary
+        """)[1:]
 
         mr = gitlab_api_state.get_mr_by_number(12)
         assert mr is not None
@@ -192,7 +193,9 @@ class TestGitLabUpdateMRDescriptions(BaseTest):
 
             <!-- end git-machete generated -->
 
-            # Summary""")[1:]
+            # Summary
+
+        """)[1:]
 
         mr = gitlab_api_state.get_mr_by_number(19)
         assert mr is not None