Repository: twisted/towncrier

Issue:
Markdown lines should not have '# ' prepended
if using Markdown, each title has a '# ' prepended (build.py). However, this makes it a top-line header, and the example creates titles like:

```md
# ## <rest of title>
```

as opposed of:

```md
## <rest of title>
```

Certainly a few ways to fix this, such as a config like "default_md_prefix" which would be "# " and could be overridden to "" to ensure backwards compatibility. Happy to send a PR.

Discussion:

Patch:
diff --git a/docs/configuration.rst b/docs/configuration.rst
index fbbe730..c55299b 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -80,7 +80,10 @@ Top level keys
 
     ``""`` by default.
 
-    Formatted titles are appended a line of ``=`` on the following line (reStructuredText title format) unless the template has an ``.md`` suffix, in which case the title will instead be prefixed with ``#`` (markdown title format).
+    When using reStructuredText, formatted titles are underlined using the ``underlines`` configuration.
+    For titles, the first value from ``underlines`` is used to create the underline (which is inserted on the line following the title).
+    If the template has an ``.md`` suffix, we assume we are looking at markdown format and the title is applied as, i.e. full control over the title format is given to the user.
+    This choice is made because setting the correct markdown header level automatically is non-trivial (mainly because you don't know what context the generated CHANGELOG entries are being written into).
 
 ``issue_format``
     A format string for rendering the issue/ticket number in newsfiles.
diff --git a/src/towncrier/build.py b/src/towncrier/build.py
index 546ef11..3d18392 100644
--- a/src/towncrier/build.py
+++ b/src/towncrier/build.py
@@ -232,7 +232,7 @@ def __main(
             name=project_name, version=project_version, project_date=project_date
         )
         if is_markdown:
-            parts = [f"# {top_line}"]
+            parts = [top_line]
         else:
             parts = [top_line, config.underlines[0] * len(top_line)]
         parts.append(rendered)
diff --git a/src/towncrier/newsfragments/651.bugfix.md b/src/towncrier/newsfragments/651.bugfix.md
new file mode 100644
index 0000000..3b3f4c0
--- /dev/null
+++ b/src/towncrier/newsfragments/651.bugfix.md
@@ -0,0 +1,1 @@
+Control of the header formatting is once again completely up to the user when they are writing markdown files (fixes a regression introduced in [#610](https://github.com/twisted/towncrier/pull/610)).


Test patch:
diff --git a/src/towncrier/test/test_build.py b/src/towncrier/test/test_build.py
index 4556cca..f97fa91 100644
--- a/src/towncrier/test/test_build.py
+++ b/src/towncrier/test/test_build.py
@@ -15,7 +15,7 @@ from twisted.trial.unittest import TestCase
 
 from .._shell import cli
 from ..build import _main
-from .helpers import read, with_git_project, with_project, write
+from .helpers import read, read_pkg_resource, with_git_project, with_project, write
 
 
 class TestCli(TestCase):
@@ -1050,7 +1050,15 @@ class TestCli(TestCase):
     def test_title_format_custom_markdown(self, runner):
         """
         A non-empty title format adds the specified title, and if the target filename is
-        markdown then the title is added as a markdown header.
+        markdown then the title is added as given by the config.
+        In this way, full control is given to the user.
+        We make this choice for markdown files
+        because markdown header levels depend on where in the file
+        the section is being written and require modifications in the same line,
+        hence there is no easy way to know what to do to get the header to be at the right level.
+        This avoids a repeat of the regression introduced in
+        [#610](https://github.com/twisted/towncrier/pull/610),
+        which mistakenly assumed that starting the line with '# ' would work in all use cases.
         """
         with open("foo/newsfragments/123.feature", "w") as f:
             f.write("Adds levitation")
@@ -1075,7 +1083,7 @@ class TestCli(TestCase):
             Draft only -- nothing has been written.
             What is seen below is what would be written.
 
-            # [20-01-2001] CUSTOM RELEASE for FooBarBaz version 7.8.9
+            [20-01-2001] CUSTOM RELEASE for FooBarBaz version 7.8.9
 
             ### Features
 
@@ -1089,6 +1097,87 @@ class TestCli(TestCase):
         self.assertEqual(0, result.exit_code)
         self.assertEqual(expected_output, result.output)
 
+    @with_project(
+        config="""
+        [tool.towncrier]
+        package = "foo"
+        filename = "NEWS.md"
+        title_format = "### [{project_date}] CUSTOM RELEASE for {name} version {version}"
+        template = "custom_template.md"
+        """
+    )
+    def test_markdown_injected_after_header(self, runner):
+        """
+        Test that we can inject markdown after some fixed header
+        and have the injected markdown header levels set at the desired level.
+        This avoids a repeat of the regression introduced in
+        [#610](https://github.com/twisted/towncrier/pull/610),
+        which mistakenly assumed that starting the line with '# ' would work in all use cases.
+        """
+        write("foo/newsfragments/123.feature", "Adds levitation")
+        write(
+            "NEWS.md",
+            contents="""
+                # Top title
+
+                ## Section title
+
+                Some text explaining something
+
+                another line
+
+                ## Release notes
+
+                <!-- towncrier release notes start -->
+
+                a footer!
+            """,
+            dedent=True,
+        )
+
+        default_template = read_pkg_resource("templates/default.md")
+        write(
+            "custom_template.md",
+            contents=default_template.replace(
+                "### {{ definitions", "#### {{ definitions"
+            ),
+        )
+
+        result = runner.invoke(_main, ["--date", "01-01-2001"], catch_exceptions=False)
+
+        with open("foo/newsfragments/123.feature", "w") as f:
+            f.write("Adds levitation")
+
+        self.assertEqual(0, result.exit_code, result.output)
+        output = read("NEWS.md")
+
+        expected_output = dedent(
+            """
+            # Top title
+
+            ## Section title
+
+            Some text explaining something
+
+            another line
+
+            ## Release notes
+
+            <!-- towncrier release notes start -->
+
+            ### [01-01-2001] CUSTOM RELEASE for Foo version 1.2.3
+
+            #### Features
+
+            - Adds levitation (#123)
+
+
+            a footer!
+            """
+        )
+
+        self.assertEqual(expected_output, output)
+
     @with_project(
         config="""
         [tool.towncrier]