Skip to content

Instantly share code, notes, and snippets.

@dsoprea
Last active August 15, 2023 22:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsoprea/84f2fcc8b02b1bd590dc4437c0985323 to your computer and use it in GitHub Desktop.
Save dsoprea/84f2fcc8b02b1bd590dc4437c0985323 to your computer and use it in GitHub Desktop.
Generate a list of commit arguments from a submodules superproject for a Sentry release
#!/usr/bin/env python3
# MIT LICENSE
#
# Copyright 2023 Dustin Oprea
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the “Software”), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import re
import sys
import argparse
import subprocess
_DESCRIPTION = \
"Given files with the output of `git ls-tree <revision>` for an earlier " \
"and later commit, print a list of arguments that can be passed to " \
"`sentry-cli releases set-commits`."
# Example: git@github.com:outandbackoutdoor/workflow_application.git
_GIT_SSH_REPO_URI_RE = re.compile(r'^([^:]+):([^:]+)/(.+)(\.git)?$')
# Example: https://github.com/outandbackoutdoor/product_extractor.git
_GIT_HTTP_REPO_URI_RE = re.compile(r'^https://([^/]+)/([^/]+)/([^.]+)(\.git)?$')
def _get_args():
parser = \
argparse.ArgumentParser(
description=_DESCRIPTION)
parser.add_argument(
'previous_commits_filepath',
help="Previous release commits filepath")
parser.add_argument(
'current_commits_filepath',
help="Current release commits filepath")
args = parser.parse_args()
return args
def _parse_commits_file(f):
"""Parse output of `git ls-tree <revision>`.
Example:
160000 commit 606912a11094c3aa0952dd3fa97413d9d7a9bdb0 asset_management
160000 commit 344e43a6f1b9921209e21d758cb707317e1cb412 ebay_client
160000 commit 67c6032ea927744c8cab2ec9e074d6755356ed2e entity_cache_layer
160000 commit 36e261e9f30e3c0f93ccbd65d10cc6b49d213458 extraction_api_client
160000 commit c8e3fa056e348ad6df7a05331d1db4dda7d67ed8 extraction_api_website
160000 commit c77afd2217dbeb1bbe39b1638c20b46c79468cf3 free_text_matcher
160000 commit a3db58057dfbaa26970a2199fa7931655ac81ea6 infrastructure_storage_layer
160000 commit 8e9f231a916d59217343624e8344d23dc87a270f obo_aws_adapters
160000 commit f7dc93fe3146d2b9d344b8621a8e9f05acadda4b product_extractor
160000 commit 6f55d29e333e8c62ae6487d1e4a67b60f7429318 search_utilities
160000 commit f306c76c11d3c9f2858af0c31565ac268930b12b service_support
160000 commit 7e8ec9cb24a4a6819f3818804d5681a1bc339ab1 shopify_layer
160000 commit ab9f8774675760bc245d5f60b66a10dda8856255 shortcut_scripts
160000 commit 41e3a49a8093481e6adf66e10e043cd3e3c84134 workflow_api_client
"""
revisions = {}
for line in f:
mode, type_, revision, project = line.split()
if type_ != 'commit':
continue
revisions[project] = revision
return revisions
def _revision_ranges_gen(previous_revisions, current_revisions):
# Identify the projects in both sets
previous_projects_s = set(previous_revisions.keys())
current_projects_s = set(current_revisions.keys())
projects = list(previous_projects_s | current_projects_s)
for project in projects:
previous_revision = previous_revisions.get(project)
current_revision = current_revisions.get(project)
if current_revision == previous_revision:
continue
yield project, previous_revision, current_revision
def _get_repo_origin(project_path):
cmd = ['git', 'remote', 'get-url', 'origin']
origin_uri = \
subprocess.check_output(
cmd,
cwd=project_path,
universal_newlines=True)
origin_uri = origin_uri.strip()
if origin_uri.startswith('git@') is True:
# Example:
# git@github.com:outandbackoutdoor/workflow_application.git
m = _GIT_SSH_REPO_URI_RE.match(origin_uri)
assert \
m is not None, \
"Could not parse Git SSH URI: [{}]".format(
origin_uri)
user_and_host_part = m.group(1)
account_part = m.group(2)
project_part = m.group(3)
elif origin_uri.startswith('https://') is True:
# Example:
# git@github.com:outandbackoutdoor/workflow_application.git
m = _GIT_HTTP_REPO_URI_RE.match(origin_uri)
assert \
m is not None, \
"Could not parse Git HTTP URI: [{}]".format(
origin_uri)
user_and_host_part = m.group(1)
account_part = m.group(2)
project_part = m.group(3)
else:
raise \
Exception(
"Could not identify format of origin: [{}]".format(
origin_uri))
print("REMOTE: [{}] -> [{}] [{}] [{}]".format(
origin_uri, user_and_host_part, account_part, project_part),
file=sys.stderr)
return user_and_host_part, account_part, project_part
def _main():
args = _get_args()
with open(args.previous_commits_filepath) as f:
previous_revisions = _parse_commits_file(f)
with open(args.current_commits_filepath) as f:
current_revisions = _parse_commits_file(f)
revision_ranges = \
_revision_ranges_gen(
previous_revisions,
current_revisions)
current_path = os.getcwd()
for rel_project_path, previous_revision, current_revision in revision_ranges:
if previous_revision is None:
continue
elif current_revision is None:
continue
# Skip anything that hasn't changed
if current_revision == previous_revisions:
continue
project_path = os.path.join(current_path, rel_project_path)
_, account, project_name = _get_repo_origin(project_path)
suffix = '.git'
if project_name.endswith(suffix) is True:
len_ = len(suffix)
project_name = project_name[:-len_]
print("PROJECT: [{}] [{}] -> [{}] -> [{}] [{}]".format(
current_path, rel_project_path, project_path, account,
project_name), file=sys.stderr)
sys.stdout.write(
"--commit {account}/{project}@{from_revision}..{to_revision} ".format(
account=account, project=project_name, from_revision=previous_revision,
to_revision=current_revision))
print('')
_main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment