Skip to content

Commit 795b17a

Browse files
authored
Merge pull request #24 from sarnold/changelogs
Add changelog support
2 parents d88ed66 + 3decd9f commit 795b17a

File tree

10 files changed

+178
-44
lines changed

10 files changed

+178
-44
lines changed

.github/workflows/sphinx.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ jobs:
2626
2727
- name: Build docs
2828
run: |
29-
tox -e docs-lint
30-
tox -e docs
29+
tox -e ldocs,docs
3130
3231
- uses: actions/upload-artifact@v4
3332
with:

CHANGELOG.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@ Changelog
22
=========
33

44

5+
0.6.2 (2024-06-01)
6+
------------------
7+
8+
New
9+
~~~
10+
- Add changelog feature for release workflows. [Stephen L Arnold]
11+
12+
* uses ``gitchangelog`` to generate either full changelog.rst or
13+
changelog diff from given base tag
14+
15+
Changes
16+
~~~~~~~
17+
- Add config option to set changelog file extension. [Stephen L Arnold]
18+
19+
* allowed options are 'rst' or 'md' depending on the gitchangelog
20+
configuration setting for ``output_engine``
21+
22+
523
0.6.1 (2024-05-22)
624
------------------
725

README.rst

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ files in yaml instead.
1616
.. _tox: https://github.com/tox-dev/tox
1717
.. _pip: https://packaging.python.org/en/latest/key_projects/#pip
1818

19+
The latest new/expanded workflow features now include:
20+
21+
* **tagging support** - tag a set of enabled repositories via config file or
22+
command line
23+
* **changelog support** in ``rSt`` or ``md`` - generate changelog documents
24+
for enabled repositories
25+
26+
See the optional feature keys in Usage_ for more info.
1927

2028
Repolite is tested on the 3 primary GH runner platforms, so as long as you
2129
have a new-ish Python and a ``git`` binary it should run on your platform
@@ -155,32 +163,31 @@ The current version supports minimal command options and there are no
155163
required arguments::
156164

157165
(dev) user@host repolite (main) $ repolite -h
158-
usage: repolite [-h] [--version] [-v] [-q] [-D] [-S] [-i] [-u] [-s] [-a] [-l]
166+
usage: repolite [-h] [--version] [-v] [-q] [-D] [-S] [-i] [-u] [-s] [-a] [-g] [-l]
159167
[TAG]
160168

161169
Manage local (git) dependencies (default: clone and checkout)
162170

163171
positional arguments:
164-
TAG Tag string override for all repositories (apply with -a)
165-
(default: None)
172+
TAG Optional tag string override (apply with -a) (default: None)
166173

167174
options:
168175
-h, --help show this help message and exit
169176
--version show program's version number and exit
170177
-v, --verbose Display more processing info (default: False)
171178
-q, --quiet Suppress output from git command (default: False)
172-
-D, --dump-config Dump default configuration file to stdout (default:
173-
False)
174-
-S, --save-config Save active config to default filename (.ymltoxml.yml)
175-
and exit (default: False)
176-
-i, --install Install existing repositories (python only) (default:
177-
False)
178-
-u, --update Update existing repositories (default: False)
179+
-D, --dump-config Dump default configuration file to stdout (default: False)
180+
-S, --save-config Save active config to default filename (.ymltoxml.yml) and exit
181+
(default: False)
182+
-i, --install Install enabled repositories (python only) (default: False)
183+
-u, --update Update existing/enabled repositories (default: False)
179184
-s, --show Display current repository state (default: False)
180-
-a, --apply-tag Apply the given tag (see TAG arg) or use one from config
181-
file (default: False)
182-
-l, --lock-config Lock active configuration in new config file and checkout
183-
hashes (default: False)
185+
-a, --apply-tag Apply the given tag (see TAG arg) or use one from config file
186+
(default: False)
187+
-g, --changelog Run gitchangelog in enabled repositories, create files in top_dir
188+
(default: False)
189+
-l, --lock-config Lock active configuration in new config file and checkout hashes
190+
(default: False)
184191

185192
Configuration settings
186193
----------------------
@@ -207,6 +214,9 @@ Configuration keys for optional extra features/behavior:
207214
(requires ``git-lfs`` to be installed first)
208215
:repo_init_submodules: if true, initialize/update git submodules in that repository
209216
:repo_install: if true, try to install the repo with pip_
217+
:repo_changelog_ext: changelog file extension (default: ``rst``)
218+
:repo_changelog_base: base version to use for changelog data
219+
:repo_gen_changes: if true, generate changelog file in ``top_dir``
210220

211221
Configuration keys that change repository state:
212222

@@ -219,6 +229,8 @@ Configuration keys that change repository state:
219229

220230
Notes:
221231

