Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions .github/workflows/sync-node-ncrypto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ on:
workflow_dispatch:
inputs:
node_ref:
description: nodejs/node ref to sync from
required: true
default: main
base_node_ref:
description: Optional previous nodejs/node ref for bootstrap or recovery
description: Optional nodejs/node release tag to sync from
required: false
default: ''

permissions:
contents: write
Expand All @@ -29,11 +24,9 @@ jobs:
id: sync
env:
NODE_REF: ${{ inputs.node_ref }}
BASE_NODE_REF: ${{ inputs.base_node_ref }}
run: |
python3 tools/sync-node-ncrypto.py \
--node-ref "$NODE_REF" \
--base-node-ref "$BASE_NODE_REF"
--node-ref "$NODE_REF"

- name: Stop when there are no changes
if: steps.sync.outputs.has_changes != 'true'
Expand All @@ -45,7 +38,9 @@ jobs:
run: |
branch='${{ steps.sync.outputs.branch_name }}'
git switch -c "$branch"
git fetch origin "$branch:refs/remotes/origin/$branch" || true
if git ls-remote --exit-code --heads origin "$branch" >/dev/null; then
git fetch origin "$branch:refs/remotes/origin/$branch"
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add \
Expand All @@ -54,7 +49,7 @@ jobs:
src/engine.cpp \
src/ncrypto.cpp
git commit \
-m 'chore: sync ncrypto from nodejs/node' \
-m 'chore: sync ncrypto from Node.js ${{ steps.sync.outputs.target_version }}' \
-m 'Node-Base-Commit: ${{ steps.sync.outputs.base_sha }}' \
-m 'Node-Target-Commit: ${{ steps.sync.outputs.target_sha }}'
git push --force-with-lease origin "$branch"
Expand All @@ -64,11 +59,10 @@ jobs:
if: steps.sync.outputs.has_changes == 'true'
run: |
{
echo 'Syncs `deps/ncrypto` from `nodejs/node` into this repository.'
echo 'Syncs `deps/ncrypto` from Node.js `${{ steps.sync.outputs.target_version }}`.'
echo
echo '- Base node commit: `${{ steps.sync.outputs.base_sha }}`'
echo '- Target node commit: `${{ steps.sync.outputs.target_sha }}`'
echo '- Conflicts: `${{ steps.sync.outputs.has_conflicts }}`'
if [ '${{ steps.sync.outputs.has_conflicts }}' = 'true' ]; then
echo
echo 'This PR was opened as a draft because the 3-way merge produced conflicts:'
Expand All @@ -83,14 +77,14 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
branch='${{ steps.commit.outputs.branch }}'
title='chore: sync ncrypto from nodejs/node'
existing_url="$(gh pr view "$branch" --json url --jq .url 2>/dev/null || true)"
title='chore: sync ncrypto from Node.js ${{ steps.sync.outputs.target_version }}'
existing_url="$(gh pr list --state open --head "$branch" --json url --jq '.[0].url // ""')"
if [ -n "$existing_url" ]; then
gh pr edit "$branch" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md"
gh pr edit "$existing_url" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md"
if [ '${{ steps.sync.outputs.has_conflicts }}' = 'true' ]; then
gh pr ready "$branch" --undo || true
gh pr ready "$existing_url" --undo || true
else
gh pr ready "$branch" || true
gh pr ready "$existing_url" || true
fi
echo "$existing_url"
exit 0
Expand Down
74 changes: 66 additions & 8 deletions tools/sync-node-ncrypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,53 @@ def fetch_ref(repository: str, ref: str) -> str:
return git(('rev-parse', 'FETCH_HEAD^{commit}')).stdout.decode().strip()


def release_version(tag: str) -> tuple[int, int, int] | None:
if tag.startswith('refs/tags/'):
tag = tag.removeprefix('refs/tags/')
if not tag.startswith('v'):
return None

parts = tag.removeprefix('v').split('.')
if len(parts) != 3 or not all(part.isdecimal() for part in parts):
return None

major, minor, patch = parts
return (int(major), int(minor), int(patch))


def latest_stable_release(repository: str) -> str:
output = git(('ls-remote', '--tags', '--refs', repository, 'v*.*.*')).stdout.decode()
releases: list[tuple[tuple[int, int, int], str]] = []
for line in output.splitlines():
try:
_, ref = line.split(None, maxsplit=1)
except ValueError:
continue

version = release_version(ref)
if version is not None:
releases.append((version, ref.removeprefix('refs/tags/')))

if not releases:
raise SyncError(f'could not find stable Node.js release tags in {repository}')

return max(releases)[1]


def resolve_target_ref(repository: str, ref: str) -> str:
if not ref:
return latest_stable_release(repository)

return ref


def target_version(ref: str, sha: str) -> str:
if release_version(ref) is not None:
return ref.removeprefix('refs/tags/')

return sha[:12]


def load_state(path: Path) -> str | None:
if not path.exists():
return None
Expand Down Expand Up @@ -180,8 +227,10 @@ def sync(args: argparse.Namespace) -> int:
if base_ref is None:
raise SyncError(f'{state_path} does not record a node_commit; pass --base-node-ref to bootstrap the sync')

target_ref = resolve_target_ref(args.node_repository, args.node_ref)
base_sha = fetch_ref(args.node_repository, base_ref)
target_sha = fetch_ref(args.node_repository, args.node_ref)
target_sha = fetch_ref(args.node_repository, target_ref)
target_node_version = target_version(target_ref, target_sha)

check_unmapped_files(target_sha)

Expand All @@ -196,7 +245,7 @@ def sync(args: argparse.Namespace) -> int:
)

