meson: Generate ChangeLog files for release tarballs on dist
authorTim-Philipp Müller <tim@centricular.com>
Mon, 7 Nov 2022 00:10:39 +0000 (00:10 +0000)
committerTim-Philipp Müller <tim@centricular.com>
Sun, 4 Dec 2022 18:16:25 +0000 (18:16 +0000)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3521>

31 files changed:
meson.build
scripts/gen-changelog.py [new file with mode: 0755]
scripts/meson.build [new file with mode: 0644]
subprojects/gst-devtools/meson.build
subprojects/gst-devtools/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-docs/meson.build
subprojects/gst-docs/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-editing-services/meson.build
subprojects/gst-editing-services/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-libav/meson.build
subprojects/gst-libav/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-omx/meson.build
subprojects/gst-omx/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-plugins-bad/meson.build
subprojects/gst-plugins-bad/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-plugins-base/meson.build
subprojects/gst-plugins-base/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-plugins-good/meson.build
subprojects/gst-plugins-good/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-plugins-ugly/meson.build
subprojects/gst-plugins-ugly/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-python/meson.build
subprojects/gst-python/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gst-rtsp-server/meson.build
subprojects/gst-rtsp-server/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gstreamer-sharp/meson.build
subprojects/gstreamer-sharp/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gstreamer-vaapi/meson.build
subprojects/gstreamer-vaapi/scripts/gen-changelog.py [new file with mode: 0755]
subprojects/gstreamer/meson.build
subprojects/gstreamer/scripts/gen-changelog.py [new file with mode: 0755]

index 88b7457..ee9d104 100644 (file)
@@ -496,6 +496,8 @@ if orc_subproject.found() and orc_update_targets.length() > 0
   alias_target('update-orc-dist', orc_update_targets)
 endif
 
+subdir('scripts')
+
 dotnet_format = find_program('dotnet-format', required: false)
 if dotnet_format.found()
     run_target('csharp_format_check',
diff --git a/scripts/gen-changelog.py b/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
diff --git a/scripts/meson.build b/scripts/meson.build
new file mode 100644 (file)
index 0000000..0554b17
--- /dev/null
@@ -0,0 +1,53 @@
+fs = import('fs')
+
+# scripts checks
+release_modules = [
+  'gstreamer',
+  'gst-plugins-base',
+  'gst-plugins-good',
+  'gst-plugins-ugly',
+  'gst-plugins-bad',
+  'gst-libav',
+  'gst-rtsp-server',
+  'gst-editing-services',
+  'gst-devtools',
+  'gst-python',
+  'gstreamer-vaapi',
+  'gst-omx',
+  'gst-docs',
+  'gstreamer-sharp',
+]
+
+# Make sure the files are all identical to avoid divergence
+gen_cl_hash = fs.hash(files('gen-changelog.py'), 'md5')
+
+out_of_sync_list = []
+
+foreach m : release_modules
+  module_gen_cl_hash = fs.hash(f'../subprojects/@m@/scripts/gen-changelog.py', 'md5')
+
+  if module_gen_cl_hash != gen_cl_hash
+    out_of_sync_list += [f'subprojects/@m@/scripts/gen-changelog.py']
+  endif
+endforeach
+
+if out_of_sync_list.length() > 0
+  module_list = ' '.join(release_modules)
+  out_of_sync_msg = '\n          '.join(out_of_sync_list)
+  error('''
+
+    ==============================================================================================================
+
+        The following subproject scripts are out of sync with scripts/gen-changelog.py:
+
+          @0@
+
+        Run
+
+          for m in @1@; do cp scripts/gen-changelog.py subprojects/$m/scripts/gen-changelog.py; done 
+
+        from the top-level git source directory to sync them up.
+
+    ==============================================================================================================
+    '''.format(out_of_sync_msg, module_list))
+endif
index d7752da..9a03dbf 100644 (file)
@@ -174,6 +174,8 @@ if not get_option('debug_viewer').disabled()
 endif
 subdir('docs')
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-devtools/scripts/gen-changelog.py b/subprojects/gst-devtools/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 69666b3..530e203 100644 (file)
@@ -144,3 +144,5 @@ run_target('release',
             meson.current_build_dir()],
   depends: [gstreamer_doc]
 )
