Skip to content

fix: copy patches folder to web docker image before pnpm install (#14… #28

fix: copy patches folder to web docker image before pnpm install (#14…

fix: copy patches folder to web docker image before pnpm install (#14… #28

Workflow file for this run

name: Add versioning tag to commit
concurrency: commit
permissions:
contents: write
on:
push:
branches:
- main
paths:
- web/**
- pingpong/**
- uv.lock
- pyproject.toml
jobs:
tag:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 11 # Fetch the last 10 commits and the current one
fetch-tags: true
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Add versioning tag to commit
env:
CHANGED_FILES_STRING: ${{ steps.changed-files.outputs.all_changed_files }}
GH_TOKEN: ${{ github.token }}
run: |
set -e
python << 'EOF'
import os
import re
import subprocess
import sys
import json
LEGACY_TAG_PATTERN = re.compile(r'^(\d+)-srv(\d+)-web(\d+)$')
NEW_TAG_PATTERN = re.compile(r'^v(\d+)\+srv(\d+)\.web(\d+)$')
def is_versioning_tag(tag):
return bool(LEGACY_TAG_PATTERN.match(tag) or NEW_TAG_PATTERN.match(tag))
def get_last_tag(n=1):
if n > 10:
raise ValueError('No versioning tags found on the last 10 commits')
try:
# Get all tags pointing to the HEAD~n commit
tags = []
try:
# Get the shorthand Git SHA of HEAD~{n}
sha_output = subprocess.check_output(
['git', 'rev-parse', '--short', f'HEAD~{n}'],
stderr=subprocess.DEVNULL
)
sha = sha_output.decode().strip()
print(f"Shorthand Git SHA of HEAD~{n}: {sha}")
output = subprocess.check_output(
['git', 'tag', '--points-at', f'HEAD~{n}'],
stderr=subprocess.DEVNULL
)
decoded_output = output.decode().strip()
print("Raw Output:", decoded_output) # Print the raw decoded output
tags = decoded_output.split('\n') # Split the output into a list of tags
print("Tags List:", tags) # Print the list of tags
except subprocess.CalledProcessError as e:
print(f"Error occurred: {e}")
# Filter tags that match our versioning format
matching_tags = [tag for tag in tags if is_versioning_tag(tag)]
print(f'Matching versioning tags on commit HEAD~{n}: {matching_tags}')
if not matching_tags:
print(f'No versioning tags found on commit HEAD~{n}')
return get_last_tag(n + 1)
print(f'Found versioning tags on commit HEAD~{n}: {matching_tags}')
# If multiple matching tags, return the one with the highest build number
return max(matching_tags, key=lambda t: parse_tag(t)['build'])
except subprocess.CalledProcessError as e:
raise ValueError("Failed to retrieve versioning tags from the previous commits", e)
def parse_tag(tag):
print(f'Parsing tag: {tag}')
match = LEGACY_TAG_PATTERN.match(tag)
if not match:
match = NEW_TAG_PATTERN.match(tag)
if not match:
raise ValueError(f"Invalid versioning tag format: {tag}. Expected NNNN-srvNNN-webNNN or vNNNN+srvNNN.webNNN")
try:
return {
'build': int(match.group(1)),
'srv': int(match.group(2)),
'web': int(match.group(3)),
}
except Exception as e:
raise ValueError(f"Failed to parse versioning tag: {tag}", e)
def create_new_tag(last_tag, web_update, server_update):
if last_tag:
last_tag_info = parse_tag(last_tag)
new_build = last_tag_info['build'] + 1
new_srv = last_tag_info['srv'] + (1 if server_update else 0)
new_web = last_tag_info['web'] + (1 if web_update else 0)
else:
new_build = 1
new_srv = 1
new_web = 1
return f"v{new_build}+srv{new_srv}.web{new_web}", new_srv, new_web
def get_compare_range():
default_range = 'HEAD~1..HEAD'
event_path = os.environ.get('GITHUB_EVENT_PATH')
if not event_path:
return default_range
try:
with open(event_path, encoding='utf-8') as f:
event = json.load(f)
except Exception as e:
print(f'Failed to parse GitHub event payload ({event_path}): {e}. Falling back to {default_range}')
return default_range
before = event.get('before')
after = event.get('after') or os.environ.get('GITHUB_SHA')
if not before or not after or before == '0' * 40:
return default_range
return f'{before}..{after}'
def get_changed_diff_lines(file_path, compare_range):
try:
patch = subprocess.check_output(
['git', 'diff', '--unified=0', compare_range, '--', file_path],
stderr=subprocess.DEVNULL,
).decode()
except subprocess.CalledProcessError:
return []
changed_lines = []
for line in patch.splitlines():
if line.startswith(('diff --git ', 'index ', '@@', '--- ', '+++ ')):
continue
if line.startswith(('+', '-')):
changed_lines.append(line[1:].strip())
return changed_lines
def is_version_only_change(file_path, compare_range):
patterns = {
'pyproject.toml': re.compile(r'^version\s*=\s*".*"$'),
'uv.lock': re.compile(r'^version\s*=\s*".*"$'),
'web/study/package.json': re.compile(r'^"version"\s*:\s*".*"\s*,?$'),
}
pattern = patterns.get(file_path)
if not pattern:
return False
def uv_lock_has_pingpong_context():
package_name_pattern = re.compile(r'^name\s*=\s*"pingpong"\s*$')
try:
patch = subprocess.check_output(
['git', 'diff', '--unified=1', compare_range, '--', 'uv.lock'],
stderr=subprocess.DEVNULL,
).decode()
except subprocess.CalledProcessError:
return False
hunk_has_pingpong_name = False
hunk_has_version_change = False
for line in patch.splitlines():
if line.startswith(('diff --git ', 'index ', '--- ', '+++ ')):
continue
if line.startswith('@@'):
if hunk_has_pingpong_name and hunk_has_version_change:
return True
hunk_has_pingpong_name = False
hunk_has_version_change = False
continue
if not line or line.startswith('\\'):
continue
if line[0] not in {' ', '+', '-'}:
continue
content = line[1:].strip()
if package_name_pattern.match(content):
hunk_has_pingpong_name = True
if line[0] in {'+', '-'} and pattern.match(content):
hunk_has_version_change = True
return hunk_has_pingpong_name and hunk_has_version_change
changed_lines = get_changed_diff_lines(file_path, compare_range)
if not changed_lines:
return False
if file_path == 'uv.lock':
only_version = (
len(changed_lines) == 2
and all(pattern.match(line) for line in changed_lines)
and uv_lock_has_pingpong_context()
)
else:
only_version = all(pattern.match(line) for line in changed_lines)
print(f'Checking version-only change for {file_path} in {compare_range}: {only_version}')
return only_version
def detect_component_updates(changed_files, compare_range):
web_files = [file for file in changed_files if file.startswith('web/')]
server_files = [
file for file in changed_files
if file.startswith(('pingpong/', 'saml/', 'alembic/')) or file in ['uv.lock', 'pyproject.toml']
]
web_update = any(file != 'web/study/package.json' for file in web_files)
if not web_update and 'web/study/package.json' in web_files:
web_update = not is_version_only_change('web/study/package.json', compare_range)
server_update = any(file not in {'pyproject.toml', 'uv.lock'} for file in server_files)
if not server_update and 'pyproject.toml' in server_files:
server_update = not is_version_only_change('pyproject.toml', compare_range)
if not server_update and 'uv.lock' in server_files:
server_update = not is_version_only_change('uv.lock', compare_range)
return web_update, server_update
try:
subprocess.run(['git', 'fetch', 'origin', '--tags', '--depth=11', 'HEAD'], check=True)
changed_files = [file for file in os.environ['CHANGED_FILES_STRING'].split() if file]
compare_range = get_compare_range()
web_update, server_update = detect_component_updates(changed_files, compare_range)
print(f'Changed files: {changed_files}')
print(f'Component updates -> server: {server_update}, web: {web_update}')
last_tag = get_last_tag()
new_tag, new_srv, new_web = create_new_tag(last_tag, web_update, server_update)
subprocess.run(['git', 'tag', new_tag], check=True)
subprocess.run(['git', 'push', 'origin', new_tag], check=True)
print(f"Successfully pushed new versioning tag: {new_tag}")
except Exception as e:
print(f"Error: {str(e)}", file=sys.stderr)
sys.exit(1)
EOF
shell: bash