232+
* if your gitchangelog_ config uses Markdown, set ``repo_changelog_ext`` to
233+
``md`` instead of ``rst``
222234
* when tagging, tag from commandline is only used when config value is ``null``
223235
* when tagging, ``create_tag_annotated`` and ``create_tag_signed`` are
224236
mutually exclusive, so only enable one of them
@@ -236,6 +248,7 @@ Notes:
236248
* you may want to add your ``top_dir`` path or default local config file
237249
patterns to your ``.gitignore`` file
238250

251+
.. _gitchangelog: https://github.com/sarnold/gitchangelog
239252

240253
Dev tools
241254
=========

requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
munch
1+
gitchangelog @ https://github.com/sarnold/gitchangelog/releases/download/3.2.0/gitchangelog-3.2.0-py3-none-any.whl
2+
importlib-metadata; python_version < '3.8'
3+
importlib-resources; python_version < '3.10'
4+
munch[yaml]
25
PyYAML

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ setup_requires =
2424
setuptools_scm[toml]
2525

2626
install_requires =
27+
gitchangelog @ https://github.com/sarnold/gitchangelog/releases/download/3.2.0/gitchangelog-3.2.0-py3-none-any.whl
2728
importlib-metadata; python_version < '3.8'
2829
importlib-resources; python_version < '3.10'
2930
munch[yaml]

src/repolite/data/example.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ repos:
1717
repo_create_tag_signed: false
1818
repo_push_new_tags: false
1919
repo_signing_key: null
20+
repo_changelog_ext: rst
21+
repo_changelog_base: null
22+
repo_gen_changes: false
2023
repo_use_rebase: false
2124
repo_has_lfs_files: false
2225
repo_init_submodules: false
@@ -36,6 +39,9 @@ repos:
3639
repo_create_tag_signed: false
3740
repo_push_new_tags: false
3841
repo_signing_key: null
42+
repo_changelog_ext: rst
43+
repo_changelog_base: null
44+
repo_gen_changes: false
3945
repo_use_rebase: false
4046
repo_has_lfs_files: false
4147
repo_init_submodules: false

src/repolite/repolite.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def check_repo_state(ucfg):
9696
os.chdir(top_dir)
9797
except OSError as exc:
9898
logging.exception("Could not change to repo directory: %s", exc)
99+
sys.exit(1)
99100

100101
sorted_dir_list = sorted([x for x in top_dir.iterdir() if x.is_dir()])
101102
repo_name_list = []
@@ -113,6 +114,33 @@ def check_repo_state(ucfg):
113114
return is_state_valid
114115

115116

117+
def generate_change_data(mrepo, base_tag, dir_name, cur_tag):
118+
"""
119+
Generate a changelog (full or diff) for the given repository and drop
120+
it in the configured top_dir directory. Checks repo config for base
121+
tag to decide full vs diff.
122+
123+
:param mrepo: a Munch repo obj with Munch repos item
124+
:type mrepo: Munch obj
125+
:param base_tag: starting tag for change diff
126+
:type base_tag: str or None
127+
:dir_name: directory name for output (top_dir)
128+
:type dir_name: str
129+
:param cur_tag: ending tag for change diff (tag on current commit)
130+
:type cur_tag: str
131+
"""
132+
base_cmd_str = 'gitchangelog'
133+
output_file = f'{dir_name}/{mrepo.name}-CHANGELOG.{mrepo.item.repo_changelog_ext}'
134+
135+
if base_tag:
136+
base_cmd_str = base_cmd_str + f' {base_tag}..{cur_tag}'
137+
138+
logging.debug('Running gitchangelog cmd: %s', base_cmd_str)
139+
chg_data = sp.check_output(split(base_cmd_str))
140+
Path(output_file).write_bytes(chg_data)
141+
logging.info('ChangeLog file: %s', Path(output_file))
142+
143+
116144
def install_with_pip(pip_name, quiet=False):
117145
"""
118146
Install a python repository via pip; this should be done in a local
@@ -403,6 +431,43 @@ def process_git_repos(flags, repos, pull, quiet):
403431
os.chdir(work_dir)
404432

405433

434+
def process_repo_changes(ucfg):
435+
"""
436+
Generate a changelog for any repos with the ``repo_gen_changes`` flag
437+
set. Note we do not check repo state here, we just process each valid
438+
repo entry.
439+
440+
:param ucfg: Munch configuration object extracted from config file
441+
:type ucfg: Munch cfgobj
442+
:param quiet: pass the ``quiet`` cmd line flag to install cmd
443+
"""
444+
work_dir, top_dir = resolve_top_dir(ucfg.top_dir)
445+
valid_repo_state = check_repo_state(ucfg)
446+
if not valid_repo_state:
447+
raise DirectoryTypeError('Inconsistent directories; try running repolite first?')
448+
449+
git_get_tags = 'git tag --sort=taggerdate'
450+
git_check_tag = 'git tag --points-at'
451+
452+
for item in [x for x in ucfg.repos if x.repo_enable and x.repo_gen_changes]:
453+
repo = Munch()
454+
repo.name = item.repo_name
455+
repo.item = Munch(item)
456+
git_dir = item.repo_alias if item.repo_alias else item.repo_name
457+
os.chdir(git_dir)
458+
459+
all_tags = sp.check_output(split(git_get_tags), text=True).splitlines()
460+
logging.debug('%s tag list: %s', item.repo_name, all_tags)
461+
commit_tags = sp.check_output(split(git_check_tag), text=True).splitlines()
462+
logging.debug('commit tag(s): %s', commit_tags)
463+
last_tag = all_tags[-1] if all_tags and all_tags[-1] in commit_tags else ''
464+
465+
generate_change_data(repo, item.repo_changelog_base, top_dir, last_tag)
466+
467+
os.chdir(top_dir)
468+
os.chdir(work_dir)
469+
470+
406471
def process_repo_install(ucfg, quiet):
407472
"""
408473
Install any repos with the ``repo_install`` flag set. Note we do not
@@ -499,13 +564,13 @@ def main(argv=None): # pragma: no cover
499564
"-i",
500565
"--install",
501566
action="store_true",
502-
help="Install existing repositories (python only)",
567+
help="Install enabled repositories (python only)",
503568
)
504569
parser.add_argument(
505570
"-u",
506571
"--update",
507572
action="store_true",
508-
help="Update existing repositories",
573+
help="Update existing/enabled repositories",
509574
)
510575
parser.add_argument(
511576
"-s",
@@ -520,6 +585,12 @@ def main(argv=None): # pragma: no cover
520585
dest="apply",
521586
help="Apply the given tag (see TAG arg) or use one from config file",
522587
)
588+
parser.add_argument(
589+
"-g",
590+
"--changelog",
591+
action="store_true",
592+
help="Run gitchangelog in enabled repositories, create files in top_dir",
593+
)
523594
parser.add_argument(
524595
'-l',
525596
'--lock-config',
@@ -531,7 +602,7 @@ def main(argv=None): # pragma: no cover
531602
"tag",
532603
metavar="TAG",
533604
nargs='?',
534-
help="Tag string override for all repositories (apply with -a)",
605+
help="Optional tag string override (apply with -a)",
535606
type=str,
536607
)
537608