+
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
diff --git a/subprojects/gst-docs/scripts/gen-changelog.py b/subprojects/gst-docs/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 86393dd..1d8c12c 100644 (file)
@@ -309,6 +309,8 @@ endif
 
 configure_file(output: 'config.h', configuration: cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-editing-services/scripts/gen-changelog.py b/subprojects/gst-editing-services/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index c53c699..a26df50 100644 (file)
@@ -236,6 +236,8 @@ endif
 
 configure_file(output: 'config.h', configuration: cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 gst_plugins = []
 foreach plugin: plugins
   pkgconfig.generate(plugin, install_dir: plugins_pkgconfig_install_dir)
diff --git a/subprojects/gst-libav/scripts/gen-changelog.py b/subprojects/gst-libav/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index bcfa1fd..c68c650 100644 (file)
@@ -425,3 +425,5 @@ if gst_version_nano == 0
 endif
 
 configure_file(output: 'config.h', configuration: cdata)
+
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
diff --git a/subprojects/gst-omx/scripts/gen-changelog.py b/subprojects/gst-omx/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index ae4a0ba..8a8ff56 100644 (file)
@@ -600,6 +600,8 @@ endif
 
 configure_file(output : 'config.h', configuration : cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 subdir('docs')
 
 plugin_names = []
diff --git a/subprojects/gst-plugins-bad/scripts/gen-changelog.py b/subprojects/gst-plugins-bad/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 83a341c..e42c202 100644 (file)
@@ -550,6 +550,8 @@ endif
 # Use core_conf after all subdirs have set values
 configure_file(output : 'config.h', configuration : core_conf)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-plugins-base/scripts/gen-changelog.py b/subprojects/gst-plugins-base/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 942b38e..6a7cc77 100644 (file)
@@ -520,6 +520,8 @@ endif
 
 configure_file(output : 'config.h', configuration : cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-plugins-good/scripts/gen-changelog.py b/subprojects/gst-plugins-good/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index b97465d..2d035b5 100644 (file)
@@ -329,6 +329,8 @@ endif
 
 configure_file(output : 'config.h', configuration : cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-plugins-ugly/scripts/gen-changelog.py b/subprojects/gst-plugins-ugly/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 209b2d7..dd5706c 100644 (file)
@@ -87,6 +87,8 @@ cdata.set('PYTHON_VERSION', '"@0@"'.format(python_dep.version()))
 configure_file(output : 'config.h', configuration : cdata)
 configinc = include_directories('.')
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 pkgconfig = import('pkgconfig')
 plugins_install_dir = join_paths(libdir, 'gstreamer-1.0')
 plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig')
diff --git a/subprojects/gst-python/scripts/gen-changelog.py b/subprojects/gst-python/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 5bc75d6..b9037a6 100644 (file)
@@ -219,6 +219,8 @@ endif
 
 configure_file(output: 'config.h', configuration: cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gst-rtsp-server/scripts/gen-changelog.py b/subprojects/gst-rtsp-server/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 6c286ce..adff8ed 100644 (file)
@@ -183,3 +183,5 @@ if bindinator.get_variable('found')
 else
     warning('Bindinator not usable as some required dependencies are not avalaible.')
 endif
+
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
diff --git a/subprojects/gstreamer-sharp/scripts/gen-changelog.py b/subprojects/gstreamer-sharp/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 21cb8bc..e7349c3 100644 (file)
@@ -237,6 +237,8 @@ endif
 
 configure_file(output: 'config.h', configuration: cdata)
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 pkgconfig = import('pkgconfig')
 plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig')
 
diff --git a/subprojects/gstreamer-vaapi/scripts/gen-changelog.py b/subprojects/gstreamer-vaapi/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')
index 33ab344..5c17c31 100644 (file)
@@ -657,6 +657,8 @@ endif
 configure_file(output : 'config.h', configuration : cdata)
 install_data('gst-element-check-1.0.m4', install_dir : join_paths(get_option('datadir'), 'aclocal'))
 
+meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.20.0', meson.project_version())
+
 plugin_names = []
 gst_plugins = []
 foreach plugin: plugins
diff --git a/subprojects/gstreamer/scripts/gen-changelog.py b/subprojects/gstreamer/scripts/gen-changelog.py
new file mode 100755 (executable)
index 0000000..3924e6e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
+    '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')