Skip to content

Commit 96c2773

Browse files
claude-codePaebbelsclaude
committed
Add the 'SynchronizeForks' composite action.
Convert the inline shell script of 'Paebbels/SynchronizeForks' into a reusable composite action, so consuming repositories keep only their '*.repos' configuration files. The algorithm is unchanged in what it synchronizes, but it now reports what the original silently ignored: a missing '<organisation>.repos' file, a malformed repository line, and the last entry of a file without a final newline. Multi-line 'gh repo sync' output is escaped for error annotations, inputs reach the script through the environment rather than by string interpolation, and 'dry-run', 'force' and 'fail-on-error' are new parameters. 'Verification.yml' runs the action in dry-run mode against the fixtures in 'tests/' and asserts its output parameters. Co-Authored-By: Patrick Lehmann <Paebbels@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnpaXYDwfNwnJrQew78j3f
1 parent 8542632 commit 96c2773

15 files changed

Lines changed: 689 additions & 5 deletions

File tree

‎.editorconfig‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
# end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
indent_style = tab
9+
indent_size = 2
10+
tab_width = 2
11+
12+
[*.{yml,yaml}]
13+
indent_style = space
14+
indent_size = 2
15+
16+
[*.md]
17+
trim_trailing_whitespace = false

‎.github/CODEOWNERS‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
* @Paebbels
2+
3+
/.github/ @Paebbels
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright © 2026 The pyTooling Authors
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6+
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
7+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8+
# persons to whom the Software is furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11+
# Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17+
name: Check-Outputs
18+
description: Check the output parameters of the 'SynchronizeForks' action against their expected values.
19+
inputs:
20+
synchronized:
21+
description: Actual number of synchronized branches.
22+
type: string
23+
required: true
24+
skipped:
25+
description: Actual number of skipped entries.
26+
type: string
27+
required: true
28+
errors:
29+
description: Actual number of errors.
30+
type: string
31+
required: true
32+
expected-synchronized:
33+
description: Expected number of synchronized branches.
34+
type: string
35+
required: true
36+
expected-skipped:
37+
description: Expected number of skipped entries.
38+
type: string
39+
required: true
40+
expected-errors:
41+
description: Expected number of errors.
42+
type: string
43+
required: true
44+
45+
runs:
46+
using: composite
47+
steps:
48+
- name: 📋 Verify output parameters
49+
shell: bash
50+
env:
51+
SYNCHRONIZED: ${{ inputs.synchronized }}
52+
SKIPPED: ${{ inputs.skipped }}
53+
ERRORS: ${{ inputs.errors }}
54+
EXPECTED_SYNCHRONIZED: ${{ inputs.expected-synchronized }}
55+
EXPECTED_SKIPPED: ${{ inputs.expected-skipped }}
56+
EXPECTED_ERRORS: ${{ inputs.expected-errors }}
57+
run: |
58+
# 📋 Verify output parameters
59+
set +e
60+
61+
ANSI_LIGHT_RED=$'\x1b[91m'
62+
ANSI_LIGHT_GREEN=$'\x1b[92m'
63+
ANSI_NOCOLOR=$'\x1b[0m'
64+
65+
errors=0
66+
checkOutput() {
67+
local name="${1}" actual="${2}" expected="${3}"
68+
69+
printf "%s" "Checking output parameter '${name}' ... "
70+
if [[ "${actual}" == "${expected}" ]]; then
71+
printf "%s\n" "${ANSI_LIGHT_GREEN}[PASSED]${ANSI_NOCOLOR} (${actual})"
72+
else
73+
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR} (expected '${expected}', got '${actual}')"
74+
printf "::error title=%s::%s\n" "check-outputs" "Output parameter '${name}' is '${actual}', but '${expected}' was expected."
75+
errors=$((errors + 1))
76+
fi
77+
}
78+
79+
checkOutput "synchronized" "${SYNCHRONIZED}" "${EXPECTED_SYNCHRONIZED}"
80+
checkOutput "skipped" "${SKIPPED}" "${EXPECTED_SKIPPED}"
81+
checkOutput "errors" "${ERRORS}" "${EXPECTED_ERRORS}"
82+
83+
printf "%s\n" ""
84+
if [[ ${errors} -ne 0 ]]; then
85+
printf "%s\n" "${ANSI_LIGHT_RED}Counted ${errors} errors.${ANSI_NOCOLOR}"
86+
exit 1
87+
else
88+
printf "%s\n" "${ANSI_LIGHT_GREEN}No errors found.${ANSI_NOCOLOR}"
89+
fi

‎.github/dependabot.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: 2
2+
updates:
3+
# Maintain GitHub Action runners
4+
- package-ecosystem: "github-actions"
5+
directory: "/"
6+
target-branch: dev
7+
commit-message:
8+
prefix: "[Dependabot]"
9+
labels:
10+
- Dependencies
11+
assignees:
12+
- Paebbels
13+
schedule:
14+
interval: "daily" # Checks on Monday trough Friday.

‎.github/pull_request_template.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# New Features
2+
3+
* tbd
4+
* tbd
5+
6+
# Changes
7+
8+
* tbd
9+
* tbd
10+
11+
# Bug Fixes
12+
13+
* tbd
14+
* tbd
15+
16+
# Documentation
17+
18+
* tbd
19+
* tbd
20+
21+
# Unit Tests
22+
23+
* tbd
24+
* tbd
25+
26+
----------
27+
# Related Issues and Pull-Requests
28+
29+
* tbd
30+
* tbd

‎.github/workflows/Verification.yml‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright © 2026 The pyTooling Authors
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6+
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
7+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8+
# persons to whom the Software is furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11+
# Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17+
name: Verification of the Synchronize Forks Action
18+
19+
on:
20+
push:
21+
workflow_dispatch:
22+
23+
jobs:
24+
DryRun:
25+
name: 🐧 Dry-run with a valid configuration
26+
runs-on: ubuntu-26.04
27+
28+
steps:
29+
- name: ⏬ Checkout repository to use the local action
30+
uses: actions/checkout@v7
31+
32+
- name: 🔄 Synchronize forked repositories
33+
id: synchronize
34+
uses: ./
35+
with:
36+
github-token: ${{ github.token }}
37+
target-organisation: Paebbels
38+
directory: tests/Valid
39+
dry-run: true
40+
41+
- name: 📋 Verify output parameters
42+
uses: ./.github/actions/check-outputs
43+
with:
44+
synchronized: ${{ steps.synchronize.outputs.synchronized }}
45+
skipped: ${{ steps.synchronize.outputs.skipped }}
46+
errors: ${{ steps.synchronize.outputs.errors }}
47+
# 1 branch of 'ghdl' + 1 branch of 'progit2' + 2 branches of 'itertree'
48+
expected-synchronized: 4
49+
# 1 commented out organisation + 1 commented out repository
50+
expected-skipped: 2
51+
expected-errors: 0
52+
53+
ErrorHandling:
54+
name: 🐧 Error handling with an invalid configuration
55+
runs-on: ubuntu-26.04
56+
57+
steps:
58+
- name: ⏬ Checkout repository to use the local action
59+
uses: actions/checkout@v7
60+
61+
- name: 🔄 Synchronize forked repositories
62+
id: synchronize
63+
uses: ./
64+
with:
65+
github-token: ${{ github.token }}
66+
target-organisation: Paebbels
67+
directory: tests/Invalid
68+
dry-run: true
69+
fail-on-error: false
70+
71+
- name: 📋 Verify output parameters
72+
uses: ./.github/actions/check-outputs
73+
with:
74+
synchronized: ${{ steps.synchronize.outputs.synchronized }}
75+
skipped: ${{ steps.synchronize.outputs.skipped }}
76+
errors: ${{ steps.synchronize.outputs.errors }}
77+
expected-synchronized: 1
78+
expected-skipped: 0
79+
# 1 missing 'Missing.repos' + 2 malformed lines in 'ghdl.repos'
80+
expected-errors: 3
81+
82+
FailOnError:
83+
name: 🐧 Counted errors let the action fail
84+
runs-on: ubuntu-26.04
85+
86+
steps:
87+
- name: ⏬ Checkout repository to use the local action
88+
uses: actions/checkout@v7
89+
90+
- name: 🔄 Synchronize forked repositories
91+
id: synchronize
92+
continue-on-error: true
93+
uses: ./
94+
with:
95+
github-token: ${{ github.token }}
96+
target-organisation: Paebbels
97+
directory: tests/Invalid
98+
dry-run: true
99+
100+
- name: 📋 Verify the action failed
101+
shell: bash
102+
env:
103+
OUTCOME: ${{ steps.synchronize.outcome }}
104+
run: |
105+
# 📋 Verify the action failed
106+
set +e
107+
108+
ANSI_LIGHT_RED=$'\x1b[91m'
109+
ANSI_LIGHT_GREEN=$'\x1b[92m'
110+
ANSI_NOCOLOR=$'\x1b[0m'
111+
112+
printf "%s" "Checking the action's outcome ... "
113+
if [[ "${OUTCOME}" == "failure" ]]; then
114+
printf "%s\n" "${ANSI_LIGHT_GREEN}[PASSED]${ANSI_NOCOLOR} (failure)"
115+
else
116+
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR} (expected 'failure', got '${OUTCOME}')"
117+
printf "::error title=%s::%s\n" "FailOnError" "The action's outcome is '${OUTCOME}', but 'failure' was expected."
118+
exit 1
119+
fi

‎LICENSE.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# The MIT License (MIT)
2+
3+
Copyright © 2026 The pyTooling Authors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6+
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
7+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8+
persons to whom the Software is furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11+
Software.
12+
13+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

0 commit comments

Comments
 (0)