python-kacl 0.7.0__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {python_kacl-0.7.0/python_kacl.egg-info → python_kacl-0.7.2}/PKG-INFO +124 -2
  2. {python_kacl-0.7.0 → python_kacl-0.7.2}/README.md +123 -1
  3. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/__init__.py +1 -1
  4. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/document.py +54 -0
  5. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/kacl_cli.py +48 -2
  6. {python_kacl-0.7.0 → python_kacl-0.7.2}/pyproject.toml +1 -1
  7. {python_kacl-0.7.0 → python_kacl-0.7.2/python_kacl.egg-info}/PKG-INFO +124 -2
  8. {python_kacl-0.7.0 → python_kacl-0.7.2}/tests/test_cli.py +151 -0
  9. {python_kacl-0.7.0 → python_kacl-0.7.2}/LICENSE +0 -0
  10. {python_kacl-0.7.0 → python_kacl-0.7.2}/MANIFEST.in +0 -0
  11. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/__main__.py +0 -0
  12. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/changes.py +0 -0
  13. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/config/kacl-default.yml +0 -0
  14. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/config.py +0 -0
  15. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/element.py +0 -0
  16. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/exception.py +0 -0
  17. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/jira_client.py +0 -0
  18. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/link_provider.py +0 -0
  19. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/parser.py +0 -0
  20. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/serializer.py +0 -0
  21. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/utils.py +0 -0
  22. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/validation.py +0 -0
  23. {python_kacl-0.7.0 → python_kacl-0.7.2}/kacl/version.py +0 -0
  24. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/SOURCES.txt +0 -0
  25. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/dependency_links.txt +0 -0
  26. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/entry_points.txt +0 -0
  27. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/not-zip-safe +0 -0
  28. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/requires.txt +0 -0
  29. {python_kacl-0.7.0 → python_kacl-0.7.2}/python_kacl.egg-info/top_level.txt +0 -0
  30. {python_kacl-0.7.0 → python_kacl-0.7.2}/requirements.txt +0 -0
  31. {python_kacl-0.7.0 → python_kacl-0.7.2}/setup.cfg +0 -0
  32. {python_kacl-0.7.0 → python_kacl-0.7.2}/tests/test_cli_workflow.py +0 -0
  33. {python_kacl-0.7.0 → python_kacl-0.7.2}/tests/test_kacl.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-kacl
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Python module and CLI tool for validating and modifying Changelogs in "keep-a-changelog" format"
5
5
  Author-email: Matthias Schmieder <schmieder.matthias@gmail.com>
6
6
  License: MIT