@@ -560,6 +631,9 @@ def main(argv=None): # pragma: no cover
560631
sys.exit(0)
561632

562633
try:
634+
if opts.changelog:
635+
process_repo_changes(cfg)
636+
sys.exit(0)
563637
if opts.install:
564638
process_repo_install(cfg, opts.quiet)
565639
sys.exit(0)

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
'git_dummy',
1616
'--allow-nested',
1717
'--constant-sha',
18+
'--commits=10',
1819
'--branches=5',
1920
'--diverge-at=2',
2021
'--git-dir',

tests/test_repolite.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
repo_create_tag_signed: false
3131
repo_push_new_tags: false
3232
repo_signing_key: null
33+
repo_changelog_ext: rst
34+
repo_changelog_base: null
35+
repo_gen_changes: true
3336
repo_use_rebase: false
3437
repo_has_lfs_files: false
3538
repo_init_submodules: false
@@ -49,6 +52,9 @@
4952
repo_create_tag_signed: false
5053
repo_push_new_tags: false
5154
repo_signing_key: null
55+
repo_changelog_ext: rst
56+
repo_changelog_base: null
57+
repo_gen_changes: true
5258
repo_use_rebase: false
5359
repo_has_lfs_files: false
5460
repo_init_submodules: false
@@ -201,3 +207,27 @@ def test_repolite_locked_cfg(tmp_path, script_loc, tmpdir_session):
201207
update = False
202208
quiet = False
203209
create_locked_cfg(cfg, pfile, quiet, tpath)
210+
211+
212+
def test_repolite_changes(script_loc, tmpdir_session):
213+
"""
214+
Show the repos in the test config.
215+
"""
216+
cfg.top_dir = str(tmpdir_session / 'ext')
217+
218+
for repo in cfg.repos:
219+
pure_path = PurePath(script_loc, 'testdata', repo.repo_url)
220+
# print(f'PurePath is {pure_path}')
221+
full_path = Path(pure_path).resolve()
222+
# print(f'Type is {type(full_path)}')
223+
# print(f'Path is {full_path}')
224+
print(f'Path string is {full_path.__str__()}')
225+
repo.repo_url = full_path.__str__()
226+
# print(f'Munch type is {type(repo.repo_url)}')
227+
# print(f'Munch value is {repo.repo_url}')
228+
229+
process_repo_changes(cfg)
230+
changelogs = list(Path(cfg.top_dir).glob('**/*.rst'))
231+
for fpath in changelogs:
232+
print(fpath)
233+
assert 'CHANGELOG.rst' in str(fpath)

0 commit comments

Comments
 (0)