| #!/usr/bin/env python3 | |
| # | |
| # Makes a GNU-Style ChangeLog from a git repository | |
| import os | |
| import sys | |
| import subprocess | |
| import re | |
| meson_source_root = os.environ.get('MESON_SOURCE_ROOT') | |
| meson_dist_root = os.environ.get('MESON_DIST_ROOT') | |
| if meson_dist_root: | |
| output_fn = os.path.join(meson_dist_root, 'ChangeLog') | |
| else: | |
| output_fn = sys.stdout.fileno() | |
| # commit hash => release version tag string | |
| release_refs = {} | |
| # These are the pre-monorepo module beginnings | |
| changelog_starts = { | |
| 'gstreamer': '70521179a75db0c7230cc47c6d7f9d63cf73d351', | |
| 'gst-plugins-base': '68746a38d5e48e6f7c220663dcc2f175ff55cb3c', | |
| 'gst-plugins-good': '81f63142d65b62b0971c19ceb79956c49ffc2f06', | |
| 'gst-plugins-ugly': '7d7c3e478e32b7b66c44cc4442d571fbab534740', | |
| 'gst-plugins-bad': 'ea6821e2934fe8d356ea89d5610f0630b3446877', | |
| 'gst-libav': '3c440154c60d1ec0a54186f0fad4aebfd2ecc3ea', | |
| 'gst-rtsp-server': '5029c85a46a8c366c4bf272d503e22bbcd624ece', | |
| 'gst-editing-services': 'ee8bf88ebf131cf7c7161356540efc20bf411e14', | |
| 'gst-python': 'b3e564eff577e2f577d795051bbcca85d47c89dc', | |
| 'gstreamer-vaapi': 'c89e9afc5d43837c498a55f8f13ddf235442b83b', | |
| 'gst-devtools': 'da962d096af9460502843e41b7d25fdece7ff1c2', | |
| 'gstreamer-sharp': 'b94528f8e7979df49fedf137dfa228d8fe475e1b', | |
| } | |
| def print_help(): | |
| print('', file=sys.stderr) | |
| print('gen-changelog: generate GNU-style changelog from git history', | |
| file=sys.stderr) | |
| print('', file=sys.stderr) | |
| print('Usage: {} [OPTIONS] GSTREAMER-MODULE [START-TAG] [HEAD-TAG]'.format( | |
| sys.argv[0]), file=sys.stderr) | |
| print('', file=sys.stderr) | |
| sys.exit(1) | |
| if len(sys.argv) < 2 or len(sys.argv) > 4 or '--help' in sys.argv: | |
| print_help() | |
| module = sys.argv[1] | |
| if len(sys.argv) > 2: | |
| start_tag = sys.argv[2] | |
| else: | |
| start_tag = None | |
| if len(sys.argv) > 3: | |
| head_tag = sys.argv[3] | |
| else: | |
| head_tag = None | |
| if module not in changelog_starts: | |
| print(f'Unknown module {module}', file=sys.stderr) | |
| print_help() | |
| def process_commit(lines, files, subtree_path=None): | |
| # DATE NAME | |
| # BLANK LINE | |
| # Subject | |
| # BLANK LINE | |
| # ... | |
| # FILES | |
| fileincommit = False | |
| lines = [x.strip() for x in lines if x.strip() | |
| and not x.startswith('git-svn-id')] | |
| files = [x.strip() for x in files if x.strip()] | |
| for line in lines: | |
| if line.startswith('* ') and ':' in line: | |
| fileincommit = True | |
| break | |
| top_line = lines[0] | |
| print(top_line.strip()) | |
| print() | |
| if not fileincommit: | |
| for f in files: | |
| if subtree_path and f.startswith(subtree_path): | |
| # requires Python 3.9 | |
| print('\t* %s:' % f.removeprefix(subtree_path)) | |
| else: | |
| print('\t* %s:' % f) | |
| for line in lines[1:]: | |
| print('\t ', line) | |
| print() | |
| def output_commits(module, start_tag, end_tag, subtree_path=None): | |
| # retrieve commit date for start tag so we can filter the log for commits | |
| # after that date. That way we don't include commits from merged-in | |
| # plugin-move branches that go back to the beginning of time. | |
| start_date = get_commit_date_for_ref(start_tag) | |
| cmd = ['git', 'log', | |
| '--pretty=format:--START-COMMIT--%H%n%ai %an <%ae>%n%n%s%n%b%n--END-COMMIT--', | |
| '--date=short', | |
| '--name-only', | |
| f'--since={start_date}', | |
| f'{start_tag}..{end_tag}', | |
| ] | |
| if subtree_path: | |
| cmd += ['--', '.'] | |
| p = subprocess.Popen(args=cmd, shell=False, | |
| stdout=subprocess.PIPE, cwd=meson_source_root) | |
| buf = [] | |
| files = [] | |
| filemode = False | |
| for lin in [x.decode('utf8', errors='replace') for x in p.stdout.readlines()]: | |
| if lin.startswith("--START-COMMIT--"): | |
| commit_hash = lin[16:].strip() | |
| if buf != []: | |
| process_commit(buf, files, subtree_path) | |
| if commit_hash in release_refs: | |
| version_str = release_refs[commit_hash] | |
| print(f'=== release {version_str} ===\n') | |
| buf = [] | |
| files = [] | |
| filemode = False | |
| elif lin.startswith("--END-COMMIT--"): | |
| filemode = True | |
| elif filemode is True: | |
| files.append(lin) | |
| else: | |
| buf.append(lin) | |
| if buf != []: | |
| process_commit(buf, files, subtree_path) | |
| def get_commit_date_for_ref(ref): | |
| cmd = ['git', 'log', '--pretty=format:%cI', '-1', ref] | |
| r = subprocess.run(cmd, capture_output=True, text=True, | |
| check=True, cwd=meson_source_root) | |
| commit_date = r.stdout.strip() | |
| return commit_date | |
| def populate_release_tags_for_premonorepo_module(module_tag_prefix): | |
| if module_tag_prefix != '': | |
| cmd = ['git', 'tag', '--list', f'{module_tag_prefix}*'] | |
| else: | |
| cmd = ['git', 'tag', '--list', '1.*', 'RELEASE-*'] | |
| p = subprocess.Popen(args=cmd, shell=False, | |
| stdout=subprocess.PIPE, cwd=meson_source_root) | |
| for line in [x.decode('utf8') for x in p.stdout.readlines()]: | |
| git_tag = line.strip() | |
| version_str = git_tag.removeprefix(module_tag_prefix).removeprefix('RELEASE-').split('-')[0].replace('_', '.') | |
| # might have been populated with post-monorepo tags already for gstreamer core | |
| if version_str not in release_refs: | |
| # find last commit before tag in module subdirectory | |
| cmd = ['git', 'log', '--pretty=format:%H', '-1', git_tag] | |
| r = subprocess.run(cmd, capture_output=True, | |
| text=True, check=True, cwd=meson_source_root) | |
| commit_hash = r.stdout.strip() | |
| release_refs[commit_hash] = version_str | |
| # print(f'{git_tag} => {version_str} => {commit_hash}') | |
| def populate_release_tags_for_monorepo_subproject(): | |
| cmd = ['git', 'tag', '--list', '1.*'] | |
| p = subprocess.Popen(args=cmd, shell=False, | |
| stdout=subprocess.PIPE, cwd=meson_source_root) | |
| for line in [x.decode('utf8') for x in p.stdout.readlines()]: | |
| version_str = line.strip() | |
| version_arr = version_str.split('.') | |
| major = int(version_arr[0]) | |
| minor = int(version_arr[1]) | |
| micro = int(version_arr[2]) | |
| # ignore pre-monorepo versions | |
| if major < 1: | |
| continue | |
| if major == 1 and minor < 19: | |
| continue | |
| if major == 1 and minor == 19 and micro < 2: | |
| continue | |
| # find last commit before tag in module subdirectory | |
| cmd = ['git', 'log', '--pretty=format:%H', | |
| '-1', version_str, '--', '.'] | |
| r = subprocess.run(cmd, capture_output=True, text=True, | |
| check=True, cwd=meson_source_root) | |
| commit_hash = r.stdout.strip() | |
| release_refs[commit_hash] = version_str | |
| if __name__ == '__main__': | |
| module_tag_prefix = '' if module == 'gstreamer' else f'{module}-' | |
| populate_release_tags_for_monorepo_subproject() | |
| with open(output_fn, 'w') as f: | |
| sys.stdout = f | |
| # Force writing of head tag | |
| if head_tag and head_tag not in release_refs.values(): | |
| print(f'=== release {head_tag} ===\n') | |
| # Output all commits from start_tag onwards, otherwise output full history. | |
| # (We assume the start_tag is after the monorepo merge if it's specified.) | |
| if start_tag and start_tag != 'start': | |
| output_commits(module, start_tag, 'HEAD', f'subprojects/{module}/') | |
| else: | |
| # First output all post-monorepo commits or commits from start_tag if specified | |
| output_commits(module, 'monorepo-start', | |
| 'HEAD', f'subprojects/{module}/') | |
| populate_release_tags_for_premonorepo_module(module_tag_prefix) | |
| # Next output all pre-monorepo commits (modules have their own root) | |
| if not start_tag: | |
| module_start = f'{module_tag_prefix}1.0.0' | |
| elif start_tag == 'start': | |
| module_start = changelog_starts[module] | |
| else: | |
| module_start = f'{module_tag_prefix}{start_tag}' | |
| output_commits(module, module_start, | |
| f'{module_tag_prefix}1.19.2', None) | |
| # Write start tag at end for clarity | |
| if not start_tag: | |
| print(f'=== release 1.0.0 ===\n') | |
| elif start_tag != 'start': | |
| print(f'=== release {start_tag} ===\n') |