@@ -42,6 +42,8 @@ A tool for verifying and modifying changelog in the [**K**eep-**A-C**hange-**L**
42
42
  - [Print the current release version](#print-the-current-release-version)
43
43
  - [Print a single release changelog](#print-a-single-release-changelog)
44
44
  - [Add an entry to an unreleased section](#add-an-entry-to-an-unreleased-section)
45
+ - [Stashing Unreleased Changes](#stashing-unreleased-changes)
46
+ - [Pre-commit Hook Integration](#pre-commit-hook-integration)
45
47
  - [Prepare a Changelog for a Release](#prepare-a-changelog-for-a-release)
46
48
  - [Changelog Fragments](#changelog-fragments)
47
49
  - [Configuration](#configuration)
@@ -121,13 +123,26 @@ docker -v $(pwd):$(pwd) -w $(pwd) mschmieder/kacl-cli:latest verify
121
123
 
122
124
  The package can also be used as a pre-commit hook. Just add the following to your `.pre-commit-config.yaml`
123
125
 
126
+ **Verify Changelog Format**
127
+
124
128
  ```yaml
125
129
  - repo: https://gitlab.com/schmieder.matthias/python-kacl
126
- rev: 'v0.3.0'
130
+ rev: 'v0.7.0' # Use the latest version
127
131
  hooks:
128
132
  - id: kacl-verify
129
133
  ```
130
134
 
135
+ **Auto-Stash Unreleased Changes (Prevent Merge Conflicts)**
136
+
137
+ ```yaml
138
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
139
+ rev: 'v0.7.0' # Use the latest version
140
+ hooks:
141
+ - id: kacl-stash
142
+ ```
143
+
144
+ The `kacl-stash` hook automatically moves unreleased changes to stash files before each commit, preventing merge conflicts in `CHANGELOG.md`. See [Stashing Unreleased Changes](#stashing-unreleased-changes) for more details.
145
+
131
146
  ## CLI
132
147
 
133
148
  ```
@@ -143,6 +158,7 @@ Commands:
143
158
  get Returns a given version from the Changelog
144
159
  new Creates a new changelog.
145
160
  release Creates a release for the latest 'unreleased' changes.
161
+ stash Moves all unreleased changes from the changelog to the stash.
146
162
  verify Verifies if the changelog is in "keep-a-changelog" format.
147
163
  ```
148
164
 
@@ -361,6 +377,112 @@ kacl-cli add added 'We just added some new cool stuff' --modify
361
377
  kacl-cli add changed 'And changed things a bit' --modify
362
378
  ```
363
379
 
380
+ ## Stashing Unreleased Changes
381
+
382
+ ```
383
+ Usage: kacl-cli stash [OPTIONS]
384
+
385
+ Moves all unreleased changes from the changelog to the stash.
386
+
387
+ Options:
388
+ -m, --modify This option will modify the changelog file directly.
389
+ --help Show this message and exit.
390
+ ```
391
+
392
+ The `stash` command provides a powerful solution for managing unreleased changes and **preventing merge conflicts** in collaborative development environments. It moves all unreleased changes from `CHANGELOG.md` to a stash file, leaving an empty "Unreleased" section in the main changelog.
393
+
394
+ **Primary Use Case: Avoiding Merge Conflicts**
395
+
396
+ When multiple team members work on different branches simultaneously and manually edit `CHANGELOG.md`, merge conflicts are inevitable. The `stash` command solves this problem by:
397
+
398
+ 1. **Moving changes out of the main changelog** into separate stash files
399
+ 2. **Preventing conflicts** during merges since the main changelog remains minimal
400
+ 3. **Preserving all changes** in stash files that are later consolidated during release
401
+
402
+ **Usage**
403
+
404
+ ```bash
405
+ # Preview what would be stashed (dry-run)
406
+ kacl-cli stash
407
+
408
+ # Actually stash the changes and update CHANGELOG.md
409
+ kacl-cli stash --modify
410
+ ```
411
+
412
+ **How It Works**
413
+
414
+ When you run `kacl-cli stash --modify`:
415
+
416
+ 1. All changes from the "Unreleased" section are extracted
417
+ 2. Changes are moved to a stash file in `.kacl_stash/` directory (default location)
418
+ 3. Stash files are named based on:
419
+ - Git branch name (when inside a git repository): `feature-branch.md`
420
+ - Current date (when outside git): `20230101.md`
421
+ 4. The "Unreleased" section in `CHANGELOG.md` is cleared
422
+ 5. During `kacl-cli release`, all stashed changes are automatically merged into the release
423
+
424
+ **Example Workflow**
425
+
426
+ ```bash
427
+ # Developer edits CHANGELOG.md directly
428
+ echo "- Added new feature" >> CHANGELOG.md
429
+
430
+ # Before committing, stash the changes to avoid conflicts
431
+ kacl-cli stash --modify
432
+
433
+ # CHANGELOG.md now has an empty Unreleased section
434
+ # Changes are in .kacl_stash/your-branch.md
435
+
436
+ # When ready to release, all stashed changes are included
437
+ kacl-cli release patch --modify
438
+ ```
439
+
440
+ ### Pre-commit Hook Integration
441
+
442
+ For maximum convenience, you can automate the stashing process using a pre-commit hook. This allows developers to continue editing `CHANGELOG.md` manually while automatically moving changes to the stash before each commit.
443
+
444
+ **Setup**
445
+
446
+ Add the `kacl-stash` hook to your `.pre-commit-config.yaml`:
447
+
448
+ ```yaml
449
+ repos:
450
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
451
+ rev: 'v0.7.0' # Use the latest version
452
+ hooks:
453
+ - id: kacl-stash
454
+ ```
455
+
456
+ **How It Works**
457
+
458
+ 1. Developer manually edits `CHANGELOG.md` (traditional workflow)
459
+ 2. Pre-commit hook automatically runs `kacl-cli stash -m` before each commit
460
+ 3. Changes are moved to stash files
461
+ 4. Only the cleaned `CHANGELOG.md` is committed
462
+ 5. Multiple branches can work independently without conflicts
463
+ 6. During release, all stashed changes are consolidated
464
+
465
+ **Benefits**
466
+
467
+ - ✅ **No workflow changes**: Developers continue editing `CHANGELOG.md` normally
468
+ - ✅ **Zero merge conflicts**: Main changelog stays minimal and conflict-free
469
+ - ✅ **Automatic management**: Pre-commit hook handles everything
470
+ - ✅ **Branch isolation**: Each branch has its own stash file
471
+ - ✅ **Seamless integration**: Changes automatically included in releases
472
+
473
+ **Configuration**
474
+
475
+ You can customize the stash directory in `.kacl.yml`:
476
+
477
+ ```yaml
478
+ kacl:
479
+ stash:
480
+ dir: .kacl_stash # Custom stash directory
481
+ always: False # Set to True to always use stash for 'kacl add'
482
+ ```
483
+
484
+ **Note**: The `stash` command is complementary to [Changelog Fragments](#changelog-fragments). While fragments support the `kacl add` workflow, the `stash` command is specifically designed for teams that prefer manually editing `CHANGELOG.md` but want to avoid merge conflicts.
485
+
364
486
  ## Prepare a Changelog for a Release
365
487
 
366
488
  ```
@@ -20,6 +20,8 @@ A tool for verifying and modifying changelog in the [**K**eep-**A-C**hange-**L**
20
20
  - [Print the current release version](#print-the-current-release-version)
21
21
  - [Print a single release changelog](#print-a-single-release-changelog)
22
22
  - [Add an entry to an unreleased section](#add-an-entry-to-an-unreleased-section)
23
+ - [Stashing Unreleased Changes](#stashing-unreleased-changes)
24
+ - [Pre-commit Hook Integration](#pre-commit-hook-integration)
23
25
  - [Prepare a Changelog for a Release](#prepare-a-changelog-for-a-release)
24
26
  - [Changelog Fragments](#changelog-fragments)
25
27
  - [Configuration](#configuration)
@@ -99,13 +101,26 @@ docker -v $(pwd):$(pwd) -w $(pwd) mschmieder/kacl-cli:latest verify
99
101
 
100
102
  The package can also be used as a pre-commit hook. Just add the following to your `.pre-commit-config.yaml`
101
103
 
104
+ **Verify Changelog Format**
105
+
102
106
  ```yaml
103
107
  - repo: https://gitlab.com/schmieder.matthias/python-kacl
104
- rev: 'v0.3.0'
108
+ rev: 'v0.7.0' # Use the latest version
105
109
  hooks:
106
110
  - id: kacl-verify
107
111
  ```
108
112
 
113
+ **Auto-Stash Unreleased Changes (Prevent Merge Conflicts)**
114
+
115
+ ```yaml
116
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
117
+ rev: 'v0.7.0' # Use the latest version
118
+ hooks:
119
+ - id: kacl-stash
120
+ ```
121
+
122
+ The `kacl-stash` hook automatically moves unreleased changes to stash files before each commit, preventing merge conflicts in `CHANGELOG.md`. See [Stashing Unreleased Changes](#stashing-unreleased-changes) for more details.
123
+
109
124
  ## CLI
110
125
 
111
126
  ```
@@ -121,6 +136,7 @@ Commands:
121
136
  get Returns a given version from the Changelog
122
137
  new Creates a new changelog.
123
138
  release Creates a release for the latest 'unreleased' changes.
139
+ stash Moves all unreleased changes from the changelog to the stash.
124
140
  verify Verifies if the changelog is in "keep-a-changelog" format.
125
141
  ```
126
142
 
@@ -339,6 +355,112 @@ kacl-cli add added 'We just added some new cool stuff' --modify
339
355
  kacl-cli add changed 'And changed things a bit' --modify
340
356
  ```
341
357
 
358
+ ## Stashing Unreleased Changes
359
+
360
+ ```
361
+ Usage: kacl-cli stash [OPTIONS]
362
+
363
+ Moves all unreleased changes from the changelog to the stash.
364
+
365
+ Options:
366
+ -m, --modify This option will modify the changelog file directly.
367
+ --help Show this message and exit.
368
+ ```
369
+
370
+ The `stash` command provides a powerful solution for managing unreleased changes and **preventing merge conflicts** in collaborative development environments. It moves all unreleased changes from `CHANGELOG.md` to a stash file, leaving an empty "Unreleased" section in the main changelog.
371
+
372
+ **Primary Use Case: Avoiding Merge Conflicts**
373
+
374
+ When multiple team members work on different branches simultaneously and manually edit `CHANGELOG.md`, merge conflicts are inevitable. The `stash` command solves this problem by:
375
+
376
+ 1. **Moving changes out of the main changelog** into separate stash files
377
+ 2. **Preventing conflicts** during merges since the main changelog remains minimal
378
+ 3. **Preserving all changes** in stash files that are later consolidated during release
379
+
380
+ **Usage**
381
+
382
+ ```bash
383
+ # Preview what would be stashed (dry-run)
384
+ kacl-cli stash
385
+
386
+ # Actually stash the changes and update CHANGELOG.md
387
+ kacl-cli stash --modify
388
+ ```
389
+
390
+ **How It Works**
391
+
392
+ When you run `kacl-cli stash --modify`:
393
+
394
+ 1. All changes from the "Unreleased" section are extracted
395
+ 2. Changes are moved to a stash file in `.kacl_stash/` directory (default location)
396
+ 3. Stash files are named based on:
397
+ - Git branch name (when inside a git repository): `feature-branch.md`
398
+ - Current date (when outside git): `20230101.md`
399
+ 4. The "Unreleased" section in `CHANGELOG.md` is cleared
400
+ 5. During `kacl-cli release`, all stashed changes are automatically merged into the release
401
+
402
+ **Example Workflow**
403
+
404
+ ```bash
405
+ # Developer edits CHANGELOG.md directly
406
+ echo "- Added new feature" >> CHANGELOG.md
407
+
408
+ # Before committing, stash the changes to avoid conflicts
409
+ kacl-cli stash --modify
410
+
411
+ # CHANGELOG.md now has an empty Unreleased section
412
+ # Changes are in .kacl_stash/your-branch.md
413
+
414
+ # When ready to release, all stashed changes are included
415
+ kacl-cli release patch --modify
416
+ ```
417
+
418
+ ### Pre-commit Hook Integration
419
+
420
+ For maximum convenience, you can automate the stashing process using a pre-commit hook. This allows developers to continue editing `CHANGELOG.md` manually while automatically moving changes to the stash before each commit.
421
+
422
+ **Setup**
423
+
424
+ Add the `kacl-stash` hook to your `.pre-commit-config.yaml`:
425
+
426
+ ```yaml
427
+ repos:
428
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
429
+ rev: 'v0.7.0' # Use the latest version
430
+ hooks:
431
+ - id: kacl-stash
432
+ ```
433
+
434
+ **How It Works**
435
+
436
+ 1. Developer manually edits `CHANGELOG.md` (traditional workflow)
437
+ 2. Pre-commit hook automatically runs `kacl-cli stash -m` before each commit
438
+ 3. Changes are moved to stash files
439
+ 4. Only the cleaned `CHANGELOG.md` is committed
440
+ 5. Multiple branches can work independently without conflicts
441
+ 6. During release, all stashed changes are consolidated
442
+
443
+ **Benefits**
444
+
445
+ - ✅ **No workflow changes**: Developers continue editing `CHANGELOG.md` normally
446
+ - ✅ **Zero merge conflicts**: Main changelog stays minimal and conflict-free
447
+ - ✅ **Automatic management**: Pre-commit hook handles everything
448
+ - ✅ **Branch isolation**: Each branch has its own stash file
449
+ - ✅ **Seamless integration**: Changes automatically included in releases
450
+
451
+ **Configuration**
452
+
453
+ You can customize the stash directory in `.kacl.yml`:
454
+
455
+ ```yaml
456
+ kacl:
457
+ stash:
458
+ dir: .kacl_stash # Custom stash directory
459
+ always: False # Set to True to always use stash for 'kacl add'
460
+ ```
461
+
462
+ **Note**: The `stash` command is complementary to [Changelog Fragments](#changelog-fragments). While fragments support the `kacl add` workflow, the `stash` command is specifically designed for teams that prefer manually editing `CHANGELOG.md` but want to avoid merge conflicts.
463
+
342
464
  ## Prepare a Changelog for a Release
343
465
 
344
466
  ```
@@ -1,5 +1,5 @@
1
1
  # Version of the python-kacl package
2
- __version__ = "0.7.0"
2
+ __version__ = "0.7.2"
3
3
 
4
4
  from kacl.document import KACLDocument
5
5
  from kacl.serializer import KACLMarkdownSerializer
@@ -330,6 +330,60 @@ class KACLDocument:
330
330
  self.__versions.insert(0, unreleased_version)
331
331
  unreleased_version.add(section.capitalize(), data)
332
332
 
333
+ def stash_unreleased(self):
334
+ """Moves all unreleased changes to the stash file.
335
+
336
+ This method takes all changes from the Unreleased section and writes them to
337
+ the stash file, then clears the Unreleased section from the changelog.
338
+ """
339
+ # Check if there are any unreleased changes
340
+ if not self.has_changes():
341
+ return False
342
+
343
+ # Get the unreleased version
344
+ unreleased_version = self.get("Unreleased")
345
+ if not unreleased_version:
346
+ return False
347
+
348
+ # Get or create the stash file
349
+ stash_file = self._get_stash_file()
350
+
351
+ if os.path.exists(stash_file):
352
+ # Load the stash file
353
+ kacl_changelog = kacl.load(stash_file)
354
+ else:
355
+ # Create a new stash file with an empty changelog
356
+ kacl_changelog = kacl.new()
357
+
358
+ # Add all unreleased changes to the stash
359
+ sections = unreleased_version.sections()
360
+ if sections:
361
+ for section_title, section_changes in sections.items():
362
+ for change_item in section_changes.items():
363
+ kacl_changelog.add(section=section_title, data=change_item)
364
+
365
+ # Write the updated stash file
366
+ kacl_changelog_content = kacl.dump(kacl_changelog)
367
+ with open(stash_file, "w") as f:
368
+ f.write(kacl_changelog_content)
369
+ f.close()
370
+
371
+ # Clear the unreleased section by replacing it with an empty one
372
+ # Find and remove the current unreleased version but keep the link reference if it exists
373
+ link = None
374
+
375
+ for i, version in enumerate(self.__versions):
376
+ if version.version() == "Unreleased":
377
+ link = version.link()
378
+ self.__versions.pop(i)
379
+ break
380
+
381
+ # Add a new empty unreleased version
382
+ new_unreleased = KACLVersion(version="Unreleased", link=link)
383
+ self.__versions.insert(0, new_unreleased)
384
+
385
+ return True
386
+
333
387
  def release(
334
388
  self,
335
389
  version=None,
@@ -44,7 +44,7 @@ def raise_on_invalid(kacl_changelog):
44
44
  sys.exit(1)
45
45
 
46
46
 
47
- def load_changelog(ctx):
47
+ def load_changelog(ctx, load_stash_files=True):
48
48
  config_file_path = ctx.obj["config"]
49
49
  input_file = ctx.obj["file"]
50
50
 
@@ -69,7 +69,8 @@ def load_changelog(ctx):
69
69
  # read the changelog
70
70
  kacl_changelog = kacl.load(kacl_config.changelog_file_path)
71
71
  kacl_changelog.config = kacl_config
72
- kacl_changelog.load_stash()
72
+ if load_stash_files:
73
+ kacl_changelog.load_stash()
73
74
 
74
75
  # share the objects
75
76
  return kacl_changelog
@@ -159,6 +160,51 @@ def add(ctx, section, message, modify, stash):
159
160
  click.echo(kacl_changelog_content)
160
161
 
161
162
 
163
+ @cli.command()
164
+ @click.pass_context
165
+ @click.option(
166
+ "-m",
167
+ "--modify",
168
+ is_flag=True,
169
+ help="This option will modify the changelog file directly.",
170
+ )
171
+ def stash(ctx, modify):
172
+ """Moves all unreleased changes from the changelog to the stash."""
173
+ # Load changelog WITHOUT loading stash files to avoid circular stashing
174
+ kacl_changelog = load_changelog(ctx, load_stash_files=False)
175
+
176
+ # Check if there are changes to stash
177
+ if not kacl_changelog.has_changes():
178
+ click.echo(
179
+ click.style("Info: ", fg="yellow") + "No unreleased changes to stash."
180
+ )
181
+ return
182
+
183
+ # Stash the unreleased changes
184
+ kacl_changelog.stash_unreleased()
185
+
186
+ stash_file = kacl_changelog._get_stash_file()
187
+ click.echo(
188
+ click.style("Success: ", fg="green")
189
+ + f"Unreleased changes have been moved to {stash_file}"
190
+ )
191
+
192
+ # Update the changelog file if modify flag is set
193
+ if modify:
194
+ kacl_changelog_content = kacl.dump(kacl_changelog)
195
+ with open(kacl_changelog.config.changelog_file_path, "w") as f:
196
+ f.write(kacl_changelog_content)
197
+ f.close()
198
+ click.echo(
199
+ click.style("Success: ", fg="green")
200
+ + f"Changelog file {kacl_changelog.config.changelog_file_path} has been updated."
201
+ )
202
+ else:
203
+ # Just print the modified changelog
204
+ kacl_changelog_content = kacl.dump(kacl_changelog)
205
+ click.echo(kacl_changelog_content)
206
+
207
+
162
208
  @cli.command()
163
209
  @click.pass_context
164
210
  def current(ctx):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-kacl"
7
- version = "0.7.0"
7
+ version = "0.7.2"
8
8
  description = "Python module and CLI tool for validating and modifying Changelogs in \"keep-a-changelog\" format\""
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-kacl
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Python module and CLI tool for validating and modifying Changelogs in "keep-a-changelog" format"
5
5
  Author-email: Matthias Schmieder <schmieder.matthias@gmail.com>
6
6
  License: MIT
@@ -42,6 +42,8 @@ A tool for verifying and modifying changelog in the [**K**eep-**A-C**hange-**L**
42
42
  - [Print the current release version](#print-the-current-release-version)
43
43
  - [Print a single release changelog](#print-a-single-release-changelog)
44
44
  - [Add an entry to an unreleased section](#add-an-entry-to-an-unreleased-section)
45
+ - [Stashing Unreleased Changes](#stashing-unreleased-changes)
46
+ - [Pre-commit Hook Integration](#pre-commit-hook-integration)
45
47
  - [Prepare a Changelog for a Release](#prepare-a-changelog-for-a-release)
46
48
  - [Changelog Fragments](#changelog-fragments)
47
49
  - [Configuration](#configuration)
@@ -121,13 +123,26 @@ docker -v $(pwd):$(pwd) -w $(pwd) mschmieder/kacl-cli:latest verify
121
123
 
122
124
  The package can also be used as a pre-commit hook. Just add the following to your `.pre-commit-config.yaml`
123
125
 
126
+ **Verify Changelog Format**
127
+
124
128
  ```yaml
125
129
  - repo: https://gitlab.com/schmieder.matthias/python-kacl
126
- rev: 'v0.3.0'
130
+ rev: 'v0.7.0' # Use the latest version
127
131
  hooks:
128
132
  - id: kacl-verify
129
133
  ```
130
134
 
135
+ **Auto-Stash Unreleased Changes (Prevent Merge Conflicts)**
136
+
137
+ ```yaml
138
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
139
+ rev: 'v0.7.0' # Use the latest version
140
+ hooks:
141
+ - id: kacl-stash
142
+ ```
143
+
144
+ The `kacl-stash` hook automatically moves unreleased changes to stash files before each commit, preventing merge conflicts in `CHANGELOG.md`. See [Stashing Unreleased Changes](#stashing-unreleased-changes) for more details.
145
+
131
146
  ## CLI
132
147
 
133
148
  ```
@@ -143,6 +158,7 @@ Commands:
143
158
  get Returns a given version from the Changelog
144
159
  new Creates a new changelog.
145
160
  release Creates a release for the latest 'unreleased' changes.
161
+ stash Moves all unreleased changes from the changelog to the stash.
146
162
  verify Verifies if the changelog is in "keep-a-changelog" format.
147
163
  ```
148
164
 
@@ -361,6 +377,112 @@ kacl-cli add added 'We just added some new cool stuff' --modify
361
377
  kacl-cli add changed 'And changed things a bit' --modify
362
378
  ```
363
379
 
380
+ ## Stashing Unreleased Changes
381
+
382
+ ```
383
+ Usage: kacl-cli stash [OPTIONS]
384
+
385
+ Moves all unreleased changes from the changelog to the stash.
386
+
387
+ Options:
388
+ -m, --modify This option will modify the changelog file directly.
389
+ --help Show this message and exit.
390
+ ```
391
+
392
+ The `stash` command provides a powerful solution for managing unreleased changes and **preventing merge conflicts** in collaborative development environments. It moves all unreleased changes from `CHANGELOG.md` to a stash file, leaving an empty "Unreleased" section in the main changelog.
393
+
394
+ **Primary Use Case: Avoiding Merge Conflicts**
395
+
396
+ When multiple team members work on different branches simultaneously and manually edit `CHANGELOG.md`, merge conflicts are inevitable. The `stash` command solves this problem by:
397
+
398
+ 1. **Moving changes out of the main changelog** into separate stash files
399
+ 2. **Preventing conflicts** during merges since the main changelog remains minimal
400
+ 3. **Preserving all changes** in stash files that are later consolidated during release
401
+
402
+ **Usage**
403
+
404
+ ```bash
405
+ # Preview what would be stashed (dry-run)
406
+ kacl-cli stash
407
+
408
+ # Actually stash the changes and update CHANGELOG.md
409
+ kacl-cli stash --modify
410
+ ```
411
+
412
+ **How It Works**
413
+
414
+ When you run `kacl-cli stash --modify`:
415
+
416
+ 1. All changes from the "Unreleased" section are extracted
417
+ 2. Changes are moved to a stash file in `.kacl_stash/` directory (default location)
418
+ 3. Stash files are named based on:
419
+ - Git branch name (when inside a git repository): `feature-branch.md`
420
+ - Current date (when outside git): `20230101.md`
421
+ 4. The "Unreleased" section in `CHANGELOG.md` is cleared
422
+ 5. During `kacl-cli release`, all stashed changes are automatically merged into the release
423
+
424
+ **Example Workflow**
425
+
426
+ ```bash
427
+ # Developer edits CHANGELOG.md directly
428
+ echo "- Added new feature" >> CHANGELOG.md
429
+
430
+ # Before committing, stash the changes to avoid conflicts
431
+ kacl-cli stash --modify
432
+
433
+ # CHANGELOG.md now has an empty Unreleased section
434
+ # Changes are in .kacl_stash/your-branch.md
435
+
436
+ # When ready to release, all stashed changes are included
437
+ kacl-cli release patch --modify
438
+ ```
439
+
440
+ ### Pre-commit Hook Integration
441
+
442
+ For maximum convenience, you can automate the stashing process using a pre-commit hook. This allows developers to continue editing `CHANGELOG.md` manually while automatically moving changes to the stash before each commit.
443
+
444
+ **Setup**
445
+
446
+ Add the `kacl-stash` hook to your `.pre-commit-config.yaml`:
447
+
448
+ ```yaml
449
+ repos:
450
+ - repo: https://gitlab.com/schmieder.matthias/python-kacl
451
+ rev: 'v0.7.0' # Use the latest version
452
+ hooks:
453
+ - id: kacl-stash
454
+ ```
455
+
456
+ **How It Works**
457
+
458
+ 1. Developer manually edits `CHANGELOG.md` (traditional workflow)
459
+ 2. Pre-commit hook automatically runs `kacl-cli stash -m` before each commit
460
+ 3. Changes are moved to stash files
461
+ 4. Only the cleaned `CHANGELOG.md` is committed
462
+ 5. Multiple branches can work independently without conflicts
463
+ 6. During release, all stashed changes are consolidated
464
+
465
+ **Benefits**
466
+
467
+ - ✅ **No workflow changes**: Developers continue editing `CHANGELOG.md` normally
468
+ - ✅ **Zero merge conflicts**: Main changelog stays minimal and conflict-free
469
+ - ✅ **Automatic management**: Pre-commit hook handles everything
470
+ - ✅ **Branch isolation**: Each branch has its own stash file
471
+ - ✅ **Seamless integration**: Changes automatically included in releases
472
+
473
+ **Configuration**
474
+
475
+ You can customize the stash directory in `.kacl.yml`:
476
+
477
+ ```yaml
478
+ kacl:
479
+ stash:
480
+ dir: .kacl_stash # Custom stash directory
481
+ always: False # Set to True to always use stash for 'kacl add'
482
+ ```
483
+
484
+ **Note**: The `stash` command is complementary to [Changelog Fragments](#changelog-fragments). While fragments support the `kacl add` workflow, the `stash` command is specifically designed for teams that prefer manually editing `CHANGELOG.md` but want to avoid merge conflicts.
485
+
364
486
  ## Prepare a Changelog for a Release
365
487
 
366
488
  ```
@@ -589,3 +589,154 @@ def test_get_suppress_available_link(tmp_path, snapshot):
589
589
  with open(os.path.join(project_root_path, "output.md"), "w") as file:
590
590
  file.write(result.output)
591
591
  snapshot_directory(snapshot=snapshot, directory_path=project_root_path)
592
+
593
+
594
+ @freeze_time("2023-01-01")
595
+ def test_stash_no_pre_existing(tmp_path, snapshot):
596
+ """Test stashing when there is no pre-existing stash file."""
597
+ runner = CliRunner()
598
+ resources_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/")
599
+ changelog_file = os.path.join(resources_dir, "CHANGELOG_with_changes.md")
600
+
601
+ with runner.isolated_filesystem(temp_dir=tmp_path) as project_root_path:
602
+ shutil.copyfile(changelog_file, os.path.join(project_root_path, "CHANGELOG.md"))
603
+ result = runner.invoke(
604
+ cli,
605
+ [
606
+ "-f",
607
+ "CHANGELOG.md",
608
+ "stash",
609
+ "-m",
610
+ ],
611
+ catch_exceptions=False,
612
+ )
613
+ assert result.exit_code == 0, result.output
614
+ assert "Success: Unreleased changes have been moved to" in result.output
615
+ assert "Success: Changelog file CHANGELOG.md has been updated" in result.output
616
+
617
+ # Verify stash directory and file were created
618
+ stash_dir = os.path.join(project_root_path, ".kacl_stash")
619
+ assert os.path.exists(stash_dir)
620
+ assert len(os.listdir(stash_dir)) == 1
621
+
622
+ snapshot_directory(snapshot=snapshot, directory_path=project_root_path)
623
+
624
+
625
+ @freeze_time("2023-01-01")
626
+ def test_stash_with_pre_existing(tmp_path, snapshot):
627
+ """Test stashing when there is a pre-existing stash file with changes."""
628
+ runner = CliRunner()
629
+ resources_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/")
630
+ changelog_file = os.path.join(resources_dir, "CHANGELOG_with_changes.md")
631
+ stash_dir_source = os.path.join(resources_dir, "stash")
632
+
633
+ with runner.isolated_filesystem(temp_dir=tmp_path) as project_root_path:
634
+ shutil.copyfile(changelog_file, os.path.join(project_root_path, "CHANGELOG.md"))
635
+ # Copy pre-existing stash directory
636
+ shutil.copytree(
637
+ stash_dir_source, os.path.join(project_root_path, ".kacl_stash")
638
+ )
639
+
640
+ result = runner.invoke(
641
+ cli,
642
+ [
643
+ "-f",
644
+ "CHANGELOG.md",
645
+ "stash",
646
+ "-m",
647
+ ],
648
+ catch_exceptions=False,
649
+ )
650
+ assert result.exit_code == 0, result.output
651
+ assert "Success: Unreleased changes have been moved to" in result.output
652
+ assert "Success: Changelog file CHANGELOG.md has been updated" in result.output
653
+
654
+ snapshot_directory(snapshot=snapshot, directory_path=project_root_path)
655
+
656
+
657
+ @freeze_time("2023-01-01")
658
+ def test_stash_with_link(tmp_path, snapshot):
659
+ """Test stashing when there is a pre-existing stash file with changes."""
660
+ runner = CliRunner()
661
+ resources_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/")
662
+ changelog_file = os.path.join(resources_dir, "CHANGELOG_with_changes_and_link.md")
663
+ stash_dir_source = os.path.join(resources_dir, "stash")
664
+
665
+ with runner.isolated_filesystem(temp_dir=tmp_path) as project_root_path:
666
+ shutil.copyfile(changelog_file, os.path.join(project_root_path, "CHANGELOG.md"))
667
+ # Copy pre-existing stash directory
668
+ shutil.copytree(
669
+ stash_dir_source, os.path.join(project_root_path, ".kacl_stash")
670
+ )
671
+
672
+ result = runner.invoke(
673
+ cli,
674
+ [
675
+ "-c",
676
+ os.path.join(resources_dir, "extension-config.yml"),
677
+ "-f",
678
+ "CHANGELOG.md",
679
+ "stash",
680
+ "-m",
681
+ ],
682
+ catch_exceptions=False,
683
+ )
684
+ assert result.exit_code == 0, result.output
685
+ assert "Success: Unreleased changes have been moved to" in result.output
686
+ assert "Success: Changelog file CHANGELOG.md has been updated" in result.output
687
+
688
+ snapshot_directory(snapshot=snapshot, directory_path=project_root_path)
689
+
690
+
691
+ @freeze_time("2023-01-01")
692
+ def test_stash_no_changes(tmp_path, snapshot):
693
+ """Test stashing when there are no unreleased changes."""
694
+ runner = CliRunner()
695
+ resources_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/")
696
+ changelog_file = os.path.join(resources_dir, "CHANGELOG_without_changes.md")
697
+
698
+ with runner.isolated_filesystem(temp_dir=tmp_path) as project_root_path:
699
+ shutil.copyfile(changelog_file, os.path.join(project_root_path, "CHANGELOG.md"))
700
+ result = runner.invoke(
701
+ cli,
702
+ [
703
+ "-f",
704
+ "CHANGELOG.md",
705
+ "stash",
706
+ "-m",
707
+ ],
708
+ catch_exceptions=False,
709
+ )
710
+ assert result.exit_code == 0, result.output
711
+ assert "Info: No unreleased changes to stash" in result.output
712
+
713
+ # Verify no stash directory was created
714
+ stash_dir = os.path.join(project_root_path, ".kacl_stash")
715
+ assert not os.path.exists(stash_dir)
716
+
717
+
718
+ @freeze_time("2023-01-01")
719
+ def test_stash_without_modify(tmp_path, snapshot):
720
+ """Test stash command without --modify flag (dry run)."""
721
+ runner = CliRunner()
722
+ resources_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/")
723
+ changelog_file = os.path.join(resources_dir, "CHANGELOG_with_changes.md")
724
+
725
+ with runner.isolated_filesystem(temp_dir=tmp_path) as project_root_path:
726
+ shutil.copyfile(changelog_file, os.path.join(project_root_path, "CHANGELOG.md"))
727
+ result = runner.invoke(
728
+ cli,
729
+ [
730
+ "-f",
731
+ "CHANGELOG.md",
732
+ "stash",
733
+ ],
734
+ catch_exceptions=False,
735
+ )
736
+ assert result.exit_code == 0, result.output
737
+ assert "Success: Unreleased changes have been moved to" in result.output
738
+ # Should output the modified changelog content
739
+ assert "## Unreleased" in result.output
740
+ assert "## 1.0.0" in result.output
741
+
742
+ snapshot_directory(snapshot=snapshot, directory_path=project_root_path)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes