Skip to content

Commit 3a3b289

Browse files
committed
Add upstream sync workflow and version tag detection
Scheduled GitHub Actions workflow that syncs main and VBox-* branches from upstream every 6 hours, then scans Version.kmk commit history for release markers and creates version tags automatically. The ci branch is an orphan branch containing only workflow infrastructure. Mirror branches remain exact copies of upstream. Signed-off-by: Marcello Sylvester Bauer <marcello.bauer@9elements.com>
0 parents  commit 3a3b289

3 files changed

Lines changed: 239 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# create-version-tags.sh - Detect VirtualBox release commits and create git tags
5+
#
6+
# Scans Version.kmk commit history across all branches. Release commits
7+
# follow a strict naming convention in their commit messages:
8+
#
9+
# "7.1.16" -> v7.1.16 (GA release)
10+
# "7.1.8 respin." -> v7.1.8 (respin overrides original)
11+
# "7.2.0_BETA1." -> v7.2.0-beta1 (pre-release)
12+
# "7.2.0_RC1." -> v7.2.0-rc1 (release candidate)
13+
# "7.0.0 ALPHA1" -> v7.0.0-alpha1 (alpha)
14+
# "6.1.0 GA" -> v6.1.0 (GA suffix stripped)
15+
# "6.0.0_RC1 rebuild 2" -> v6.0.0-rc1 (rebuild overrides original)
16+
# "5.2.0 again." -> v5.2.0 (retag overrides original)
17+
#
18+
# Commits are processed in chronological order so that respins, rebuilds,
19+
# and retags naturally overwrite the original release commit -- the last
20+
# commit for each version wins.
21+
#
22+
# Usage:
23+
# create-version-tags.sh [--dry-run] [--push]
24+
#
25+
# Options:
26+
# --dry-run Print what tags would be created without creating them
27+
# --push Push new tags to origin after creating them
28+
29+
set -euo pipefail
30+
shopt -s extglob
31+
32+
DRY_RUN=false
33+
PUSH=false
34+
35+
for arg in "$@"; do
36+
case "$arg" in
37+
--dry-run) DRY_RUN=true ;;
38+
--push) PUSH=true ;;
39+
-h | --help)
40+
sed -n '2,/^[^#]/{ /^#/s/^# \?//p }' "$0"
41+
exit 0
42+
;;
43+
*)
44+
echo "error: unknown option: $arg" >&2
45+
echo "usage: $0 [--dry-run] [--push]" >&2
46+
exit 1
47+
;;
48+
esac
49+
done
50+
51+
# ---------------------------------------------------------------------------
52+
# Collect existing tags for fast lookup
53+
# ---------------------------------------------------------------------------
54+
declare -A existing_tags
55+
while IFS= read -r tag; do
56+
[[ -n "$tag" ]] && existing_tags["$tag"]=1
57+
done < <(git tag -l 'v*')
58+
59+
# ---------------------------------------------------------------------------
60+
# Walk all Version.kmk commits (chronological order) and detect releases
61+
# ---------------------------------------------------------------------------
62+
declare -A tag_map # tag name -> commit hash
63+
declare -a tag_order=() # preserve discovery order for deterministic output
64+
65+
while IFS=' ' read -r hash msg; do
66+
# Strip trailing whitespace, dots, and periods for matching
67+
msg_clean="${msg%"${msg##*[^. ]}"}"
68+
69+
# Match release patterns:
70+
# X.Y.Z (GA release)
71+
# X.Y.Z_BETAX / X.Y.Z_RCX (pre-release with underscore)
72+
# X.Y.Z ALPHAX / X.Y.Z AlphaX (pre-release with space)
73+
# X.Y.Z GA (explicit GA marker)
74+
# Optionally followed by: respin, rebuild [N], again
75+
if [[ "$msg_clean" =~ ^([0-9]+\.[0-9]+\.[0-9]+)([_ ](BETA[0-9]*|RC[0-9]*|ALPHA[0-9]*|Alpha[0-9]*|GA))?(\ (respin|rebuild[^.]*|again))?$ ]]; then
76+
version="${BASH_REMATCH[1]}"
77+
prerelease="${BASH_REMATCH[3]}"
78+
79+
# Build tag name
80+
tag="v${version}"
81+
if [[ -n "$prerelease" ]]; then
82+
prerelease_lower="$(echo "$prerelease" | tr '[:upper:]' '[:lower:]')"
83+
# "GA" is not a pre-release suffix -- it just confirms the release
84+
if [[ "$prerelease_lower" != "ga" ]]; then
85+
tag="${tag}-${prerelease_lower}"
86+
fi
87+
fi
88+
89+
# Record (overwrites previous entry for same tag -- last one wins)
90+
if [[ -z "${tag_map[$tag]:-}" ]]; then
91+
tag_order+=("$tag")
92+
fi
93+
tag_map["$tag"]="$hash"
94+
fi
95+
done < <(git log --all --reverse --format='%H %s' -- Version.kmk)
96+
97+
# ---------------------------------------------------------------------------
98+
# Create missing tags
99+
# ---------------------------------------------------------------------------
100+
created=0
101+
skipped=0
102+
103+
for tag in "${tag_order[@]}"; do
104+
hash="${tag_map[$tag]}"
105+
if [[ -n "${existing_tags[$tag]:-}" ]]; then
106+
skipped=$((skipped + 1))
107+
continue
108+
fi
109+
110+
if [[ "$DRY_RUN" == true ]]; then
111+
echo "[dry-run] would create tag $tag -> ${hash:0:12}"
112+
else
113+
git tag "$tag" "$hash"
114+
echo "created tag $tag -> ${hash:0:12}"
115+
fi
116+
created=$((created + 1))
117+
done
118+
119+
echo ""
120+
echo "summary: $created new, $skipped already existed, ${#tag_map[@]} total detected"
121+
122+
# ---------------------------------------------------------------------------
123+
# Push if requested
124+
# ---------------------------------------------------------------------------
125+
if [[ "$PUSH" == true && "$DRY_RUN" == false && "$created" -gt 0 ]]; then
126+
git push origin --tags
127+
echo "pushed tags to origin"
128+
elif [[ "$PUSH" == true && "$created" -eq 0 ]]; then
129+
echo "no new tags to push"
130+
fi

.github/workflows/sync-and-tag.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Sync upstream and create version tags
2+
3+
on:
4+
schedule:
5+
- cron: '17 */6 * * *' # Every 6 hours at :17
6+
workflow_dispatch: # Manual trigger
7+
8+
jobs:
9+
sync-and-tag:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- name: Checkout ci branch
15+
uses: actions/checkout@v4
16+
with:
17+
ref: ci
18+
fetch-depth: 0
19+
20+
- name: Fetch all origin refs
21+
run: |
22+
git fetch origin '+refs/heads/*:refs/remotes/origin/*' --prune
23+
git fetch origin --tags --prune-tags
24+
25+
- name: Add upstream remote and fetch
26+
run: |
27+
git remote add upstream https://github.com/VirtualBox/virtualbox.git
28+
git fetch upstream --prune
29+
30+
- name: Sync branches from upstream
31+
run: |
32+
# Sync main
33+
echo "::group::Syncing main"
34+
git push origin upstream/main:refs/heads/main
35+
echo "::endgroup::"
36+
37+
# Sync all upstream VBox-* branches
38+
for ref in $(git for-each-ref --format='%(refname:short)' refs/remotes/upstream/ \
39+
| grep '^upstream/VBox-'); do
40+
branch="${ref#upstream/}"
41+
echo "::group::Syncing $branch"
42+
git push origin "$ref:refs/heads/$branch"
43+
echo "::endgroup::"
44+
done
45+
46+
- name: Create version tags
47+
run: |
48+
chmod +x .github/scripts/create-version-tags.sh
49+
.github/scripts/create-version-tags.sh --push

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# VirtualBox Mirror with Version Tags
2+
3+
Fork of [VirtualBox/virtualbox](https://github.com/VirtualBox/virtualbox)
4+
with automatic version tagging.
5+
6+
The upstream repository does not provide git tags for releases. This fork
7+
adds them automatically by scanning `Version.kmk` commit messages for the
8+
release markers that Oracle uses internally.
9+
10+
## Branches
11+
12+
| Branch | Purpose |
13+
|---|---|
14+
| `ci` (default) | GitHub Actions workflows for sync and tagging |
15+
| `main` | Mirror of upstream trunk |
16+
| `VBox-7.1` | Mirror of upstream 7.1.x stable branch |
17+
| `VBox-7.2` | Mirror of upstream 7.2.x stable branch |
18+
19+
Mirror branches are synced from upstream every 6 hours. They are kept as
20+
exact copies -- no additional commits are added.
21+
22+
## Tags
23+
24+
Version tags are created automatically from `Version.kmk` release commits:
25+
26+
| Tag | Example |
27+
|---|---|
28+
| Stable release | `v7.2.6`, `v7.1.16` |
29+
| Pre-release | `v7.2.0-beta1`, `v7.2.0-rc1` |
30+
| Alpha | `v7.0.0-alpha1` |
31+
32+
Browse a specific release:
33+
34+
```
35+
git checkout v7.2.6
36+
```
37+
38+
## How it works
39+
40+
A scheduled GitHub Actions workflow ([sync-and-tag.yml](.github/workflows/sync-and-tag.yml)):
41+
42+
1. Fetches all branches from the upstream VirtualBox repository
43+
2. Fast-forwards the mirror branches (`main`, `VBox-*`)
44+
3. Scans `Version.kmk` commit history for release markers
45+
4. Creates and pushes tags for any new releases detected
46+
47+
The tag detection script ([create-version-tags.sh](.github/scripts/create-version-tags.sh))
48+
can also be run locally:
49+
50+
```bash
51+
# Preview what tags would be created
52+
.github/scripts/create-version-tags.sh --dry-run
53+
54+
# Create tags locally
55+
.github/scripts/create-version-tags.sh
56+
57+
# Create and push tags
58+
.github/scripts/create-version-tags.sh --push
59+
```
60+

0 commit comments

Comments
 (0)