1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
#!/usr/bin/env python3
import argparse
import os
import os.path
import re
import subprocess
import sys
def run(*args, check=True):
return subprocess.run(args, capture_output=True, check=check, universal_newlines=True)
def get_submodules():
"""
Return list of submodules for current repo, sorted by name.
Each item looks like:
{
'name': 'aws-c-common',
'path': 'crt/aws-c-common',
'url': 'https://github.com/awslabs/aws-c-common.git',
}
"""
if not os.path.exists('.gitmodules'):
sys.exit(f'No .gitmodules found in {os.getcwd()}')
submodules = []
start_pattern = re.compile(r'\[submodule')
path_pattern = re.compile(r'\s+path = (\S+)')
url_pattern = re.compile(r'\s+url = (\S+)')
current = None
with open('.gitmodules', 'r') as f:
for line in f.readlines():
m = start_pattern.match(line)
if m:
current = {}
submodules.append(current)
continue
m = path_pattern.match(line)
if m:
current['path'] = m.group(1)
current['name'] = os.path.basename(current['path'])
continue
m = url_pattern.match(line)
if m:
current['url'] = m.group(1)
continue
return sorted(submodules, key=lambda x: x['name'])
def get_release_tags():
"""
Return list of release tags for current repo, sorted high to low.
Each item looks like:
{
'commit': 'e18f041a0c8d17189f2eae2a32f16e0a7a3f0f1c',
'version': 'v0.5.18'
'num_tuple': (0,5,18),
}
"""
git_output = run('git', 'ls-remote', '--tags').stdout
tags = []
for line in git_output.splitlines():
# line looks like: "e18f041a0c8d17189f2eae2a32f16e0a7a3f0f1c refs/tags/v0.5.18"
match = re.match(
r'([a-f0-9]+)\s+refs/tags/(v([0-9]+)\.([0-9]+)\.([0-9]+))$', line)
if not match:
# skip malformed release tags
continue
tags.append({
'commit': match.group(1),
'version': match.group(2),
'num_tuple': (int(match.group(3)), int(match.group(4)), int(match.group(5))),
})
# sort highest version first
return sorted(tags, reverse=True, key=lambda tag: tag['num_tuple'])
def get_current_commit():
git_output = run('git', 'rev-parse', 'HEAD').stdout
return git_output.splitlines()[0]
def is_ancestor(ancestor, descendant):
"""Return whether first commit is an ancestor to the second'"""
result = run('git', 'merge-base', '--is-ancestor',
ancestor, descendant, check=False)
return result.returncode == 0
def get_tag_for_commit(tags, commit):
for tag in tags:
if tag['commit'] == commit:
return tag
return None
def main():
parser = argparse.ArgumentParser(
description="Update submodules to latest tags")
parser.add_argument('ignore', nargs='*', help="submodules to ignore")
parser.add_argument('--dry-run', action='store_true',
help="print without actually updating")
args = parser.parse_args()
root_path = os.getcwd()
submodules = get_submodules()
name_pad = max([len(x['name']) for x in submodules])
for submodule in submodules:
name = submodule['name']
os.chdir(os.path.join(root_path, submodule['path']))
tags = get_release_tags()
current_commit = get_current_commit()
current_tag = get_tag_for_commit(tags, current_commit)
sync_from = current_tag['version'] if current_tag else current_commit
if name in args.ignore:
print(f"{name:<{name_pad}} {sync_from} (ignored)")
continue
latest_tag = tags[0]
sync_to = latest_tag['version']
# The only time we don't want to sync to the latest release is:
# The submodule is at some commit beyond the latest release,
# and the CRT team doesn't control this repo so can't just cut a new release
if sync_from != sync_to and current_tag is None:
if name in ['aws-lc', 's2n', 's2n-tls']:
# must fetch tags before we can check their ancestry
run('git', 'fetch', '--tags', '--prune', '--prune-tags', '--force')
if not is_ancestor(ancestor=current_commit, descendant=sync_to):
sync_to = sync_from
if sync_from == sync_to:
print(f"{name:<{name_pad}} {sync_from} ✓")
else:
print(f"{name:<{name_pad}} {sync_from} -> {sync_to}")
if not args.dry_run:
run('git', 'fetch', '--tags', '--prune', '--prune-tags', '--force')
run('git', 'checkout', sync_to)
run('git', 'submodule', 'update')
if __name__ == '__main__':
main()
|