Skip to content

Commit 826beac

Browse files
SmileyChrisglyph
andauthored
Make towncrier create increment fragments rather than failing (#475)
* Make `towncrier create` increment fragments rather than failing Fixes #474 * Rename change file * Add example to tutorial of incrementing via towncrier create * Remove the counter example from the tutorial, this isn't core functionality * Better docstring for the new test --------- Co-authored-by: Glyph <[email protected]>
1 parent 566d315 commit 826beac

File tree

4 files changed

+57
-11
lines changed

4 files changed

+57
-11
lines changed

docs/cli.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ Create a news fragment in the directory that ``towncrier`` is configured to look
6464

6565
``towncrier create`` will enforce that the passed type (e.g. ``bugfix``) is valid.
6666

67+
If the filename exists already, ``towncrier create`` will add (and then increment) a number after the fragment type until it finds a filename that does not exist yet.
68+
In the above example, it will generate ``123.bugfix.1.rst`` if ``123.bugfix.rst`` already exists.
69+
6770
.. option:: --content, -c CONTENT
6871

6972
A string to use for content.

src/towncrier/create.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,17 @@ def __main(
119119
os.makedirs(fragments_directory)
120120

121121
segment_file = os.path.join(fragments_directory, filename)
122-
if os.path.exists(segment_file):
123-
raise click.ClickException(f"{segment_file} already exists")
122+
123+
retry = 0
124+
if filename.split(".")[-1] not in config.types:
125+
filename, extra_ext = os.path.splitext(filename)
126+
else:
127+
extra_ext = ""
128+
while os.path.exists(segment_file):
129+
retry += 1
130+
segment_file = os.path.join(
131+
fragments_directory, f"{filename}.{retry}{extra_ext}"
132+
)
124133

125134
if edit:
126135
edited_content = _get_news_content_from_user(content)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Make ``towncrier create`` use the fragment counter rather than failing
2+
on existing fragment names.
3+
4+
For example, if there is an existing fragment named ``123.feature``,
5+
then ``towncrier create 123.feature`` will now create a fragment
6+
named ``123.feature.1``.

src/towncrier/test/test_create.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,48 @@ def test_invalid_section(self):
151151
"Expected filename '123.foobar.rst' to be of format", result.output
152152
)
153153

154-
def test_file_exists(self):
154+
@with_isolated_runner
155+
def test_file_exists(self, runner: CliRunner):
155156
"""Ensure we don't overwrite existing files."""
156-
runner = CliRunner()
157+
setup_simple_project()
158+
frag_path = Path("foo", "newsfragments")
157159

158-
with runner.isolated_filesystem():
159-
setup_simple_project()
160+
for _ in range(3):
161+
result = runner.invoke(_main, ["123.feature"])
162+
self.assertEqual(result.exit_code, 0, result.output)
163+
164+
fragments = [f.name for f in frag_path.iterdir()]
165+
self.assertEqual(
166+
sorted(fragments),
167+
[
168+
"123.feature",
169+
"123.feature.1",
170+
"123.feature.2",
171+
],
172+
)
160173

161-
self.assertEqual([], os.listdir("foo/newsfragments"))
174+
@with_isolated_runner
175+
def test_file_exists_with_ext(self, runner: CliRunner):
176+
"""
177+
Ensure we don't overwrite existing files when using an extension after the
178+
fragment type.
179+
"""
180+
setup_simple_project()
181+
frag_path = Path("foo", "newsfragments")
162182

163-
runner.invoke(_main, ["123.feature.rst"])
183+
for _ in range(3):
164184
result = runner.invoke(_main, ["123.feature.rst"])
165-
166-
self.assertEqual(type(result.exception), SystemExit)
167-
self.assertIn("123.feature.rst already exists", result.output)
185+
self.assertEqual(result.exit_code, 0, result.output)
186+
187+
fragments = [f.name for f in frag_path.iterdir()]
188+
self.assertEqual(
189+
sorted(fragments),
190+
[
191+
"123.feature.1.rst",
192+
"123.feature.2.rst",
193+
"123.feature.rst",
194+
],
195+
)
168196

169197
@with_isolated_runner
170198
def test_create_orphan_fragment(self, runner: CliRunner):

0 commit comments

Comments
 (0)