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
3 changes: 3 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
set -euo pipefail
exec "$(git rev-parse --show-toplevel)/scripts/pre-commit.sh"
31 changes: 31 additions & 0 deletions .github/workflows/assets/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ set_git_bot_config() {

repo_exists() { gh repo view "$1/$2" &>/dev/null; }

# Returns 0 if org/repo has an open PR into local-{lang_code} with head matching
# "translation-{lang_code}-*"; 1 if none; 2 if the GitHub API check failed.
has_open_translation_pr() {
local org="$1" repo="$2" lang_code="$3"
local base_br="local-${lang_code}"
local output
if ! output=$(gh pr list --repo "$org/$repo" --state open --base "$base_br" --json headRefName \
--jq ".[] | select(.headRefName | startswith(\"translation-${lang_code}-\")) | .headRefName" \
2>&1); then
echo " Error: could not list open PRs for $org/$repo (base=$base_br): $output" >&2
return 2
fi
[[ -n "$output" ]]
}
Comment thread
AuraMindNest marked this conversation as resolved.

# ── Git clone helpers ────────────────────────────────────────────────

# Clone repo at branch/tag into $3. Pass "keep" as $4 to preserve .git.
Expand Down Expand Up @@ -197,6 +212,22 @@ parse_list() {
done
}

# Parse "[.adoc, .md]" or '[".adoc",".md"]' into one extension per line.
parse_extensions() {
local s="$1"
s="${s//[[:space:]]/}"; [[ -z "$s" ]] && return
s="${s#[}"; s="${s%]}"
local result=()
IFS=',' read -ra parts <<< "$s"
for part in "${parts[@]}"; do
part="${part//\"/}"; part="${part//\'/}"
[[ -z "$part" ]] && continue
[[ "$part" == .* ]] || part=".${part}"
result+=("$part")
done
printf '%s\n' "${result[@]}"
}

# Return 0 when $1 is a well-formed Weblate language code.
is_valid_lang_code() {
local code="$1"
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/assets/translation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# shellcheck shell=bash
# start-translation orchestration helpers.
# Source after env.sh and lib.sh. Requires globals:
# MODULE_ORG, MASTER_BRANCH, BOOST_ORG, BOOST_WORK, ORG_WORK, libs_ref,
# lang_codes_arr, add_or_update (associative), ORG_REPO_MISSING, META_MISSING,
# NO_DOC_PATHS, GITHUB_WORKSPACE.
# shellcheck disable=SC2034,SC2154

# Wipe dest_repo (except .git), copy pruned source, commit, push master only.
sync_repo_master() {
local dest_repo="$1" sub_clone="$2" libs_ref="$3"
find "$dest_repo" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + || return 2
cp -r "$sub_clone/." "$dest_repo/" || return 2
set_git_bot_config "$dest_repo"
git -C "$dest_repo" add -A || return 2
if ! git -C "$dest_repo" diff --cached --quiet; then
git -C "$dest_repo" commit -m "Update the original documentation of $libs_ref" || return 2
fi
git -C "$dest_repo" push origin "$MASTER_BRANCH" || return 2
}

# Merge master into local-{lang_code} and push.
update_local_merge_from_master() {
local repo_dir="$1" lang_code="$2"
local local_br="local-${lang_code}"
git -C "$repo_dir" fetch origin "$MASTER_BRANCH" || return 2
git -C "$repo_dir" fetch origin "$local_br" || return 2
git -C "$repo_dir" checkout -B "$local_br" "origin/$local_br" || return 2
git -C "$repo_dir" merge "origin/$MASTER_BRANCH" || return 2
git -C "$repo_dir" push origin "${local_br}:${local_br}" || return 2
}

# Create local-{lang_code} in a library mirror repo from master, with create-tag.yml.
ensure_local_branch_in_repo() {
local dest_repo="$1" sub_name="$2" lang_code="$3"
local local_br="local-${lang_code}"
if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then
echo " Branch $local_br already exists in $sub_name." >&2
return 0
fi
echo " Creating branch $local_br in $sub_name from $MASTER_BRANCH..." >&2
git -C "$dest_repo" fetch origin "$MASTER_BRANCH" || return 2
git -C "$dest_repo" checkout -B "$local_br" "origin/$MASTER_BRANCH" || return 2
add_create_tag_workflow "$dest_repo" || return 2
git -C "$dest_repo" push -u origin "$local_br" || return 2
echo " Created branch $local_br." >&2
}

# Handle local-{lang_code} branch in a library mirror repo after master is synced.
# Returns 0 if submodule should be added to add_or_update[lang_code]; 1 if skipped (open PR); 2 on git failure.
process_local_branch() {
local dest_repo="$1" sub_name="$2" lang_code="$3"
local local_br="local-${lang_code}"
if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then
has_open_translation_pr "$MODULE_ORG" "$sub_name" "$lang_code"
case $? in
0) echo " Open translation PR found for $sub_name ($local_br), skipping." >&2; return 1 ;;
2) return 2 ;;
esac
update_local_merge_from_master "$dest_repo" "$lang_code" || return 2
else
ensure_local_branch_in_repo "$dest_repo" "$sub_name" "$lang_code" || return 2
fi
return 0
}