conflicts: list[str] = []
would_change = current_state != target_sha
would_change_mapped_files = False
with tempfile.TemporaryDirectory(prefix='sync-node-ncrypto-') as temporary_directory_name:
temporary_directory = Path(temporary_directory_name)
for source, destination in MAPPINGS.items():
Expand All @@ -208,30 +257,35 @@ def sync(args: argparse.Namespace) -> int:
temporary_directory=temporary_directory,
)
if destination.read_bytes() != merged:
would_change = True
would_change_mapped_files = True
if not args.dry_run:
destination.write_bytes(merged)
if conflicted:
conflicts.append(str(destination))

if not args.dry_run:
paths = list(MAPPINGS.values())
changed = would_change_mapped_files if args.dry_run else has_changes(paths)

if not args.dry_run and changed:
write_state(state_path, target_sha)

paths = [*MAPPINGS.values(), state_path]
changed = would_change if args.dry_run else has_changes(paths)
outputs = {
'base_sha': base_sha,
'target_sha': target_sha,
'target_short_sha': target_sha[:12],
'target_ref': target_ref,
'target_version': target_node_version,
'has_changes': changed,
'has_conflicts': bool(conflicts),
'conflicts': conflicts,
'branch_name': f'sync-node-ncrypto/{target_sha[:12]}',
'branch_name': f'sync-node-ncrypto/{target_node_version}',
}
write_github_output(outputs)

print(f'Base node commit: {base_sha}')
print(f'Target node commit: {target_sha}')
print(f'Target node ref: {target_ref}')
print(f'Target Node.js: {target_node_version}')
print(f'Changed files: {str(changed).lower()}')
print(f'Conflicts: {str(bool(conflicts)).lower()}')
for path in conflicts:
Expand All @@ -243,7 +297,11 @@ def sync(args: argparse.Namespace) -> int:
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description='Sync nodejs/node deps/ncrypto into standalone ncrypto.')
parser.add_argument('--node-repository', default=NODE_REPOSITORY)
parser.add_argument('--node-ref', default='main')
parser.add_argument(
'--node-ref',
default='',
help='nodejs/node ref to sync from; defaults to latest stable release',
)
parser.add_argument('--base-node-ref', default='')
parser.add_argument('--state-file', default=str(STATE_FILE))
parser.add_argument('--dry-run', action='store_true')
Expand Down
Loading