process_one_submodule() {
local sub_name="$1" doc_paths

if ! repo_exists "$MODULE_ORG" "$sub_name"; then
ORG_REPO_MISSING+=("$sub_name")
echo " Error: $MODULE_ORG/$sub_name does not exist. Run add-submodules first." >&2
return 2
fi

doc_paths=$(get_doc_paths "$sub_name" "$libs_ref") || {
META_MISSING+=("$sub_name")
echo " No libraries.json." >&2; return 2
}
[[ -z "$doc_paths" ]] && {
NO_DOC_PATHS+=("$sub_name")
echo " No doc paths in metadata, skipping." >&2; return 1
}

local sub_clone="$BOOST_WORK/$sub_name"
clone_repo "https://github.com/${BOOST_ORG}/${sub_name}.git" \
"$libs_ref" "$sub_clone" || { echo " Clone failed." >&2; return 2; }

local -a paths_arr
mapfile -t paths_arr <<< "$doc_paths"
prune_to_doc_only "$sub_clone" "${paths_arr[@]}"

local org_repo_url="https://github.com/${MODULE_ORG}/${sub_name}.git"
local dest_repo="$ORG_WORK/$sub_name"
clone_repo "$org_repo_url" "$MASTER_BRANCH" "$dest_repo" keep || {
echo " clone_repo failed." >&2; return 2
}

sync_repo_master "$dest_repo" "$sub_clone" "$libs_ref" || return 2

local any_added=0 rc
for lang_code in "${lang_codes_arr[@]}"; do
if process_local_branch "$dest_repo" "$sub_name" "$lang_code"; then
if [[ -n "${add_or_update[$lang_code]:-}" ]]; then
add_or_update["$lang_code"]+=" $sub_name"
else
add_or_update["$lang_code"]="$sub_name"
fi
any_added=1
else
rc=$?
[[ $rc -eq 2 ]] && return 2
fi
done
[[ $any_added -eq 1 ]]
}
34 changes: 16 additions & 18 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,20 @@ jobs:
with:
persist-credentials: false

- name: ShellCheck
run: |
set -euo pipefail
shellcheck -x \
.github/workflows/assets/env.sh \
.github/workflows/assets/lib.sh \
scripts/*.sh
- name: ShellCheck and actionlint
run: scripts/lint.sh

- name: actionlint
run: |
set -euo pipefail
version="1.7.7"
tarball="actionlint_${version}_linux_amd64.tar.gz"
expected_sha256="023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757"
curl -fsSL -o "$tarball" \
"https://github.com/rhysd/actionlint/releases/download/v${version}/${tarball}"
echo "${expected_sha256} ${tarball}" | sha256sum -c -
tar -xzf "$tarball"
./actionlint -color
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Install bats
run: sudo apt-get update && sudo apt-get install -y bats

- name: Run tests
run: make test
145 changes: 2 additions & 143 deletions .github/workflows/start-translation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,159 +113,18 @@ jobs:
source "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh"
# shellcheck source=assets/lib.sh
source "$GITHUB_WORKSPACE/.github/workflows/assets/lib.sh"
# shellcheck source=assets/translation.sh
source "$GITHUB_WORKSPACE/.github/workflows/assets/translation.sh"

# LIBS_REF: workflow env
# shellcheck disable=SC2153
libs_ref="${LIBS_REF:?}"
# BOOST_ORG: env.sh
# shellcheck disable=SC2153
boost_org="${BOOST_ORG:?}"

ORG_WORK="$WORK_DIR/$MODULE_ORG"
mkdir -p "$ORG_WORK"

# ── GitHub API helpers (via gh CLI) ──────────────────────────────────

# Returns 0 if org/repo has an open PR into local-{lang_code} with head matching
# "translation-{lang_code}-*".
has_open_translation_pr() {
local org="$1" repo="$2" lang_code="$3"
local base_br="local-${lang_code}"
gh pr list --repo "$org/$repo" --state open --base "$base_br" --json headRefName \
--jq ".[] | select(.headRefName | startswith(\"translation-${lang_code}-\")) | .headRefName" \
2>/dev/null | grep -q .
}

# ── Organization repo sync helpers ──────────────────────────────────────

# Wipe dest_repo (except .git), copy pruned source, commit, push master only.
sync_repo_master() {
local dest_repo="$1" sub_clone="$2" libs_ref="$3"
find "$dest_repo" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + || return 2
cp -r "$sub_clone/." "$dest_repo/" || return 2
set_git_bot_config "$dest_repo"
git -C "$dest_repo" add -A || return 2
if ! git -C "$dest_repo" diff --cached --quiet; then
git -C "$dest_repo" commit -m "Update the original documentation of $libs_ref" || return 2
fi
git -C "$dest_repo" push origin "$MASTER_BRANCH" || return 2
}

# Merge master into local-{lang_code} and push.
update_local_merge_from_master() {
local repo_dir="$1" lang_code="$2"
local local_br="local-${lang_code}"
git -C "$repo_dir" fetch origin "$MASTER_BRANCH" || return 2
git -C "$repo_dir" fetch origin "$local_br" || return 2
git -C "$repo_dir" checkout -B "$local_br" "origin/$local_br" || return 2
git -C "$repo_dir" merge "origin/$MASTER_BRANCH" || return 2
git -C "$repo_dir" push origin "${local_br}:${local_br}" || return 2
}

# Create local-{lang_code} in a library mirror repo from master, with create-tag.yml.
ensure_local_branch_in_repo() {
local dest_repo="$1" sub_name="$2" lang_code="$3"
local local_br="local-${lang_code}"
if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then
echo " Branch $local_br already exists in $sub_name." >&2
return 0
fi
echo " Creating branch $local_br in $sub_name from $MASTER_BRANCH..." >&2
git -C "$dest_repo" fetch origin "$MASTER_BRANCH" || return 2
git -C "$dest_repo" checkout -B "$local_br" "origin/$MASTER_BRANCH" || return 2
add_create_tag_workflow "$dest_repo" || return 2
git -C "$dest_repo" push -u origin "$local_br" || return 2
echo " Created branch $local_br." >&2
}

# Handle local-{lang_code} branch in a library mirror repo after master is synced.
# Returns 0 if submodule should be added to add_or_update[lang_code]; 1 if skipped (open PR); 2 on git failure.
process_local_branch() {
local dest_repo="$1" sub_name="$2" lang_code="$3"
local local_br="local-${lang_code}"
if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then
if has_open_translation_pr "$MODULE_ORG" "$sub_name" "$lang_code"; then
echo " Open translation PR found for $sub_name ($local_br), skipping." >&2
return 1
fi
update_local_merge_from_master "$dest_repo" "$lang_code" || return 2
else
ensure_local_branch_in_repo "$dest_repo" "$sub_name" "$lang_code" || return 2
fi
return 0
}

# ── Per-submodule processing ──────────────────────────────────────────

process_one_submodule() {
local sub_name="$1" doc_paths

if ! repo_exists "$MODULE_ORG" "$sub_name"; then
ORG_REPO_MISSING+=("$sub_name")
echo " Error: $MODULE_ORG/$sub_name does not exist. Run add-submodules first." >&2
return 2
fi

doc_paths=$(get_doc_paths "$sub_name" "$libs_ref") || {
META_MISSING+=("$sub_name")
echo " No libraries.json." >&2; return 2
}
[[ -z "$doc_paths" ]] && {
NO_DOC_PATHS+=("$sub_name")
echo " No doc paths in metadata, skipping." >&2; return 1
}

local sub_clone="$BOOST_WORK/$sub_name"
clone_repo "https://github.com/${boost_org}/${sub_name}.git" \
"$libs_ref" "$sub_clone" || { echo " Clone failed." >&2; return 2; }

local -a paths_arr
mapfile -t paths_arr <<< "$doc_paths"
prune_to_doc_only "$sub_clone" "${paths_arr[@]}"

local org_repo_url="https://github.com/${MODULE_ORG}/${sub_name}.git"
local dest_repo="$ORG_WORK/$sub_name"
clone_repo "$org_repo_url" "$MASTER_BRANCH" "$dest_repo" keep || {
echo " clone_repo failed." >&2; return 2
}

sync_repo_master "$dest_repo" "$sub_clone" "$libs_ref" || return 2

local any_added=0
for lang_code in "${lang_codes_arr[@]}"; do
if process_local_branch "$dest_repo" "$sub_name" "$lang_code"; then
if [[ -n "${add_or_update[$lang_code]:-}" ]]; then
add_or_update["$lang_code"]+=" $sub_name"
else
add_or_update["$lang_code"]="$sub_name"
fi
any_added=1
else
rc=$?
[[ $rc -eq 2 ]] && return 2
fi
done
[[ $any_added -eq 1 ]]
}

# ── Weblate helpers ───────────────────────────────────────────────────

# Parse "[.adoc, .md]" or '[".adoc",".md"]' into one extension per line.
parse_extensions() {
local s="$1"
s="${s//[[:space:]]/}"; [[ -z "$s" ]] && return
s="${s#[}"; s="${s%]}"
local result=()
IFS=',' read -ra parts <<< "$s"
for part in "${parts[@]}"; do
part="${part//\"/}"; part="${part//\'/}"
[[ -z "$part" ]] && continue
[[ "$part" == .* ]] || part=".${part}"
result+=("$part")
done
printf '%s\n' "${result[@]}"
}

# POST to Weblate add-or-update (async: server returns 202 + task_id quickly).
# Payload: {organization, add_or_update: {lang_code: [subs...]}, version, extensions}
trigger_weblate() {
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
data/

# Local test scripts
test/
test/

# Tool caches (actionlint binary downloaded by scripts/lint.sh)
.cache/
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.PHONY: lint test check
lint:
scripts/lint.sh

test:
scripts/test.sh

check: lint test
Loading
Loading